如何根据 has_many 关联进行过滤?

How to filter based on has_many association?

我有 3 个模型

项目

class Project < ApplicationRecord
  has_many :taggings
  has_many :tags, through: :taggings
end

标记

class Tagging < ApplicationRecord
  belongs_to :tag
  belongs_to :project
end

标签

class Tag < ApplicationRecord
  has_many :taggings
  has_many :projects, through: :taggings
end

简而言之项目有很多标签彻底的标签。

我想找出具有所有给定标签的项目。我的输入是一个 tag id 的数组(例如 [1,3,5])。我试过 Project.joins(:tags).where(tags: {id: [1 ,3, 5]}) 但它会找到具有 ID 来自 [1,3,5] 的任一标签的项目。我正在寻找具有所有输入标签的项目。我该怎么做?

tags = [1, 3, 5]
projects = Project.joins(:tags)
                  .where(tags: {id: tags})
                  .group(:id)
                  .having('COUNT(tags) = ?', tags.size)

这将 return 个具有所有三个标签的项目。

您正在查找“包含”查询:

SELECT p.*
FROM projects p
         INNER JOIN taggings t ON p.id = t.project_id
GROUP BY p.id
HAVING array_agg(t.tag_id ORDER BY t.tag_id) @> ARRAY [1, 3, 5];

这将return所有具有所有给定标签但不限于它们的项目。即,如果一个项目有标签 1, 3, 5, 7,它将被 return 编辑到。但不是

的项目

几个条件:

  1. ARRAY [1, 3, 5]必须排序
  2. p.id(实际上是 projects.id)必须:a) 为主键或 b) 附加唯一性约束。

这样做的好处是查询灵活——可以通过改变运算来快速改变意思。比方说,您现在可以写“return 一个项目 这些标签”,而不是“return 一个项目将所有这些标签”。

考虑这个数据集:

projects:

id  name
1   guttenberg
2   x
3   aristotle


tags:

id  name
1   books
2   teams
3   management
4   library
5   movie

taggings:

id  project_id  tag_id
1   1   1
2   1   3
3   1   5
4   2   1
5   2   3
6   3   4
7   3   5

如果您要查询 1, 3,您应该得到项目 1 和 2。

A SQL fiddle 一起玩:http://sqlfiddle.com/#!17/345dd0/9/1

等效的 ActiveRecord:

tag_ids = [1, 5, 3].sort # condition 1
projects = 
  Project.joins(:taggings) # don't need tags
    .group(:id)
    .having("array_agg(taggings.tag_id ORDER BY taggings.tag_id) @> ARRAY[?]", tag_ids)