3

我有两个模型,项目和类别,它们之间具有多对多的关系。项目模型非常简单:

class Project < ActiveRecord::Base
  has_and_belongs_to_many :categories

  scope :in_categories, lambda { |categories|
    joins(:categories).
    where("categories.id in (?)", categories.collect(&:to_i))
  }
end

:in_categories 范围采用类别 ID 数组(作为字符串),因此使用此范围我可以取回属于至少一个传入类别的每个项目。

但我真正想做的是过滤(更好的名字是:has_categories)。我只想获取属于传入的所有类别的项目。所以如果我传入 ["1", "3", "4"] 我只想获取属于所有类别的项目。

4

2 回答 2

1

SQL 中有两种常见的解决方案来执行您所描述的操作。

自加入:

SELECT ...
FROM Projects p
JOIN Categories c1 ON c1.project_id = p.id
JOIN Categories c3 ON c3.project_id = p.id
JOIN Categories c4 ON c4.project_id = p.id
WHERE (c1.id, c3.id, c4.id) = (1, 3, 4);

注意我使用语法来比较元组。这相当于:

WHERE c1.id = 1 AND c3.id = 3 AND c4.id = 4;

一般来说,如果你有一个覆盖索引,自连接解决方​​案具有非常好的性能。可能Categories.(project_id,id)是正确的索引,但是用 EXPLAIN 分析 SQL 来确定。

这种方法的缺点是,如果您要搜索匹配四个不同类别的项目,则需要四个连接。五个类别的五个连接等。

通过...分组:

SELECT ...
FROM Projects p
JOIN Categories cc ON c.project_id = p.id
WHERE c.id IN (1, 3, 4)
GROUP BY p.id
HAVING COUNT(*) = 3;

如果您使用的是 MySQL(我假设您是),大多数 GROUP BY 查询都会调用临时表,这会降低性能。

我将把它留作练习,让您将这些 SQL 解决方案之一改编为等效的 Rails ActiveRecord API。

于 2010-07-14T07:03:03.347 回答
1

似乎在 ActiveRecord 中你会这样做:

scope :has_categories, lambda { |categories|
  joins(:categories).
  where("categories.id in (?)", categories.collect(&:to_i)).
  group("projects.id HAVING COUNT(projects.id) = #{categories.count}")
}
于 2010-07-14T07:43:26.857 回答