Intersection of two relations

前端 未结 5 2080
情歌与酒
情歌与酒 2020-12-16 17:06

Say I have two relations that hold records in the same model, such as:

@companies1 = Company.where(...)
@companies2 = Company.where(...)

Ho

相关标签:
5条回答
  • 2020-12-16 17:14

    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.

    0 讨论(0)
  • 2020-12-16 17:20

    You could use ActiveRecord::SpawnMethods#merge

    Example:

    Company.where(condition: 'value').merge(Company.where(other_condition: 'value'))
    
    0 讨论(0)
  • 2020-12-16 17:22

    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.

    0 讨论(0)
  • 2020-12-16 17:24

    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

    0 讨论(0)
  • 2020-12-16 17:37

    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.

    0 讨论(0)
提交回复
热议问题