【问题标题】:PostgreSQL remove duplicates by GROUP BYPostgreSQL 按 GROUP BY 删除重复项
【发布时间】:2020-04-25 11:31:25
【问题描述】:

我想打印一个人的最后一条消息,但每个人只能打印他的最新消息。我使用 PostgreSQL 10。

+-----------+----------+--------------+
| name      |   body   |  created_at  |
+-----------+----------+--------------+
| Maria     | Test3    |  2017-07-07  |
| Paul      | Test5    |  2017-06-01  |
+-----------+----------+--------------+

我已经用下面的 SQL 查询尝试过,这给了我确切的回报,但不幸的是,人们在其中翻了一番。

SELECT * FROM messages 
WHERE receive = 't'
GROUP BY name
ORDER BY MAX(created_at) DESC
+-----------+----------+--------------+
| name      |   body   |  created_at  |
+-----------+----------+--------------+
| Maria     | Test1    |  2016-06-01  |
| Maria     | Test2    |  2016-11-01  |
| Maria     | Test3    |  2017-07-07  |
| Paul      | Test4    |  2017-01-01  |
| Paul      | Test5    |  2017-06-01  |
+-----------+----------+--------------+

我尝试使用 DISTINCT 删除重复项,但不幸的是我收到以下错误消息:

SELECT DISTINCT ON (name) * FROM messages 
WHERE receive = 't'
GROUP BY name
ORDER BY MAX(created_at) DESC
ERROR: SELECT DISTINCT ON expressions must match initial ORDER BY expressions LINE 1: SELECT DISTINCT ON (name) * FROM messages ^ : SELECT DISTINCT ON (name) * FROM messages WHERE receive = 't' GROUP BY name ORDER BY MAX(created_at) DESC

您有什么想法可以解决这个问题吗?

【问题讨论】:

    标签: sql postgresql sql-order-by distinct greatest-n-per-group


    【解决方案1】:

    您将使用DISTINCT ON,如下所示:

    SELECT DISTINCT ON (name) * 
    FROM messages 
    WHERE receive = 't'
    ORDER BY name, created_at DESC
    

    即:

    • 不需要GROUP BY 子句

    • DISTINCT ON(...) 中列出的列必须首先出现在 ORDER BY 子句中

    • ... 后跟应该用于拆分组的列(此处为created_at

    请注意,distinct on 查询的结果始终按子句中的列排序(因为这种排序用于标识应保留哪些行)。

    如果您想更好地控制排序顺序,那么您可以改用窗口函数:

    SELECT *
    FROM (
        SELECT m.*, ROW_NUMBER() OVER(PARTITION BY name ORDER BY created_at DESC) rn
        FROM messages m
        WHERE receive = 't'
    ) t
    WHERE rn = 1
    ORDER BY created_at DESC
    

    【讨论】:

      【解决方案2】:

      使用DISTINCT ON,但使用正确的ORDER BY

      SELECT DISTINCT ON (name) m.*
      FROM messages m
      WHERE receive = 't'
      ORDER BY name, created_at DESC;
      

      一般情况下,您不会将DISTINCT ONGROUP BY 一起使用。它与ORDER BY 一起使用。它的工作方式是根据ORDER BY 子句为每个name 选择第一行。

      您不应该将您正在做的事情视为聚合。您想根据created_at 进行过滤。在许多数据库中,您会使用相关子查询来表达这一点:

      select m.*
      from messages m
      where m.created_at = (select max(m2.created_at)
                            from messages m2
                            where m2.name = m.name and m2.receive = 't'
                           ) and
            m.receive = 't';   -- this condition is probably not needed
      

      【讨论】:

      • 但是它是按名称排序的。当我尝试使用子查询执行此操作时,缺少最新条目。为什么会这样?
      【解决方案3】:
      SELECT * 
      FROM messages 
      WHERE receive = 't' and not exists (
          select 1
          from messages m
          where m.receive = message.receive and messages.name = m.name and m.created_at > messages.created_at
      )
      ORDER BY created_at DESC
      

      上面的查询找到满足以下条件的消息:

      • 接收是't'
      • 不存在另一条消息
        • receive 值相同
        • 同名
        • 并且更新

      假设同一个名字不会同时发送两条消息,这就足够了。另一点是,如果值中存在一些白色字符,则名称可能看起来相似但不同,因此,如果您在结果中看到两个具有相同名称但在上面的查询中具有不同 created_at 的记录,那么很有可能是白人在捉弄你。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-09
        • 1970-01-01
        • 1970-01-01
        • 2017-02-17
        • 1970-01-01
        相关资源
        最近更新 更多