SQL WHERE IN 每个 IN id 的结果有限且排序

SQL WHERE IN with limited and sorted results per IN id

我不知道怎么形容这个,所以请原谅这个标题...

我正在尝试编写一个函数,以便在提供对话 ID 数组时检索属于对话的最新消息。

我的"message"table看起来如下:

id | conversationId | body           | createdDate
-------------------------------------------------------
1  | 1              | Hello          | 2020-06-09 01:01
2  | 1              | How are you?   | 2020-06-09 01:02
3  | 2              | Hi             | 2020-06-09 01:02
4  | 1              | I'm good, you? | 2020-06-09 01:03
5  | 2              | Hey there!     | 2020-06-09 01:04

我查询到:

SELECT * FROM "message" WHERE "conversationId" IN (1, 2) ORDER BY "createdDate" DESC

正如预期的那样,这 returns 所有与提供的对话 ID 匹配的消息,按最近 createdDate.

排序

我对如何 LIMIT 只包含每个 conversationId 的第一个结果(最近的 createdDate)感到有点困惑。

我正在寻找的输出是:

id | conversationId | body           | createdDate
-------------------------------------------------------
4  | 1              | I'm good, you? | 2020-06-09 01:03
5  | 2              | Hey there!     | 2020-06-09 01:04

这是一个典型的每组最大 n 问题。在 Postgres 中,解决这个问题的一种简单有效的方法是 distinct on:

select distinct on (conversationId) m.*
from messages m
-- where conversationId in (...)    -- if needed
order by conversationId, createdDate desc

你也可以使用window函数row_number

select
  id,
  conversationId,
  body,
  createdDate
from
(
  SELECT 
    *,
    row_number() over (partition by conversationId order by createdDate desc) as rn
  FROM "message" 
) val
where rnk = 1

如之前在其他答案中所述,DISTINCT ON 是最简单的方法。但它有限制。特别是排序顺序。

没有限制,这可以通过这两种方法实现,也可以不使用 GROUP BY


A) 只是一个简单的 self ANTI JOIN;

SELECT
    m.*
FROM
    "message" AS m
    LEFT JOIN "message" _m ON _m."conversationId" = m."conversationId" AND _m."createdDate" > m."createdDate"
WHERE
    _m."id" IS NULL

它被称为 ANTI JOIN,因为通常我们想要 "joined" 结果,而在这种情况下我们不需要它们。因此,使用对话中最高时间戳的所需条件进行自左外连接。

为了更好地理解这个概念,您可以 运行 不带 _m."id" IS NULL 条件的查询。这将为您提供所有匹配的记录:

Right Side : "m"                       || Left Side : "_m"
---------------------------------------------------------------------------------
id | conversationId | createdDate      ||  id | conversationId | createdDate      
---------------------------------------------------------------------------------
1  | 1              | 2020-06-09 01:01 ||  2  | 1              | 2020-06-09 01:02
1  | 1              | 2020-06-09 01:01 ||  4  | 1              | 2020-06-09 01:03
2  | 1              | 2020-06-09 01:02 ||  4  | 1              | 2020-06-09 01:03
3  | 2              | 2020-06-09 01:02 ||  5  | 2              | 2020-06-09 01:04
4  | 1              | 2020-06-09 01:03 || NULL
5  | 2              | 2020-06-09 01:04 || NULL

包含 4 和 5 的记录没有任何匹配项,因为这些对话中没有更高的日期。当左侧没有匹配记录时,我们可以确定右侧具有该对话的最高时间戳。


B) 一个简单的子查询

SELECT
    m.*
FROM
    "message" m
WHERE
    m."id" = (
        SELECT
            "id"
        FROM
            "message"
        WHERE
            "conversationId" = m."conversationId"
        ORDER BY
            "createdDate" DESC
        LIMIT 1
    )

条件中的 SUBQUERY 总是 returns 会话中具有最高时间戳的记录的 ID。我们使用它的 return 值来限制主查询中的记录。