查询与activerecord的交集

时间:2012-08-07 02:41:53

标签: mysql ruby-on-rails ruby-on-rails-3 activerecord

我真的想在活动记录

的帮助下进行以下查询
(select *
from people p join cities c join services s
where p.city_id = c.id and p.id = s.person_id and s.type = 1)

intersect

(select *
from people p join cities c join services s
where p.city_id = c.id and p.id = s.person_id and s.type = 2)

问题是,首先,mysql不支持交叉。但是,这可以解决。问题是,我可以获得积极的记录来输出甚至接近它的任何东西。

在活动记录中,我能做的最好的事情是发出多个查询,然后使用reduce :&加入它们,但后来我得到一个数组,而不是一个关系。这对我来说是一个问题,因为我想调用像限制等的东西。另外,我认为交叉点要由数据库完成,而不是ruby代码。

3 个答案:

答案 0 :(得分:7)

您的问题可能无法解决,例如:

Person.joins(:services).where(services: {service_type: [1,2]}).group(
   people: :id).having('COUNT("people"."id")=2')

然而,以下是我用于在ActiveRecord中构建交叉类似查询的一般方法:

class Service < ActiveRecord::Base
  belongs_to :person

  def self.with_types(*types)
    where(service_type: types)
  end
end

class City < ActiveRecord::Base
  has_and_belongs_to_many :services
  has_many :people, inverse_of: :city
end

class Person < ActiveRecord::Base
  belongs_to :city, inverse_of: :people

  def self.with_cities(cities)
    where(city_id: cities)
  end

  def self.with_all_service_types(*types)
    types.map { |t|
      joins(:services).merge(Service.with_types t).select(:id)
    }.reduce(scoped) { |scope, subquery|
      scope.where(id: subquery)
    }
  end
end

Person.with_all_service_types(1, 2)
Person.with_all_service_types(1, 2).with_cities(City.where(name: 'Gold Coast'))

它将生成以下形式的SQL:

SELECT "people".*
  FROM "people"
 WHERE "people"."id" in (SELECT "people"."id" FROM ...)
   AND "people"."id" in (SELECT ...)
   AND ...

只要每个子查询返回其结果集中匹配人员的id,您就可以根据任何条件/连接等使用上述方法根据需要创建任意数量的子查询。

每个子查询结果集将被“和”在一起,从而将匹配集限制为所有子查询的交集。

更新

对于那些使用删除了scoped的AR4的人,我的另一个答案提供了语义上等效的scoped polyfil,all不是等效的替代品,尽管AR文档建议。在这里回答:With Rails 4, Model.scoped is deprecated but Model.all can't replace it

答案 1 :(得分:1)

我正在努力解决同样的问题,并且只找到了一个解决方案:针对同一个关联的多个联接。由于我正在构建连接的SQL字符串,所以这可能不是太多了,但我还没有找到另一种方法。这适用于任意数量的服务类型(城市似乎没有考虑因素,因此为了清楚起见省略了连接):

s = [1,2]
j = ''
s.each_index {|i|
  j += " INNER JOIN services s#{i} ON s.person_id = people.id AND s#{i}.type_id = #{s[i]}" 
}
People.all.joins(j)

答案 2 :(得分:0)

我对相交并不熟悉,但有没有理由你不能将其简化为单个查询并改为使用IN?:

People.where(:services => {:type => [1,2]}).joins(:cities => :services)

更新:你可以链接方法,他们将与AND加入:

People.where(:services => {:type => 1}).where(:services => {:type => 2}).joins(:cities => :services)