it-swarm.com.de

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

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

Ich verstehe, dass LATERAL Joins mir vielleicht helfen können, aber auch nachdem ich Artikel wie dieser von Heap Analytics gelesen habe, folge ich immer noch nicht ganz.

Was ist der Anwendungsfall für einen LATERAL Join? Was ist der Unterschied zwischen einem LATERAL - Join und einer Unterabfrage?

109
jdotjdot

Eher wie eine korrelierte Unterabfrage

Ein LATERAL-Join (Postgres 9.3 oder höher) ist eher eine korrelierte Unterabfrage , keine einfache Unterabfrage. Wie Andomar wies darauf hin muss eine Funktion oder Unterabfrage rechts von einem LATERAL-Join für jede Zeile links davon einmal ausgewertet werden - genau wie ein korreliert Unterabfrage - während eine einfache Unterabfrage (Tabellenausdruck) ausgewertet wird einmalig nur. (Der Abfrageplaner hat jedoch Möglichkeiten, die Leistung für beide zu optimieren.)
Diese verwandte Antwort enthält Codebeispiele für beide nebeneinander und löst das gleiche Problem:

Bei der Rückgabe von mehr als einer Spalte ist eine LATERAL -Verbindung normalerweise einfacher, sauberer und schneller.
Denken Sie auch daran, dass das Äquivalent einer korrelierten Unterabfrage LEFT JOIN LATERAL ... ON true ist:

Lesen Sie das Handbuch zu LATERAL

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

Was eine Unterabfrage nicht kann

Es gibt are Dinge, die ein LATERAL Join tun kann, eine (korrelierte) Unterabfrage jedoch nicht (leicht). Eine korrelierte Unterabfrage kann nur einen einzelnen Wert zurückgeben, nicht mehrere Spalten und nicht mehrere Zeilen - mit Ausnahme von Aufrufen von Bare-Funktionen (die Ergebniszeilen multiplizieren, wenn sie mehrere Zeilen zurückgeben). Aber auch bestimmte set-return-Funktionen sind nur in der FROM -Klausel zulässig. Wie unnest() mit mehreren Parametern in Postgres 9.4 oder höher. Das Handbuch:

Dies ist nur in der FROM -Klausel zulässig.

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 FROM -Klausel ist eine Kurzschreibweise für CROSS JOIN.
LATERAL wird automatisch für Tabellenfunktionen angenommen.
Mehr zum Sonderfall von UNNEST( array_expression [, ... ] ):

Set-Return-Funktionen in der Liste SELECT

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

Aufbauend auf obigem Beispiel:

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

Vergleich:

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

Klären Sie Fehlinformationen

Das Handbuch:

Für die Join-Typen INNER und OUTER muss eine Join-Bedingung angegeben werden, und zwar genau eine von NATURAL, ON join_condition oder USING ( join_column [ ...]). Siehe unten für die Bedeutung.
Bei CROSS JOIN Darf keine dieser Klauseln vorkommen.

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 @ Andomars Codebeispiel korrekt (für CROSS JOIN Ist keine Join-Bedingung erforderlich) und @ Attila's ist war ungültig.

127

Der Unterschied zwischen einem Nicht-Join lateral und einem Join lateral 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 "Hinausschauen" bedeutet, dass die Unterabfrage mehrmals ausgewertet werden muss. Nach alldem, t1.col1 kann viele Werte annehmen.

Im Gegensatz dazu kann die Unterabfrage nach einem Join ohne lateral einmal ausgewertet werden:

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

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

33
Andomar

Erstens Queranwendung und Queranwendung sind dasselbe . Lesen Sie deshalb auch über Cross Apply. Da es lange Zeit in SQL Server implementiert war, finden Sie unter Lateral weitere Informationen dazu.

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

Betrachten Sie 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 

Sie können laterale in diesem Zustand 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 es keine einfache Join-Bedingung gibt .

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

9
Atilla Ozgur

Niemand hat darauf hingewiesen, dass Sie LATERAL Abfragen 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.

1