如何根据多列获取不同的行,包括来自另一个 table 的最新行?

How to get distinct rows based on multiple columns are include the latest row from another table?

我有一个 users table 和一个 private_messages table,我想获取当前用户发送或接收消息的用户列表另一位用户连同最新消息内容。

用户table

id | username

private_messages table

author_id | recipient_id | content | creation_date

这是我最接近获得用户的一次

SELECT u.id, u.username FROM users u
WHERE u.id IN 
    (SELECT p.author_id as id
     FROM private_messages p
     WHERE p.recipient_id = '6d480813-c854-40fc-a3cf-cea0944854ab' 
    );

但如您所见,只有当用户不是消息的作者时才有效,而且我不知道如何包含最新消息的内容。

作为 SQL 的新手,我也准备更改 table 的模型。

用户 table 数据

╔════════════════════════════════════════╦════════════════╗
║                  "id"                  ║   "username"   ║
╠════════════════════════════════════════╬════════════════╣
║ "2f8afc87-13d3-4654-8333-465b094c9ccf" ║ "KayleeSchumm" ║
║ "e5e37e98-3468-47e1-9c7e-1311988d6d79" ║ "JordonWaters" ║
║ "6d480813-c854-40fc-a3cf-cea0944854ab" ║ "AsafAviv"     ║
╚════════════════════════════════════════╩════════════════╝

private_messages数据

╔════════════════════════════════════════╦════════════════════════════════════════╦═════════════════╦══════════════════════════════╗
║              "author_id"               ║             "recipient_id"             ║    "content"    ║       "creation_date"        ║
╠════════════════════════════════════════╬════════════════════════════════════════╬═════════════════╬══════════════════════════════╣
║ "2f8afc87-13d3-4654-8333-465b094c9ccf" ║ "e5e37e98-3468-47e1-9c7e-1311988d6d79" ║ "hello world 0" ║ "2020-04-14 14:01:40.121+03" ║
║ "2f8afc87-13d3-4654-8333-465b094c9ccf" ║ "6d480813-c854-40fc-a3cf-cea0944854ab" ║ "hello world 0" ║ "2020-04-14 14:01:40.121+03" ║
║ "e5e37e98-3468-47e1-9c7e-1311988d6d79" ║ "2f8afc87-13d3-4654-8333-465b094c9ccf" ║ "hello world 0" ║ "2020-04-14 14:01:40.121+03" ║
║ "e5e37e98-3468-47e1-9c7e-1311988d6d79" ║ "6d480813-c854-40fc-a3cf-cea0944854ab" ║ "hello world 0" ║ "2020-04-14 14:01:40.121+03" ║
║ "6d480813-c854-40fc-a3cf-cea0944854ab" ║ "2f8afc87-13d3-4654-8333-465b094c9ccf" ║ "hello world 0" ║ "2020-04-14 14:01:40.121+03" ║
║ "6d480813-c854-40fc-a3cf-cea0944854ab" ║ "e5e37e98-3468-47e1-9c7e-1311988d6d79" ║ "hello world 0" ║ "2020-04-14 14:01:40.121+03" ║
╚════════════════════════════════════════╩════════════════════════════════════════╩═════════════════╩══════════════════════════════╝

预期输出

╔════════════════════════════════════════╦════════════════╦═════════════════╗
║                  "id"                  ║   "username"   ║    "content"    ║
╠════════════════════════════════════════╬════════════════╬═════════════════╣
║ "2f8afc87-13d3-4654-8333-465b094c9ccf" ║ "KayleeSchumm" ║ "hello world 0" ║
║ "e5e37e98-3468-47e1-9c7e-1311988d6d79" ║ "JordonWaters" ║ "hello world 0" ║
╚════════════════════════════════════════╩════════════════╩═════════════════╝

我想你想要:

SELECT u.id, u.username, pm.*
FROM users u CROSS JOIN
     (values ('6d480813-c854-40fc-a3cf-cea0944854ab')
     ) as v(the_user) CROSS JOIN LATERAL
     (SELECT DISTINCT ON (v2.other_user) pm.*
      FROM private_messages pm CROSS JOIN LATERAL
           (VALUES (CASE WHEN pm.recipient_id = v.the_user
                         THEN pm.author_id ELSE pm.recipient_id
                   END)
           ) v2(other_user)
      WHERE v.the_user = v2.other_user
      ORDER BY v2.other_user, pm.creation_date desc
     ) pm;

VALUES() 子句只是为了方便处理常量用户值。

关键部分是DISTINCT ON。这returns第一行每个"other user"基于ORDER BY。在这种情况下,ORDER BY 将最近的记录放在第一位。

试试这个:

SELECT u.id, u.username, p.content 
FROM users u
INNER JOIN (
  SELECT DISTINCT
    CASE WHEN p.author_id = '6d480813-c854-40fc-a3cf-cea0944854ab' THEN p.recipient_id ELSE p.author_id END user_id,
    p.content
  FROM private_messages p
  WHERE '6d480813-c854-40fc-a3cf-cea0944854ab' IN (p.author_id, p.recipient_id)
  AND NOT EXISTS (
    SELECT 1 FROM private_messages
    WHERE (author_id, recipient_id) IN ((p.author_id, p.recipient_id), (p.recipient_id, p.author_id))
      AND creation_date > p.creation_date
  )                                                                         
) p ON u.id = p.user_id

连接到 users 的查询使用 NOT EXISTS 到 return 给每个用户的最后一条消息。
demo.
或者使用 CTEs 和 ROW_NUMBER() window 函数:

WITH 
  cur_user(id) AS (VALUES('6d480813-c854-40fc-a3cf-cea0944854ab')),
  cte_all AS (
    SELECT content, creation_date,
      CASE (SELECT id FROM cur_user)
        WHEN author_id THEN recipient_id
        ELSE author_id
      END user_id
    FROM private_messages 
    WHERE (SELECT id FROM cur_user) IN (author_id, recipient_id)
  ),
  cte_last AS (
    SELECT t.content, t.creation_date, t.user_id
    FROM (
      SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY creation_date) rn
      FROM cte_all
    ) t
    WHERE t.rn = 1 
  )  
SELECT u.id, u.username, c.content 
FROM users u INNER JOIN cte_last c
ON c.user_id = u.id

参见demo
结果:

| id                                   | username     | content       |
| ------------------------------------ | ------------ | ------------- |
| 2f8afc87-13d3-4654-8333-465b094c9ccf | KayleeSchumm | hello world 0 |
| e5e37e98-3468-47e1-9c7e-1311988d6d79 | JordonWaters | hello world 0 |