【问题标题】:Query conditionally return only one row per distinct id查询有条件地为每个不同的 id 返回一行
【发布时间】:2015-06-16 20:51:36
【问题描述】:

我正在制作 Reddit 克隆,但在查询我的帖子列表时遇到了问题,给定一个登录用户,该列表显示登录用户是否对每个帖子都投了赞成票。我做了一个小例子让事情变得更简单。

我试图为每个不同的 post_id 返回一行,但将 upvoted 列的优先级设置为 t > f > null。

对于这个示例数据:

> select * from post;
 id
----
  1
  2
  3

> select * from users;
 id
----
  1
  2

> select * from upvoted;
 user_id | post_id
---------+---------
       1 |       1
       2 |       1

如果给我user_id = 1,我希望我的查询返回:

 postid | user_upvoted
--------+--------------
      1 | t
      2 | f
      3 | f

由于 user1 对 post1 投了赞成票,upvoted 是 t。由于 user1 没有为 post2 投票,upvoted 是 f。 post3 也一样。

架构

CREATE TABLE IF NOT EXISTS post (
    id              bigserial,
    PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS users (
    id              serial,
    PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS upvoted (
    user_id         integer
        REFERENCES users(id)
        ON DELETE CASCADE ON UPDATE CASCADE,
    post_id         bigint
        REFERENCES post(id)
        ON DELETE CASCADE ON UPDATE CASCADE,
    PRIMARY KEY (user_id, post_id)
);

到目前为止我尝试了什么

SELECT post.id as postid,
    CASE WHEN user_id=1 THEN true ELSE false END as user_upvoted
    FROM post LEFT OUTER JOIN upvoted
    ON post_id = post.id;

这给了我:

 postid | user_upvoted
--------+--------------
      1 | t
      1 | f
      2 | f
      3 | f

由于连接,查询产生了两个“重复”行。我想优先使用t > f > null 的行。所以我想保留1 | t 行。

Full script with schema+data.

【问题讨论】:

    标签: sql postgresql left-join greatest-n-per-group


    【解决方案1】:

    你应该可以用distinct on做到这一点:

    SELECT distinct on (p.id) p.id as postid,
           (CASE WHEN user_id = 1 THEN true ELSE false END) as upvoted
    FROM post p LEFT OUTER JOIN
         upvoted u
         ON u.post_id = p.id
    ORDER BY p.id, upvoted desc;
    

    【讨论】:

    • 谢谢!这就是我要找的。我的 reddit 克隆最终以应有的方式工作。只有一个问题 - 第二个order by 到底应该在那里吗?我在那里遇到语法错误,但是当我执行ORDER BY p.id, upvoted desc; 时它工作正常。
    • @KeithYong 。 . .不,第二个order by 不应该在那里。
    • DISTINCT ON 对于这种简单的情况来说是昂贵的噪音。没有人应该使用它。这将加入每个帖子的每个投票,而我们一开始只对给定用户感兴趣。
    • 因为这个答案会定期被否决,我应该指出,这个解决方案并不假设每个帖子只有一个赞成票。 Erwin 的回答也是一个不错的答案,但它假设只有一个赞成票。
    【解决方案2】:

    exists() 运算符产生一个布尔值:

    SELECT p.id
       , EXISTS (SELECT * FROM upvoted x
                  WHERE x.post_id = p.id
                    AND x.user_id = 1) AS it_was_upvoted_by_user1
    FROM post p
            ;
    

    【讨论】:

    • 这里比DISTINCT ON好,但你忘了AND x.user_id = 1。
    • 好吧,我明白了...编辑...关于第二个想法:I am trying to return only one row per distinct post_id
    • 是的,每个不同的post_id,我的评论仍然适用。您当前的查询返回帖子是否有 any 点赞,但 OP 对它是否有来自给定用户的点赞感兴趣:whether or not logged in user upvoted the post。
    • 我的大脑终于又开始工作了。你是对的。当然。
    【解决方案3】:

    由于(user_id, post_id) 的组合在upvoted (PRIMARY KEY) 中被定义为唯一,所以这可以简单得多:

    SELECT p.id AS post_id, u.post_id IS NOT NULL AS user_upvoted
    FROM   post p
    LEFT   JOIN upvoted u ON u.post_id = p.id
                         AND u.user_id = 1;
    

    只需将user_id = 1 添加到连接条件即可。完美利用索引,应该是最简单最快的。

    您还提到了 NULL,但结果中只有两个不同的状态:true / false。

    替代方法

    再想一想,您可能正在使一项非常基本的任务复杂化。如果您只对当前用户点赞的帖子感兴趣,请改用此简单查询:

    SELECT post_id FROM upvoted WHERE user_id = 1;
    

    所有其他帖子都没有被给定用户投票。看来我们不必明确列出这些。

    SQL Fiddle.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-22
      • 1970-01-01
      相关资源
      最近更新 更多