【问题标题】:Rails query through 2 different associated modelsRails 通过 2 个不同的关联模型进行查询
【发布时间】:2015-01-06 08:49:24
【问题描述】:

我在尝试让查询按我想要的方式工作时遇到了一些麻烦,我没有得到我希望的所有结果。

我有 3 个模型 PostCommentTag。帖子和 cmets 都可以包含标签,并且都与标签具有has_and_belongs_to_many 关系。我希望能够获取具有指定标签或具有该标签的 cmets 的所有帖子,我一直在以下范围内的帖子中这样做:

scope :tag, -> (tag_id) { joins(:tags, :comment_tags).where("tags_posts.tag_id = :tag_id OR comments_tags.tag_id = :tag_id", tag_id: tag_id) }

但这并没有返回所有帖子,只是其中的一个子集,似乎只有关于 cmets 的帖子,这是它生成的查询:

SELECT COUNT(*) FROM "posts" 
INNER JOIN "tags_posts" ON "tags_posts"."post_id" = "posts"."id" 
INNER JOIN "tags" ON "tags"."id" = "tags_posts"."tag_id" 
INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" 
INNER JOIN "comments_tags" ON "comments_tags"."comment_id" = "comments"."id" 
INNER JOIN "tags" "comment_tags_posts" ON "comment_tags_posts"."id" = "comments_tags"."tag_id" 
WHERE (tags_posts.tag_id = 1 OR comments_tags.tag_id = 1)

这些是模型:

class Post < ActiveRecord::Base
  has_and_belongs_to_many :tags
  has_many :comment_tags, through: :comments, source: :tags
end

class Tag < ActiveRecord::Base
  has_and_belongs_to_many :posts
  has_and_belongs_to_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
  has_and_belongs_to_many :tags
end

【问题讨论】:

    标签: ruby-on-rails postgresql activerecord


    【解决方案1】:

    我不确定您是否已经解决了这个问题,但如果您还没有解决,这里有一个可能的解决方案:

    在纯 SQL 中,主要用于说明目的:

    SELECT 
        DISTINCT posts.*
    FROM
        posts
            INNER JOIN
        tags_posts ON tags_posts.post_id = posts.id
            LEFT JOIN
        comments ON comments.post_id = posts.id
            LEFT JOIN
        comments_tags ON comments_tags.comment_id = comments.id
            INNER JOIN
        tags ON (tags.id = tags_posts.tag_id OR tags.id = comments_tags.tag_id)
    WHERE tags.id = 1
    

    原始版本中的主要问题是您使用commentscomments_tags 制作INNER JOIN。结果,您可能会删除每个没有任何 cmets 的 Post。所以解决方案是LEFT JOIN 与 cmets 相关的所有内容。然后,因为我们离开了,我们可以在标签帖子或评论帖子上INNER JOINtags

    转换为 Active Record 不是很漂亮,但很有必要:

    Post.joins("INNER JOIN posts_tags ON posts_tags.post_id = posts.id")
        .joins("LEFT JOIN comments ON comments.post_id = posts.id")
        .joins("LEFT JOIN comments_tags ON comments_tags.comment_id = comments.id")
        .joins("INNER JOIN tags ON (posts_tags.tag_id = tags.id OR comments_tags.tag_id = tags.id)")
        .where(tags: {id: 1})
        .uniq
    

    注意DISTINCTuniq 的必要性,因为LEFT JOIN 会导致重复。

    编辑

    如果对数据集或结构有一些误解,这是我在测试中用于创建上述查询的数据示例。

    帖子

    +----+--------------------------+
    | id | text                     |
    +----+--------------------------+
    |  1 | Post about programming 1 |
    |  2 | Post about programming 2 |
    |  3 | Post about programming 3 |
    |  4 | Post about cooking 1     |
    |  5 | Post about cooking 2     |
    +----+--------------------------+
    

    标签

    +----+-------------+
    | id | name        |
    +----+-------------+
    |  1 | programming |
    |  2 | cooking     |
    |  3 | woodworking |
    +----+-------------+
    

    tags_posts

    +--------+---------+
    | tag_id | post_id |
    +--------+---------+
    |      1 |       1 |
    |      1 |       2 |
    |      1 |       3 |
    |      2 |       4 |
    |      2 |       5 |
    +--------+---------+
    

    cmets

    +----+----------------------------------------------+---------+
    | id | comment_text                                 | post_id |
    +----+----------------------------------------------+---------+
    |  1 | comment - programming on programming post 1a |       1 |
    |  2 | comment - programming on programming post 1b |       1 |
    |  3 | comment - programming on programming post 2a |       2 |
    |  4 | comment - cooking on programming post 3a     |       3 |
    |  5 | comment - programming on cooking post 4a     |       4 |
    |  6 | comment - cooking on cooking post 4b         |       4 |
    |  7 | comment - cooking on cooking post 5a         |       5 |
    +----+----------------------------------------------+---------+
    

    cmets_tags

    +--------+------------+
    | tag_id | comment_id |
    +--------+------------+
    |      1 |          1 |
    |      1 |          2 |
    |      1 |          3 |
    |      1 |          5 |
    |      2 |          4 |
    |      2 |          6 |
    |      2 |          7 |
    +--------+------------+
    

    如果我想搜索“programming”,上面的查询会产生这个结果集:

    +----+--------------------------+
    | id | text                     |
    +----+--------------------------+
    |  1 | Post about programming 1 |
    |  2 | Post about programming 2 |
    |  4 | Post about cooking 1     |
    |  3 | Post about programming 3 |
    +----+--------------------------+
    

    因为我们有 3 篇专门标记为“编程”的帖子,而在另一篇标记不同的帖子中,有一条评论被标记为“编程”。

    【讨论】:

    • 我没听懂,我一会儿试试。非常感谢。
    • @8vius 没问题,希望能成功。如果有任何问题,请告诉我。
    • 很抱歉,这也不起作用。没有得到我应该使用它的所有帖子。
    • @8vius 这很奇怪。你能确定哪些帖子没有被拉起吗?他们有什么不同之处可以解释这一点吗?
    • 它们都是由 cmets 标记的,一个只有带有标记的注释,另一个有几个 cmets。这是我看到的唯一区别。
    【解决方案2】:

    我不确定什么是 yum,是帖子吗?

    从您的 SQL 查询看来,它似乎只会计算同时具有特定标记和带有此特定标记的注释的 yum。您想要的是计算具有特定标签的 yum 或具有此特定标签的 cmets。

    我会做 2 个查询,一个来计算带有特定标签的 yum + 一个来计算带有特定注释标签的 yum,然后将它们相加以获得总数,或者使用 UNION 条件进行一个查询。

    scope :yums_tagged, -> (tag_id) { joins(:tags).where("tags_yums.tag_id = :tag_id", tag_id: tag_id) }
    scope :comments_taged, -> (tag_id) { joins(:comment_tags).where("comments_tags.tag_id = :tag_id", tag_id: tag_id) }
    

    【讨论】:

    • 是的,就我而言,yum 就是我所说的我的帖子。
    • 我不想要来自 2 个查询的 2 个不同的结果,我想要 1 个同时被评论标记和在内容中标记的查询。
    • 我将所有的 yum 实例都更改为 post 以使其更易于理解,抱歉造成混淆。
    猜你喜欢
    • 2012-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多