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?
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?
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.
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.
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.
You could use ActiveRecord::SpawnMethods#merge
Example:
Company.where(condition: 'value').merge(Company.where(other_condition: 'value'))
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