没有重复行的左连接

RaR*_*RaR 10 postgresql greatest-n-per-group

我有两个表叫做recordrecord_history。对于每条记录,可能有多个历史记录。它们可以通过id和 连接record_id。我想record用最近的record_history数据获取所有的条目。我已经创建了这样的查询,

SELECT rec.id, rec.name, rech1.data AS last_history_data
FROM record rec
LEFT OUTER JOIN record_history rech1 ON (rec.id = rech1.record_id)
LEFT OUTER JOIN record_history rech2 ON (rec.id = rech2.record_id AND rech2.ts > rech1.ts)
WHERE rech2.id IS NULL
ORDER BY rec.id DESC
Run Code Online (Sandbox Code Playgroud)

在这里,我通过ts. 只要没有重复的ts条目,这就会起作用。如果最近的时间戳在 中重复record_history,则此查询将返回多于一行的记录。我们如何在左连接上应用限制来限制重复行?

ype*_*eᵀᴹ 14

除非您使用的是非常旧版本的 Postgres,否则您不需要双重连接。您可以通过使用LATERALjoin获得相同的结果。

通过添加除rec.id = rech2.record_id. 使用LATERALjoin 方法,LIMIT 无论如何都要避免它。横向子查询只能返回 1 行。我们可以添加第二个条件,以便选择是确定性的(来自具有相同时间戳的两行或更多行):

SELECT rec.id, rec.name, rech.data AS last_history_data
FROM record AS rec
     LEFT OUTER JOIN LATERAL
     ( SELECT rech.data
       FROM record_history AS rech
       WHERE rec.id = rech.record_id
       ORDER BY rech.ts DESC
                -- ,rech.id DESC               -- optional
       LIMIT 1 
     ) AS rech
     ON TRUE
ORDER BY rec.id DESC ;
Run Code Online (Sandbox Code Playgroud)

关于如何使用原始方法(2 个连接和IS NULL检查)执行此操作,您可以更改ON条件 - 假设id历史表中有一个列,以便(id)或至少(ts, id)是唯一的:

LEFT OUTER JOIN record_history rech2 
ON rec.id = rech2.record_id 
   AND (rech2.ts > rech1.ts OR rech2.ts = rech1.ts AND rech2.id > rech1.id)
Run Code Online (Sandbox Code Playgroud)

顺便说一下,您可以替换第二个LEFT连接并IS NULL使用NOT EXISTS具有相同结果和可能相似效率的子查询进行检查(或者甚至使用NOT IN子查询,尽管这需要额外注意可空列,不推荐)。

  • @Evan 因为在 `record` 中可能有行而在 `record_history` 中没有任何相关的行。 (3认同)
  • 为什么使用“left joinlateral .. on (true)”而不是“CROSS JOIN LATERAL ()”? (2认同)