web-dev-qa-db-ja.com

重複する行のない左結合

recordrecord_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で繰り返されている場合、このクエリはレコードに対して複数の行を返します。ここで左側の結合に制限を適用して重複行を制限するにはどうすればよいですか?

8
RaR

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許容列には特別な注意が必要です(推奨されません)。

11
ypercubeᵀᴹ