【问题标题】:Returning unique items in postgres返回 postgres 中的唯一项目
【发布时间】:2021-04-05 08:45:13
【问题描述】:

我想返回唯一的问题,但即使使用 DISTINCT,如果我有一个问题有多个答案,所有相同的问题的答案都会返回 我只希望这个问题返回一次这里是我的 sql

SELECT 
DISTINCT questions.id AS question_id,
questions.title AS question_title,
questions.created_at AS questionCreatedAt,
questions.updated_at AS question_updated_at,
answers.id AS answer_id,
answers.content AS answer_content,
answers.created_at AS answer_created_at,
answers.updated_at AS answer_updated_at,
(SELECT SUM(votes.value) AS votes FROM votes WHERE answers.id =votes.answer_id)
FROM questions
LEFT JOIN answers ON questions.id = answers.question_id
LEFT JOIN votes ON answers.id = votes.answer_id; 

【问题讨论】:

    标签: sql postgresql aggregate greatest-n-per-group


    【解决方案1】:

    要从每个组返回特定​​行,您需要添加确定性ORDER BY 子句。基本上:

    SELECT DISTINCT ON (q.id)
           q.id AS question_id
         , q.title AS question_title
         , q.created_at AS question_created_at
         , q.updated_at AS question_updated_at
         , a.id AS answer_id
         , a.content AS answer_content
         , a.created_at AS answer_created_at
         , a.updated_at AS answer_updated_at
         , SUM(v.value) AS votes
    FROM   questions q
    LEFT   JOIN answers a ON q.id = a.question_id
    LEFT   JOIN votes   v ON a.id = v.answer_id
    GROUP  BY q.id, a.id   -- for the sum
    ORDER  BY q.id, a.created_at DESC NULLS LAST, a.id;
    

    第一个ORDER BY 项必须与DISTINCT ON 子句一致。
    您想要“最新”的答案,所以接下来是 a.created_at DESC。
    NULLS LAST 因为该列可能为空(您没有透露)。
    最后的a.id 仅在多个答案与a.created_at 并列的情况下用作决胜局。

    详细解释:

    加入votes 后,就不需要投票总和的相关子查询了:

    (SELECT SUM(votes.value) AS votes FROM votes WHERE answers.id =votes.answer_id)
    

    目前,您可能得到不正确的(相乘)总和。假设answers 和votes 之间存在一对多关系(否则,投票计数可以作为另一列添加到answers),它是非此即彼:要么加入表,然后GROUP BY,或者不加入表并添加相关的子查询。

    我用一个简单的sum() 来修复它,假设q.id 和a.id 是它们表的各自主键(您没有透露表定义)。这是可能的,因为DISTINCT ON 在GROUP BY 之后应用。见:

    或查看以下可能更好的解决方案。

    当您返回所有或大多数问题时,如果您在获得最新信息后加入,则查询通常会更快每个问题的答案。喜欢:

    SELECT q.id AS question_id
         , q.title AS question_title
         , q.created_at AS question_created_at
         , q.updated_at AS question_updated_at
         , a.id AS answer_id
         , a.content AS answer_content
         , a.created_at AS answer_created_at
         , a.updated_at AS answer_updated_at
         , u.user_name                -- whatever you need from users table
         , (SELECT SUM(value) FROM votes v WHERE v.answer_id = a.answer_id) AS votes
    FROM   questions q
    LEFT   JOIN (
       SELECT DISTINCT ON (a.question_id)
              a.question_id AS id
            , a.id AS answer_id
            , a.content AS answer_content
            , a.created_at AS answer_created_at
            , a.updated_at AS answer_updated_at
            , a.user_id
       FROM   answers    a
       ORDER  BY a.question_id, a.created_at DESC NULLS LAST, a.id
       ) a USING (id)
    LEFT JOIN users u ON u.id = a.user_id
    

    在这里,我保留了 votes 的相关子查询,因为这样做通常会更便宜减少选择的答案而不是计算所有答案。

    users 类似(在您的答案中添加):加入 after 减少到所选答案。并将来自users 的内容放入SELECT 列表中以实际返回。

    如果您的表 answers 很大,则 answer(question_id, created_at DESC NULLS LAST) 上的多列索引将是性能的理想选择。
    如果每个问题有很多答案,则不同的查询技术可能会更快。见:

    对于检索所有问题的一小部分,LATERAL 或相关子查询通常更快。

    详细信息取决于未公开的表定义和基数。

    【讨论】:

      【解决方案2】:

      您应该使用“DISTINCT ON”而不是“DISTINCT”。

      SELECT 
      DISTINCT ON (questions.id) questions.id,
      questions.title AS question_title,
      questions.created_at AS questionCreatedAt,
      questions.updated_at AS question_updated_at,
      answers.id AS answer_id,
      answers.content AS answer_content,
      answers.created_at AS answer_created_at,
      answers.updated_at AS answer_updated_at,
      (SELECT SUM(votes.value) AS votes FROM votes WHERE answers.id =votes.answer_id)
      FROM questions
      LEFT JOIN answers ON questions.id = answers.question_id
      LEFT JOIN votes ON answers.id = votes.answer_id; 
      

      Similar question

      Guide

      【讨论】:

      • 谢谢它的工作,但你知道我怎样才能返回一个最新答案 created_at date
      • @sebastian,您可以使用“ORDER BY”和“LIMIT”Documentation。 ORDER BY answers.created_at DESC LIMIT 1;
      • LIMIT 不是必需的。 DISTINCT ON 已经为每个问题返回了一个答案。
      【解决方案3】:

      得到最新的答案

      `SELECT
                  DISTINCT ON (questions.id) questions.id,
                  questions.title AS question_title,
                  questions.created_at AS questionCreatedAt,
                  questions.updated_at AS question_updated_at,
                  answers.id AS answer_id,
                  answers.content AS answer_content,
                  answers.created_at AS answer_created_at,
                  answers.updated_at AS answer_updated_at,
                  (SELECT SUM(votes.value) AS votes FROM votes WHERE answers.id =votes.answer_id)
                  FROM questions
                  LEFT JOIN answers ON questions.id = answers.question_id
                  LEFT JOIN users ON  users.id  = answers.user_id
                  LEFT JOIN votes ON answers.id = votes.answer_id
                  ORDER  BY questions.id, answers.created_at DESC NULLS LAST, answers.id;
      

      【讨论】:

      • 上面的代码似乎没有向用户返回给定的答案,有人知道为什么吗?抱歉,所有问题的人,但我刚刚学习了 postgres
      • 添加的users 表中没有列在SELECT 列表中。就像我在回答中暗示的那样,您的问题应该披露(相关部分)表定义(CREATE TABLE 语句)。加上基数和你的 Postgres 版本。
      【解决方案4】:

      select distinct 是 row operator 这意味着它检查每个选定的列并考虑该行是否与其他行不同。如果由于任何选定的列而导致当前行与任何其他行不同,则将返回该行。在您的情况下,您已将问题与答案结合在一起,我假设一旦您在调查中有多个人,那么您可能会得到不同的答案,因此这些差异会导致更多行。如果您只需要不同的问题,那么也不要加入会成倍增加结果的表格。

      但是,在您的查询中,您似乎还想要一个SUM(),所以与其追求使用select distinct,或许您可以考虑使用group by,如下所示:

      SELECT
        questions.id AS question_id,
        questions.title AS question_title,
        questions.created_at AS questionCreatedAt,
        questions.updated_at AS question_updated_at,
        COUNT(DISTINCT answers.id) and num_answers,
        MIN(answers.created_at) AS answer_created_at,
        MAX(answers.updated_at) AS answer_updated_at,
        SUM(votes.value) AS votes
      FROM questions
      LEFT JOIN answers
        ON questions.id = answers.question_id
      LEFT JOIN votes
        ON answers.id = votes.answer_id
      GROUP BY
        questions.id,
        questions.title,
        questions.created_at,
        questions.updated_at
      

      在 Postgres 中,有一个额外的限定符来表示 distinct 为 select distinct on (...),但为了控制结果,它应该与 order by 子句一起使用

      DISTINCT ON 表达式使用与 ORDER BY 相同的规则进行解释(见上文)。请注意,除非使用 ORDER BY 来确保所需的行首先出现,否则每组的“第一行”是不可预测的。 (ref)

      因此这将减少返回的行数,并且可以使用order by 子句控制输出最近创建的答案

      SELECT DISTINCT ON (questions.id)
       questions.id,
      questions.title AS question_title,
      questions.created_at AS questionCreatedAt,
      questions.updated_at AS question_updated_at,
      answers.id AS answer_id,
      answers.content AS answer_content,
      answers.created_at AS answer_created_at,
      answers.updated_at AS answer_updated_at,
      (SELECT SUM(votes.value) AS votes FROM votes WHERE answers.id =votes.answer_id)
      FROM questions
      LEFT JOIN answers ON questions.id = answers.question_id
      LEFT JOIN votes ON answers.id = votes.answer_id; 
      ORDER BY questions.id, answers.created_at DESC
      

      查找“最新行”的更通用(不是 Postgres 特定)解决方案是使用row_number() over()。此外,不是通过“相关子查询”对投票进行求和,而是先通过答案汇总投票,然后加入其余表,如下所示:

      SELECT
        questions.id AS question_id,
        questions.title AS question_title,
        questions.created_at AS questionCreatedAt,
        questions.updated_at AS question_updated_at
        answers.id AS answer_id,
        answers.content AS answer_content,
        answers.created_at AS answer_created_at,
        answers.updated_at AS answer_updated_at,
        votes.votes
      FROM questions
      LEFT JOIN ( SELECT
                  a.*,
                  ROW_NUMBER() OVER (PARTITION BY a.id ORDER BY created_at DESC) AS rn
                  FROM answers AS a
        ) AS answers
        ON questions.id = answers.question_id
        AND answers.rn = 1
      LEFT JOIN (
                  SELECT v.answer_id, SUM(v.value) as votes
                  FROM votes as v
                  GROUP BY v.answer_id
        ) AS votes
        ON answers.id = votes.answer_id
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-04
        • 2020-05-04
        • 2012-09-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多