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

发布于 2024-09-10 06:46:44 字数 472 浏览 7 评论 0原文

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

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

I have two models, Project and Category, which have a many-to-many relationship between them. The Project model is very simple:

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

The :in_categories scope takes an array of Category IDs (as strings), so using this scope I can get back every project that belongs to at least one of the categories passed in.

But what I'm actually trying to do is filter (a better name would be :has_categories). I want to just get the projects that belong to all of the categories passed in. So if I pass in ["1", "3", "4"] I only want to get the projects that belong to all of the categories.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

夜血缘 2024-09-17 06:46:45

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 来确定。

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

Group-by:

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。

There are two common solutions in SQL to do what you're describing.

Self-join:

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);

Note I'm using syntax to compare tuples. This is equivalent to:

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

In general, the self-join solution has very good performance if you have a covering index. Probably Categories.(project_id,id) would be the right index, but analyze the SQL with EXPLAIN to be sure.

The disadvantage of this method is that you need four joins if you're searching for projects that match four different categories. Five joins for five categories, etc.

Group-by:

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;

If you're using MySQL (I assume you are), most GROUP BY queries invoke a temp table and this kills performance.

I'll leave it as an exercise for you to adapt one of these SQL solutions to equivalent Rails ActiveRecord API.

ゞ花落谁相伴 2024-09-17 06:46:45

看起来在 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}")
}

It seems like in ActiveRecord you would do it like so:

scope :has_categories, lambda { |categories|
  joins(:categories).
  where("categories.id in (?)", categories.collect(&:to_i)).
  group("projects.id HAVING COUNT(projects.id) = #{categories.count}")
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文