【问题标题】:How to filter based on has_many association?如何根据 has_many 关联进行过滤?
【发布时间】:2020-09-27 16:59:21
【问题描述】:

我有 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 ids 的数组(例如 [1,3,5])。我试过了 Project.joins(:tags).where(tags: {id: [1 ,3, 5]}) 但它会找到具有来自[1,3,5] 的任一标签的项目。我正在寻找具有所有输入标签的项目。我该怎么做?

【问题讨论】:

    标签: ruby-on-rails postgresql activerecord


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

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

    【讨论】:

    • 我错了,查询没有获取正确的项目。
    • 我在我的代码中使用了数组包含方法。为什么你认为这行不通?它在一些测试中为我提供了准确的结果。
    • 数组方法有效,我的建议无效。对于具有 4 个标签(id:1、3、5 和 7)的项目,过滤标签 1、3、5 和 count = 3,将找到该项目,所有条件都将匹配。它在这里做错了,这是一个误报。
    【解决方案2】:

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

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

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

    几个条件:

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

    这样做的好处是查询很灵活——你可以改变操作来快速改变意思。比如说,您现在可以写“返回一个带有这些标签的项目”,而不是“返回一个项目将所有这些标签”。

    考虑这个数据集:

    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。

    可以使用的 SQL 小提琴: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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-27
      • 1970-01-01
      • 2020-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多