Linker Join ohne doppelte Zeilen

8

Ich habe zwei Tabellen namens recordund record_history. Für jeden Datensatz kann es mehr als einen Verlauf geben. Sie können durch idund verbunden werden record_id. Ich möchte alle recordEinträge mit den neuesten record_historyDaten erhalten. Ich habe die Abfrage wie folgt erstellt:

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

Hier bekomme ich die neueste von ts. Dies funktioniert, solange keine doppelten tsEinträge vorhanden sind. Wenn der letzte Zeitstempel in wiederholt wird record_history, gibt diese Abfrage mehr als eine Zeile für einen Datensatz zurück. Wie können wir das Limit hier auf der linken Verknüpfung anwenden, um doppelte Zeilen einzuschränken?

RaR
quelle
Sie wählen nichts aus rech2 aus?
Evan Carroll
@EvanCarroll rech2wird hier verwendet, wählen Sie die erste record_history und mein Bedürfnis nach rech2 soll seinNULL
RaR

Antworten:

11

Sofern Sie sich nicht in einer sehr alten Version von Postgres befinden, benötigen Sie keinen Double Join. Sie können das gleiche Ergebnis erzielen, indem Sie einen LATERALJoin verwenden .

Die doppelten Ergebnisse können in Ihrer Methode vermieden werden, indem neben dem eine zweite Bedingung hinzugefügt wird rec.id = rech2.record_id. Mit der LATERALJoin-Methode LIMITwird dies ohnehin vermieden. Es kann nur 1 Zeile von der seitlichen Unterabfrage zurückgegeben werden. Wir können eine zweite Bedingung hinzufügen, damit die Auswahl deterministisch ist (aus zwei oder mehr Zeilen mit demselben Zeitstempel):

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 ;

In Bezug auf die Vorgehensweise mit der ursprünglichen Methode (2 Verknüpfungen und IS NULLPrüfung) können Sie die ONBedingung ändern - vorausgesetzt, die idVerlaufstabelle enthält eine Spalte, die eindeutig ist (id)oder zumindest (ts, id)eindeutig:

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)

Übrigens könnten Sie diesen zweiten LEFTJoin ersetzen und IS NULLmit einer NOT EXISTSUnterabfrage mit denselben Ergebnissen und möglicherweise ähnlicher Effizienz prüfen (oder sogar mit einer NOT INUnterabfrage, obwohl dies besondere Sorgfalt für nullfähige Spalten erfordert, was nicht empfohlen wird).

ypercubeᵀᴹ
quelle
Großartig! Das hat funktioniert. Wusste nichts davon LATERAL. Es ist ein gutes Lernen für mich. Vielen Dank!
RaR
@ypercube The duplicate results can be avoided in your method by adding a second condition besides the rec.id = rech2.record_idWelche Bedingung können wir hier hinzufügen, um Doppelarbeit zu vermeiden?
RaR
1
Warum left join lateral .. on (true)eher ein als CROSS JOIN LATERAL ()?
Evan Carroll
3
@Evan, da möglicherweise Zeilen recordohne zugehörige Zeile vorhanden sind record_history.
Ypercubeᵀᴹ
1
@EvanCarroll, sorry, ich habe nicht klar erwähnt. Ich wollte alle recordEinträge, auch wenn es für einige keine Verlaufseinträge gibt.
RaR