Was ist der Unterschied zwischen LATERAL und einer Unterabfrage in PostgreSQL?

145

Seit Postgres die Möglichkeit hat, LATERALVerknüpfungen durchzuführen, habe ich mich darüber informiert, da ich derzeit komplexe Daten-Dumps für mein Team mit vielen ineffizienten Unterabfragen durchführe, bei denen die Gesamtabfrage vier Minuten oder länger dauert.

Ich verstehe, dass LATERALJoins mir vielleicht helfen können, aber selbst nachdem ich Artikel wie diesen von Heap Analytics gelesen habe , folge ich immer noch nicht ganz.

Was ist der Anwendungsfall für einen LATERALJoin? Was ist der Unterschied zwischen einem LATERALJoin und einer Unterabfrage?

jdotjdot
quelle
2
blog.heapanalytics.com/... und explainextended.com/2009/07/16/inner-join-vs-cross-apply (SQL Server applyist die gleiche wie die lateralvon dem SQL - Standard)
a_horse_with_no_name

Antworten:

163

Eher wie eine korrelierte Unterabfrage

Ein LATERALJoin (Postgres 9.3 oder höher) ähnelt eher einer korrelierten Unterabfrage , nicht einer einfachen Unterabfrage. Wie Andomar betonte, muss eine Funktion oder Unterabfrage rechts von einem LATERALJoin - genau wie eine korrelierte Unterabfrage - für jede Zeile links davon einmal ausgewertet werden, während eine einfache Unterabfrage (Tabellenausdruck) nur einmal ausgewertet wird . (Der Abfrageplaner bietet jedoch Möglichkeiten, die Leistung für beide zu optimieren.)
Diese verwandte Antwort enthält Codebeispiele für beide nebeneinander, mit denen das gleiche Problem gelöst wird:

Wenn Sie mehr als eine Spalte zurückgeben , ist ein LATERALJoin normalerweise einfacher, sauberer und schneller.
Denken Sie auch daran, dass das Äquivalent einer korrelierten Unterabfrage lautet LEFT JOIN LATERAL ... ON true:

Lesen Sie das Handbuch weiter LATERAL

Es ist maßgeblicher als alles, was wir hier beantworten werden:

Dinge, die eine Unterabfrage nicht kann

Es gibt Dinge, die ein LATERALJoin tun kann, aber eine (korrelierte) Unterabfrage kann nicht (leicht). Eine korrelierte Unterabfrage kann nur einen einzelnen Wert zurückgeben, nicht mehrere Spalten und nicht mehrere Zeilen - mit Ausnahme von bloßen Funktionsaufrufen (die Ergebniszeilen multiplizieren, wenn sie mehrere Zeilen zurückgeben). Aber auch bestimmte Set-Return-Funktionen sind nur in der FROMKlausel zulässig . Wie unnest()bei mehreren Parametern in Postgres 9.4 oder höher. Das Handbuch:

Dies ist nur in der FROMKlausel erlaubt ;

Das funktioniert also, kann aber nicht einfach durch eine Unterabfrage ersetzt werden:

CREATE TABLE tbl (a1 int[], a2 int[]);
SELECT * FROM tbl, unnest(a1, a2) u(elem1, elem2);  -- implicit LATERAL

Das Komma ( ,) in der FROMKlausel ist eine Kurzschreibweise für CROSS JOIN.
LATERALwird für Tabellenfunktionen automatisch angenommen.
Mehr zum Sonderfall von UNNEST( array_expression [, ... ] ):

Set-Return-Funktionen in der SELECTListe

Sie können auch Set-Return-Funktionen wie unnest()in der SELECTListe direkt verwenden. Dies zeigte ein überraschendes Verhalten mit mehr als einer solchen Funktion in derselben SELECTListe bis zu Postgres 9.6. Aber es wurde endlich mit Postgres 10 bereinigt und ist jetzt eine gültige Alternative (auch wenn es sich nicht um Standard-SQL handelt). Sehen:

Aufbauend auf dem obigen Beispiel:

SELECT *, unnest(a1) AS elem1, unnest(a2) AS elem2
FROM   tbl;

Vergleich:

dbfiddle für pg 9.6 hier
dbfiddle für pg 10 hier

Klären Sie Fehlinformationen

Das Handbuch:

Für die INNERund OUTERJoin - Typen, ein Join - Bedingung angegeben werden muss, und zwar genau einen NATURAL, ON join_bedingung oder USING( join_column [, ...]). Siehe unten für die Bedeutung.
Denn CROSS JOINkeine dieser Klauseln kann erscheinen.

Diese beiden Abfragen sind also gültig (auch wenn sie nicht besonders nützlich sind):

SELECT *
FROM   tbl t
LEFT   JOIN LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t ON TRUE;

SELECT *
FROM   tbl t, LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t;

Während dieser nicht ist:

SELECT *
FROM   tbl t
LEFT   JOIN LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t;

Aus diesem Grund ist das Codebeispiel von @ Andomar korrekt (für das CROSS JOINkeine Join-Bedingung erforderlich ist) und das von @ Attila ist ungültig.

