record
とrecord_history
という2つのテーブルがあります。レコードごとに、複数の履歴が存在する可能性があります。それらはid
およびrecord_id
で結合できます。最近のrecord_history
データを含むすべてのrecord
のエントリを取得したい。私は次のようなクエリを作成しました、
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
ここでは、ts
から最新のものを入手しています。これは、重複するts
エントリがない限り機能します。最近のタイムスタンプがrecord_history
で繰り返されている場合、このクエリはレコードに対して複数の行を返します。ここで左側の結合に制限を適用して重複行を制限するにはどうすればよいですか?
Postgresの非常に古いバージョンでない限り、二重結合は必要ありません。 LATERAL
joinを使用しても同じ結果が得られます。
rec.id = rech2.record_id
以外に2番目の条件を追加することにより、メソッドで重複した結果を回避できます。 LATERAL
結合メソッドを使用すると、LIMIT
の使用により、とにかくそれを回避できます。横方向のサブクエリから返される行は1つだけです。 2番目の条件を追加して、(同じタイムスタンプの2つ以上の行から)選択が確定するようにすることができます。
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 ;
元の方法(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)
ちなみに、2番目のLEFT
結合とIS NULL
チェックをNOT EXISTS
サブクエリに置き換えると、結果は同じになり、効率もよくなります(またはNOT IN
サブクエリを使ってもnull許容列には特別な注意が必要です(推奨されません)。