sql - 如何编写命名范围以通过传入的所有数组进行过滤,而不仅仅是通过匹配一个元素(使用 IN)

标签 sql ruby-on-rails activerecord named-scope sql-match-all

我有两个模型,Project 和 Category,它们之间存在多对多关系。项目模型非常简单:

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"] 我只想获得属于所有类别的项目。

最佳答案

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。

关于sql - 如何编写命名范围以通过传入的所有数组进行过滤,而不仅仅是通过匹配一个元素(使用 IN),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3243913/

相关文章:

ruby-on-rails - Rails、RSpec 和 Factory Girl : how to connect to 2 databases

ruby-on-rails - Rails 缓存模型变量 - 如何 "force reload"?

sql - 从按多列分组的行组中选择具有最大值的行(PSQL)

MySQL Select Case——两张表的比较

mysql - Begin 语句的 SQL 错误 #1064

ruby-on-rails - ExtJs4 json data.store 和 Rails

sql - 如何创建历史事实表?

ruby-on-rails - 仅针对特定 Controller 调用before_filter

activerecord - Yii - 如何使用 where 和 limit 等条件

ruby-on-rails - 如何摆脱归因于 rails 关联的 n+1 查询?