问题
Say I have two relations that hold records in the same model, such as:
@companies1 = Company.where(...)
@companies2 = Company.where(...)
How can I find the intersection of these two relations, i.e. only those companies that exist within both?
回答1:
By default connecting those where
together creates AND which is what you want.
So many be:
class Company < ActiveRecord::Base
def self.where_1
where(...)
end
def self.where_2
where(...)
end
end
@companies = Company.where_1.where_2
====== UPDATED ======
There are two cases:
# case 1: the fields selecting are different
Company.where(:id => [1, 2, 3, 4]) & Company.where(:other_field => true)
# a-rel supports &, |, +, -, but please notice case 2
# case 2
Company.where(:id => [1, 2, 3]) & Company.where(:id => [1, 2, 4, 5])
# the result would be the same as
Company.where(:id => [1, 2, 4, 5])
# because it is &-ing the :id key, instead of the content inside :id key
So if you are in case 2, you will need to do like what @apneadiving commented.
Company.where(...).all & Company.where(...).all
Of course, doing this sends out two queries and most likely queried more results than you needed.
回答2:
Use sql keyword INTERSECT.
params1 = [1,2,4]
params2 = [1,3,4]
query = "
SELECT companies.* FROM companies
WHERE id in (?,?,?)
INTERSECT
SELECT companies.* FROM companies
WHERE id in (?,?,?)
"
Company.find_by_sql([query, *params1, *params2])
it will be faster than previous solution.
回答3:
I solve similar problem this way
Company.connection.unprepared_statement do
Company.find_by_sql "#{@companies1.to_sql} INTERSECT #{@companies2.to_sql}"
end
We need unprepared_statement
block here because latest Rails versions use prepared statements to speed up arel queries, but we need pure SQL in place.
回答4:
You could use ActiveRecord::SpawnMethods#merge
Example:
Company.where(condition: 'value').merge(Company.where(other_condition: 'value'))
回答5:
For anyone who is stuck with Rails4 and cant use Rails5 .or syntax:
I had a dynamically number of big queries, which had similar conditions ( and therefore also similar results). My rake server would have problems when all of them at once would get instantiated, converted to arrays and then merged.
I needed a ActiveRecord::Relation (not fired yet) to use with find_each.
Looked something like this:
Class Conditions
def initialize
self.where_arr = []
self.joins_arr = []
end
def my_condition1
where_arr << 'customers.status = "active"'
joins_arr << :customer
end
def my_condition2
where_arr << 'companies.id = 1'
end
end
conditions = []
joins = []
# probably call them in a .each block with .send
conditions1 = Conditions.new
conditions1.my_condition1
conditions1.my_condition2
conditions << "(#{conditions1.where_arr.join(' AND ')})"
joins << conditions1.joins_arr
conditions2 = Conditions.new
conditions2.my_condition1
joins << conditions2.joins_arr
Company.joins(joins).where(conditions.join(' OR '))
=> SELECT companies.* FROM companies
INNER JOIN customers ON companies.customer_id = customers.id
WHERE (customers.status = 'active' AND companies.id = 1) OR
(customers.status = 'active')
Its kind of hacky but it works, until you can hopefully migrate to Rails 5
来源:https://stackoverflow.com/questions/6435389/intersection-of-two-relations