Erwin Brandstetter
quelle
Es gibt einige Dinge, die eine Unterabfrage tun kann, die ein LATERAL JOIN nicht kann. Wie Fensterfunktionen. Wie hier
Evan Carroll
@EvanCarroll: Ich konnte keine korrelierten Unterabfragen im Link finden. Aber ich habe eine andere Antwort hinzugefügt, um eine Fensterfunktion in einer LATERALUnterabfrage zu demonstrieren : gis.stackexchange.com/a/230070/7244
Erwin Brandstetter
1
Sauberer und schneller? Wie Größen in einigen Fällen schneller. Ich hatte eine Abfrage, die nach dem Wechsel zu LATERAL von Tagen auf Sekunden ging.
Rovyko
51

Der Unterschied zwischen einem Nicht- lateralund einem lateralJoin besteht darin, ob Sie in die Zeile der linken Tabelle schauen können. Beispielsweise:

select  *
from    table1 t1
cross join lateral
        (
        select  *
        from    t2
        where   t1.col1 = t2.col1 -- Only allowed because of lateral
        ) sub

Dieses "nach außen gerichtete" bedeutet, dass die Unterabfrage mehr als einmal ausgewertet werden muss. Immerhin t1.col1können viele Werte annehmen.

Im Gegensatz dazu kann die Unterabfrage nach einem Nicht- lateralJoin einmal ausgewertet werden:

select  *
from    table1 t1
cross join
        (
        select  *
        from    t2
        where   t2.col1 = 42 -- No reference to outer query
        ) sub

Wie ohne erforderlich lateral, hängt die innere Abfrage in keiner Weise von der äußeren Abfrage ab. Eine lateralAbfrage ist correlatedaufgrund ihrer Beziehung zu Zeilen außerhalb der Abfrage selbst ein Beispiel für eine Abfrage.

Andomar
quelle
5
Dies ist die sauberste Erklärung für die seitliche Verbindung.
1valdis
leicht verständliche Erklärung, danke.
Arilwan
Wie select * from table1 left join t2 using (col1)vergleicht man? Es ist mir unklar, wann ein Join mit / on-Bedingung nicht ausreicht und es sinnvoller wäre, lateral zu verwenden.
No_name
9

Erstens ist Lateral und Cross Apply dasselbe . Daher können Sie auch über Cross Apply lesen. Da es seit Ewigkeiten in SQL Server implementiert ist, finden Sie weitere Informationen dazu als Lateral.

Zweitens gibt es nach meinem Verständnis nichts, was Sie nicht mit Unterabfragen anstelle von Lateral tun können. Aber:

Betrachten Sie die folgende Abfrage.

Select A.*
, (Select B.Column1 from B where B.Fk1 = A.PK and Limit 1)
, (Select B.Column2 from B where B.Fk1 = A.PK and Limit 1)
FROM A 

In diesem Zustand können Sie lateral verwenden.

Select A.*
, x.Column1
, x.Column2
FROM A LEFT JOIN LATERAL (
  Select B.Column1,B.Column2,B.Fk1 from B  Limit 1
) x ON X.Fk1 = A.PK

In dieser Abfrage können Sie aufgrund der Limit-Klausel keinen normalen Join verwenden. Lateral oder Cross Apply kann verwendet werden, wenn keine einfache Join-Bedingung vorliegt .

Es gibt mehr Verwendungen für seitliche oder Kreuzanwendung, aber dies ist die häufigste, die ich gefunden habe.

Atilla Ozgur
quelle
1
Genau das frage ich mich , warum PostgreSQL verwendet lateralstatt apply. Vielleicht hat Microsoft die Syntax patentiert?
Andomar
9
@Andomar AFAIK lateralist im SQL-Standard, applyist es aber nicht.
Mu ist zu kurz
Das LEFT JOINerfordert eine Join-Bedingung. Machen Sie ON TRUEes, es sei denn, Sie möchten irgendwie einschränken.
Erwin Brandstetter
Erwin hat recht, Sie erhalten eine Fehlermeldung, es sei denn, Sie verwenden eine cross joinoder eine onBedingung
Andomar
1
@Andomar: Angespornt durch diese Fehlinformationen habe ich eine weitere Antwort hinzugefügt, um dies zu klären.
Erwin Brandstetter
4

Niemand hat darauf hingewiesen, dass Sie LATERALAbfragen verwenden können, um eine benutzerdefinierte Funktion auf jede ausgewählte Zeile anzuwenden.

Zum Beispiel:

CREATE OR REPLACE FUNCTION delete_company(companyId varchar(255))
RETURNS void AS $$
    BEGIN
        DELETE FROM company_settings WHERE "company_id"=company_id;
        DELETE FROM users WHERE "company_id"=companyId;
        DELETE FROM companies WHERE id=companyId;
    END; 
$$ LANGUAGE plpgsql;

SELECT * FROM (
    SELECT id, name, created_at FROM companies WHERE created_at < '2018-01-01'
) c, LATERAL delete_company(c.id);

Nur so kann ich so etwas in PostgreSQL machen.

Theodore R. Smith
quelle