I have an User model with a HABTM association with Tag model. I need all users who necessarily have all conditions, not just one.
Ex:
User.includes(:tags).where(tags: { id: [2,3,...] })
Returns users who have tags with id 2 and / or 3, but I would like to only return users who have tags with ids 2 AND 3.
I can think of this option:
User.includes(:tags).where(tags: { id: 2 }).where(tags: { id: 3 })
If you have a severals tag_ids, and if the intermediate table between user and tags is user_tags
tag_ids = [1, 2, 3, 4, 5]
where = tag_ids.map do |id|
"tags.id = #{id}"
end.join(" AND ")
puts where # "tags.id = 1 AND tags.id = 2 AND tags.id = 3 AND tags.id = 4 AND tags.id = 5"
User.joins("INNER JOIN user_tags ON user_tags.user_id = user.id INNER JOIN tags ON tags.id = user_tags.tag_id").where(where)
If you wish to continue using the includes, there is no other choice that continue using the where in the rails-ish way.
How about:
user_ids = Tag.where(id: [2,3]).pluck(:user_id).uniq
User.where(id: user_id)
pluck won't instantiate all the tag objects, so while this may not be ideal, it should at least be pretty quick.
Related
Let's suppose, there are three tables in the database:
courses (:id, :name)
course_details (:course_id, :effective_date, :status),
course_codes (:course_detail_id, :code)
course has many course_details
course_detail has many cource_codes
Course can have multiple course_details records which are effective_dated applicable (means only one record of course_detail will be used in the system).
Problem statement: I want to filter courses by course codes given code. And course should only be filtered by the course_codes which are linked with effective dated course_detail and should skip the past effective dated records.
course = Course.find(params[:id])
course_detail = CourseDetail.find_by(effective_date: CourseDetail.max(effective_date), course_id: course.id)
If I use this code this will filter course irrespective of effective_dated course_details:
Course.left_joins(course_details: :course_codes).where(course_details: { course_codes: { code: params[:code] } })
courses:
Id
Name
1
English
2
Maths
course_details:
id
course_id
effective_date
1
1
2020-10-01
2
1
2021-01-01
3
2
2020-09-01
course_codes:
id
course_detail_id
code.
1
1
eng-01
2
2
eng-505
3
3
math-01
when I pass code = eng-01 it should return empty array instead of course with id 1.
Can somebody please help me?
To resolve this issue, I used a subquery that returns ids of course_details of all the courses according to effective_date:
query = "select child.id from courses as parent
inner join course_details as child on child.course_id = parent.id
where child.effective_date =
(select max(child1.effective_date) as effective_date
from course_details as child1
where child1.course_id = parent.id
and (child1.effective_date <= CURRENT_DATE
or child1.effective_date = (select min(child2.effective_date) as effective_date
from course_details as child2
where child2.course_id = parent.id)
))"
effective_dated_ids = Course.find_by_sql(query).pluck(:id)
After getting all the ids, I passed these ids in search.
records = Course.left_joins(course_details: :course_codes).where(course_details: { id: effective_record_ids, course_codes: { course_code: params[:course_code] } })
And it worked as expected.
I have a Rails query which is shown below:
query_results =
User.
joins("INNER JOIN posts ON posts.user_id = users.user_id").
select("posts.topic, posts.thread_id")
query_results contains values of 2 columns: topic and thread_id.
I would like to split query_results into 2 arrays - 1 containing values from all records (from query_results) for column topic alone and the 2nd containing values from all records for column thread_id alone.
How can I achieve this?
Try This out can help you!
here we are going to use pluck.
Yes. According to Rails guides, pluck directly converts a database result into an array, without constructing ActiveRecord objects. This means better performance for a large or often-running query.
topic_arr = []
thread_id = []
query_results = User.joins("INNER JOIN posts ON posts.user_id = users.user_id").pluck("posts.topic, posts.thread_id")
query_results.each do |i|
topic_arr.push(i.first)
thread_id.push(i.last)
end
puts query_results #=>[["topic1", 1], ["topic2", 2], ["topic3", 3]]
puts topic_arr #=>["topic1","topic2","topic3"]
puts thread_id #=>[1,2,3]
I think you can try below code for your requirement :-
query_results =
User.
joins("INNER JOIN posts ON posts.user_id = users.user_id").
pluck("posts.topic, posts.thread_id").to_h
topic_arr = query_results.keys
thread_id_arr = query_results.values
Example
Above query will give you result like:-
query_results = {"topic 1"=>1, "topic 2" => 2}
topic_arr = query_results.keys
topic_arr = ["topic 1", "topic 2"]
thread_id_arr = query_results.values
thread_id_arr = [1, 2]
I have a table products which has a product_type_code column on it. What I'd like to do is retrieve different numbers of objects based on this column (eg.: 3 products with product_type_code = 'fridge', 6 products with product_type_code = 'car', 9 products with product_type_code = 'house', etc.).
I know I can do like this:
fridges = Product.where(product_type_code: 'fridge').limit(3)
houses = Product.where(product_type_code: 'house').limit(9)
[...]
And even create a scope like this:
# app/models/product.rb
scope :by_product_type_code, -> (material) { where(product_type_code: product_type_code) }
However, this is not efficient since I go to the database 3 times, if I'm not wrong. What I'd like to do is something like:
scope :by_product_type_code, -> (hash) { some_method(hash) }
where hash is: { fridge: 3, car: 6, house: 9 }
and get an ActiveRecord_Relation containing 3 fridges, 6 cars and 9 houses.
How can I do that efficiently?
You can create a query using UNION ALL, which selects records having a specifc product_type_code and limit to use it with find_by_sql:
{ fridge: 3, car: 6, house: 9 }.map do |product_type_code, limit|
"(SELECT *
FROM products
WHERE product_type_code = '#{product_type_code}'
LIMIT #{limit})"
end.join(' UNION ALL ')
And you're gonna have a query like:
(SELECT * FROM products WHERE product_type_code = 'fridge'LIMIT 3)
UNION ALL
(SELECT * FROM products WHERE product_type_code = 'car'LIMIT 6)
UNION ALL
(SELECT * FROM products WHERE product_type_code = 'house'LIMIT 9)
#SebastianPalma's answer is the best solution; however if you were looking for a more "railsy" fashion of generating this query you can use arel as follows:
scope :by_product_type_code, ->(h) {
products_table = self.arel_table
query = h.map do |product_type,limit|
products_table.project(:id)
.where(products_table[:product_type_code].eq(product_type))
.take(limit)
end.reduce do |scope1, scope2|
Arel::Nodes::UnionAll.new(scope1,scope2)
end
self.where(id: query)
end
This will result in the sub query being part of the where clause.
Or
scope :by_product_type_code, ->(h) {
products_table = self.arel_table
query = h.map do |product_type,limit|
products_table.project(Arel.star)
.where(products_table[:product_type_code].eq(product_type))
.take(limit)
end.reduce do |scope1, scope2|
Arel::Nodes::UnionAll.new(scope1,scope2)
end
sub_query = Arel::Nodes::As.new(query,products_table)
self.from(sub_query)
end
This will result in the subquery being the source of the data.
I have a model Category that has_many Pendencies. I would like to create a scope that order the categories by the amount of Pendencies that has active = true without excluding active = false.
What I have so far is:
scope :order_by_pendencies, -> { left_joins(:pendencies).group(:id).order('COUNT(pendencies.id) DESC')}
This will order it by number of pendencies, but I want to order by pendencies that has active = true.
Another try was:
scope :order_by_pendencies, -> { left_joins(:pendencies).group(:id).where('pendencies.active = ?', true).order('COUNT(pendencies.id) DESC')}
This will order by number of pendencies that has pendencies.active = true, but will exclude the pendencies.active = false.
Thank you for your help.
I guess you want to sort by the amount of active pendencies without ignoring categories that have no active pendencies.
That would be something like:
scope :order_by_pendencies, -> {
active_count_q = Pendency.
group(:category_id).
where(active: true).
select(:category_id, "COUNT(*) AS count")
joins("LEFT JOIN (#{active_count_q.to_sql}) AS ac ON ac.category_id = id").
order("ac.count DESC")
}
The equivalent SQL query:
SELECT *, ac.count
FROM categories
LEFT JOIN (
SELECT category_id, COUNT(*) AS count
FROM pendencies
GROUP BY category_id
WHERE active = true
) AS ac ON ac.category_id = id
ORDER BY ac.count DESC
Note that if there are no active pendencies for a category, the count will be null and will be added to the end of the list.
A similar subquery could be added to sort additionally by the total amount of pendencies...
C# answer as requested:
method() {
....OrderBy((category) => category.Count(pendencies.Where((pendency) => pendency.Active))
}
Or in straight SQL:
SELECT category.id, ..., ActivePendnecies
FROM (SELECT category.id, ..., count(pendency) ActivePendnecies
FROM category
LEFT JOIN pendency ON category.id = pendency.id AND pendnecy.Active = 1
GROUP BY category.id, ...) P
ORDER BY ActivePendnecies;
We have to output ActivePendnecies in SQL even if the code will throw it out because otherwise the optimizer is within its rights to throw out the ORDER BY.
For now I developed the following (it's working, but I believe that it's not the best way):
scope :order_by_pendencies, -> { scoped = Category.left_joins(:pendencies)
.group(:id)
.order('COUNT(pendencies.id) DESC')
.where('pendencies.active = ?', true)
all = Category.all
(scoped + all).uniq}
I'm using a find_by_sql method to search users in my userstable.
is there a possibility to use rails code in the select statement?
User.find_by_sql ["SELECT DISTINCT
users.*
FROM
users
JOIN
clients_courses cc
ON
cc.client_id = users.client_id
LEFT JOIN
memberships m
ON
m.user_id = users.id AND m.course_id = cc.course_id
WHERE
cc.course_id = ?
AND
m.user_id IS NULL
AND
users.active = ?
AND
users.firstname LIKE ? or users.lastname LIKE ?
AND NOT IN ( RAILS CODE )", self.id, true, "#{search}%", "#{search}%"]
end
I Marked the position with RAILS CODE
I want to do someting linke this:
Membership.where("course_id = ?", self.id).users
is there a way to do this?
You can do this -
member_user_ids = []
Membership.where("course_id = ?", self.id).map{|membership| membership.users.map{|user| member_user_ids << user.id}}
# you might want to put a uniq! on member_user_ids
User.find_by_sql ["SELECT DISTINCT
users.*
FROM
users
JOIN
clients_courses cc
ON
cc.client_id = users.client_id
LEFT JOIN
memberships m
ON
m.user_id = users.id AND m.course_id = cc.course_id
WHERE
cc.course_id = ?
AND
m.user_id IS NULL
AND
users.active = ?
AND
users.firstname LIKE ? or users.lastname LIKE ?
AND users.id NOT IN ( #{member_user_ids.join(',')} )", self.id, true, "#{search}%", "#{search}%"]
You can also have a look at link which explains how to put array of strings in where clause.