【问题标题】:Exclude tag from post in many to many queries在多对多查询中从帖子中排除标签
【发布时间】:2017-07-04 20:37:39
【问题描述】:

使用 Postgres 我有 3 个表:

CREATE TABLE post (id SERIAL, body TEXT);
CREATE TABLE tag (id SERIAL, name TEXT);
CREATE TABLE post_tag (post_id INT, tag_id INT);

INSERT INTO post(body) values('post 1');
INSERT INTO post(body) values('post 2');
INSERT INTO tag(name) values('a');
INSERT INTO tag(name) values('b');
INSERT INTO post_tag values(1, 1);
INSERT INTO post_tag values(1, 2);
INSERT INTO post_tag values(2, 1);

因此post 1 有标签a, bpost 2a 作为标签。

问题:如何选择所有没有标签b的帖子,这意味着它应该只选择post 2

这里的查询不好,因为它会选择两个帖子,因为 post 1 有 2 个标签 ab

SELECT post.*
FROM post
JOIN post_tag ON post_tag.post_id = post.id
JOIN tag ON tag.id = post_tag.tag_id
WHERE tag.name != 'b';

下面的查询有效,但错误,因为如果有标签aaaaaaab,那么它也会匹配它:

SELECT post.id, post.body, string_agg(tag1.name, ', ')
FROM post
JOIN post_tag ON post_tag.post_id = post.id
JOIN tag ON tag.id = post_tag.tag_id
GROUP BY post.id, post.body
HAVING string_agg(tag.name, ', ') not like '%b, %';

我正在寻找一种“正确”且有效的方法。

编辑:查询还应该匹配根本没有任何标签的帖子。

【问题讨论】:

    标签: postgresql


    【解决方案1】:

    一个使用子查询的解决方案是这样的:

    SELECT *
    FROM post
    WHERE post.id IN (
      SELECT post_id
      FROM post_tag
      WHERE post_id != ALL (
        SELECT post_id
        FROM post_tag
        WHERE tag_id = (
          SELECT id
          FROM tag
          WHERE name = 'b'
          )
        )
      );
    

    不过我不确定这个查询的效率如何,它绝对不是最易读的。

    【讨论】:

      【解决方案2】:

      您可以使用查询选择带有聚合标签的帖子:

      select p.id, p.body, array_agg(t.name) tags
      from post p
      left join post_tag pt on pt.post_id = p.id
      left join tag t on pt.tag_id = t.id
      group by 1, 2;
      
       id |  body  | tags  
      ----+--------+-------
        1 | post 1 | {a,b}
        2 | post 2 | {a}
      (2 rows)    
      

      通过适当的修改,您可以使用查询来过滤您的数据,例如

      select p.id, p.body
      from post p
      left join post_tag pt on pt.post_id = p.id
      left join tag t on pt.tag_id = t.id
      group by 1, 2
      having 'b' <> all(array_agg(t.name));
      -- or to get also posts without tags:
      -- having 'b' <> all(array_agg(t.name)) or array_agg(t.name) = '{null}';
      
       id |  body  
      ----+--------
        2 | post 2
      (1 row) 
      

      【讨论】:

      • 谢谢!没有标签的帖子怎么样?逻辑上也应该在结果中。抱歉,我以前没有考虑过。
      猜你喜欢
      • 2014-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-24
      • 1970-01-01
      • 1970-01-01
      • 2012-02-26
      相关资源
      最近更新 更多