Qual è la differenza tra LATERAL e una sottoquery in PostgreSQL?


146

Da quando Postgres è uscito con la possibilità di fare LATERALjoin, lo sto leggendo, dal momento che attualmente eseguo dump di dati complessi per il mio team con molte sottoquery inefficienti che rendono la query complessiva impiega quattro minuti o più.

Capisco che i LATERALjoin potrebbero essere in grado di aiutarmi, ma anche dopo aver letto articoli come questo da Heap Analytics, non riesco ancora a seguirli.

Qual è il caso d'uso di un LATERALjoin? Qual è la differenza tra un LATERALjoin e una sottoquery?


2
blog.heapanalytics.com/... e explainextended.com/2009/07/16/inner-join-vs-cross-apply (SQL Server di applyè lo stesso del lateraldallo standard SQL)
a_horse_with_no_name

Risposte:


163

Più come una sottoquery correlata

Un LATERALjoin (Postgres 9.3 o successivo) è più simile a una query secondaria correlata , non a una query secondaria semplice. Come sottolineato da Andomar , una funzione o una sottoquery a destra di un LATERALjoin devono essere valutate una volta per ogni riga a sinistra di essa - proprio come una sottoquery correlata - mentre una subquery semplice (espressione di tabella) viene valutata una sola volta . (Il pianificatore di query ha modi per ottimizzare le prestazioni per entrambi.)
Questa risposta correlata presenta esempi di codice per entrambi fianco a fianco, risolvendo lo stesso problema:

Per restituire più di una colonna , un LATERALjoin è in genere più semplice, più pulito e più veloce.
Inoltre, ricorda che l'equivalente di una sottoquery correlata è LEFT JOIN LATERAL ... ON true:

Leggi il manuale su LATERAL

È più autorevole di qualsiasi cosa stiamo per dare una risposta qui:

Cose che una sottoquery non può fare

Ci sono cose che un LATERALjoin può fare, ma una subquery (correlata) non può (facilmente). Una sottoquery correlata può restituire solo un singolo valore, non più colonne e non più righe, ad eccezione delle chiamate di funzioni nude (che moltiplicano le righe dei risultati se restituiscono più righe). Ma anche determinate funzioni di restituzione del set sono consentite solo nella FROMclausola. Come unnest()con più parametri in Postgres 9.4 o successivo. Il manuale:

Questo è consentito solo nella FROMclausola;

Quindi funziona, ma non può essere facilmente sostituito con una sottoquery:

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

La virgola ( ,) nella FROMclausola è una breve notazione per CROSS JOIN.
LATERALviene assunto automaticamente per le funzioni della tabella.
Maggiori informazioni sul caso speciale di UNNEST( array_expression [, ... ] ):

Funzioni di restituzione SELECTdell'elenco nell'elenco

È inoltre possibile utilizzare le funzioni di set-ritorno come unnest()nella SELECTlista direttamente. Questo mostrava comportamenti sorprendenti con più di una di queste funzioni nella stessa SELECTlista fino a Postgres 9.6. Ma è stato finalmente sterilizzato con Postgres 10 ed è una valida alternativa ora (anche se non standard SQL). Vedere:

Sulla base dell'esempio sopra:

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

Confronto:

dbfiddle per pg 9.6 qui
dbfiddle per pg 10 qui

Chiarire la disinformazione

Il manuale:

Per i tipi di join INNERe OUTER, è necessario specificare una condizione di join, ovvero esattamente uno di NATURAL, ON join_condition o USING( join_column [, ...]). Vedi sotto per il significato.
Perché CROSS JOINnessuna di queste clausole può apparire.

Quindi queste due query sono valide (anche se non particolarmente utili):

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;

Mentre questo non lo è:

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

Ecco perché l' esempio di codice di @ Andomar è corretto ( CROSS JOINnon richiede una condizione di join) e quello di @ Attila non è valido.


Ci sono alcune cose che una sottoquery può fare un LATERAL JOIN non può fare. Come le funzioni della finestra. Come qui
Evan Carroll

@EvanCarroll: non sono riuscito a trovare sottoquery correlate nel link. Ma ho aggiunto un'altra risposta per dimostrare una funzione di finestra in una LATERALsottoquery: gis.stackexchange.com/a/230070/7244
Erwin Brandstetter

1
Più pulito e più veloce? Come magnitudini più veloci in alcuni casi. Ho avuto una query che è passata da giorni a secondi dopo essere passato a LATERAL.
Rovyko,

52

La differenza tra un non laterale un lateraljoin risiede nel fatto che si può guardare alla riga della tabella di sinistra. Per esempio:

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

Questo "aspetto esteriore" significa che la sottoquery deve essere valutata più di una volta. Dopotutto, t1.col1può assumere molti valori.

Al contrario, la sottoquery dopo un non- lateraljoin può essere valutata una volta:

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

Come richiesto senza lateral, la query interna non dipende in alcun modo dalla query esterna. Una lateralquery è un esempio di correlatedquery, a causa della sua relazione con le righe esterne alla query stessa.


5
Questa è la spiegazione più chiara dell'unione laterale.
1valdis,

spiegazione facile da capire, grazie.
arilwan,

come si select * from table1 left join t2 using (col1)confronta? Non mi è chiaro quando un join che utilizza la condizione / on non è sufficiente e avrebbe più senso usare lateralmente.
No_name

9

Innanzitutto, Lateral e Cross Apply sono la stessa cosa . Pertanto, puoi anche leggere Cross Cross. Da quando è stato implementato in SQL Server per secoli, troverai maggiori informazioni a riguardo di Laterale.

Secondo, secondo la mia comprensione , non c'è nulla che non si possa fare usando la subquery invece di usare lateralmente. Ma:

Prendi in considerazione la seguente query.

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 

Puoi usare laterale in questa condizione.

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 questa query non è possibile utilizzare il join normale, a causa della clausola limit. L'applicazione laterale o incrociata può essere utilizzata quando non esiste una condizione di join semplice .

Ci sono più usi per l'applicazione laterale o incrociata, ma questo è il più comune che ho trovato.


1
Esatto, mi chiedo perché PostgreSQL usi lateralinvece di apply. Forse Microsoft ha brevettato la sintassi?
Andomar,

9
@Andomar AFAIK lateralè nello standard SQL ma applynon lo è.
mu è troppo corto il

Il LEFT JOINrichiede una condizione di join. Fallo a ON TRUEmeno che tu non voglia limitare in qualche modo.
Erwin Brandstetter,

Erwin ha ragione, otterrai un errore a meno che tu non usi una cross joino una oncondizione
Andomar,

1
@Andomar: stimolato da questa disinformazione ho aggiunto un'altra risposta per chiarire.
Erwin Brandstetter,

4

Una cosa che nessuno ha sottolineato è che è possibile utilizzare le LATERALquery per applicare una funzione definita dall'utente su ogni riga selezionata.

Per esempio:

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);

Questo è l'unico modo in cui so come fare questo genere di cose in PostgreSQL.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.