Quelle est la différence entre LATERAL et une sous-requête dans PostgreSQL?

146

Depuis que Postgres est sorti avec la possibilité de faire des LATERALjointures, j'ai lu à ce sujet, car je fais actuellement des vidages de données complexes pour mon équipe avec beaucoup de sous-requêtes inefficaces qui font que la requête globale prend quatre minutes ou plus.

Je comprends que les LATERALjointures peuvent m'aider, mais même après avoir lu des articles comme celui-ci de Heap Analytics, je ne suis toujours pas tout à fait à suivre.

Quel est le cas d'utilisation d'une LATERALjointure? Quelle est la différence entre une LATERALjointure et une sous-requête?

jdotjdot
la source
2
blog.heapanalytics.com/... et explainextended.com/2009/07/16/inner-join-vs-cross-apply (de SQL Server applyest le même que celui lateralde la norme SQL)
a_horse_with_no_name

Réponses:

163

Plus comme une sous-requête corrélée

Une LATERALjointure (Postgres 9.3 ou version ultérieure) ressemble plus à une sous - requête corrélée , pas à une sous-requête simple. Comme l'a souligné Andomar , une fonction ou une sous-requête à droite d'une LATERALjointure doit être évaluée une fois pour chaque ligne à gauche - tout comme une sous-requête corrélée - tandis qu'une sous-requête simple (expression de table) n'est évaluée qu'une seule fois . (Le planificateur de requêtes a cependant des moyens d'optimiser les performances pour l'un ou l'autre.)
Cette réponse connexe a des exemples de code pour les deux côte à côte, résolvant le même problème:

Pour le retour plus d'une colonne , une LATERALjointure est généralement plus simple, plus propre et plus rapide.
Souvenez-vous également que l'équivalent d'une sous-requête corrélée est LEFT JOIN LATERAL ... ON true:

Lisez le manuel sur LATERAL

Il fait plus autorité que tout ce que nous allons mettre dans les réponses ici:

Choses qu'une sous-requête ne peut pas faire

Il y a des choses qu'une LATERALjointure peut faire, mais une sous-requête (corrélée) ne le peut pas (facilement). Une sous-requête corrélée ne peut renvoyer qu'une seule valeur, pas plusieurs colonnes ni plusieurs lignes - à l'exception des appels de fonction nus (qui multiplient les lignes de résultat si elles renvoient plusieurs lignes). Mais même certaines fonctions de retour d'ensemble ne sont autorisées que dans la FROMclause. Comme unnest()avec plusieurs paramètres dans Postgres 9.4 ou version ultérieure. Le manuel:

Ceci n'est autorisé que dans la FROMclause;

Donc, cela fonctionne, mais ne peut pas être facilement remplacé par une sous-requête:

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

La virgule ( ,) dans la FROMclause est une notation courte pour CROSS JOIN.
LATERALest supposé automatiquement pour les fonctions de table.
En savoir plus sur le cas particulier de UNNEST( array_expression [, ... ] ):

Fonctions de retour d'ensemble dans la SELECTliste

Vous pouvez également utiliser directement des fonctions de retour de set comme unnest()dans la SELECTliste. Cela montrait un comportement surprenant avec plus d'une de ces fonctions dans la même SELECTliste jusqu'à Postgres 9.6. Mais il a finalement été nettoyé avec Postgres 10 et est une alternative valide maintenant (même si ce n'est pas le SQL standard). Voir:

S'appuyant sur l'exemple ci-dessus:

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

Comparaison:

dbfiddle pour pg 9.6 ici
dbfiddle pour pg 10 ici

Clarifier la désinformation

Le manuel:

Pour les types de jointure INNERet OUTER, une condition de jointure doit être spécifiée, à savoir exactement l'une des conditions NATURAL, ON join_condition , ou USING( join_column [, ...]). Voir ci-dessous pour la signification.
Car CROSS JOINaucune de ces clauses ne peut apparaître.

Donc, ces deux requêtes sont valides (même si elles ne sont pas particulièrement utiles):

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;

Bien que celui-ci ne soit pas:

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

C'est pourquoi l' exemple de code de @ Andomar est correct (le CROSS JOINne nécessite pas de condition de jointure) et celui de @ Attila n'est pas valide.

Erwin Brandstetter
la source
Il y a certaines choses qu'une sous-requête peut faire qu'un LATERAL JOIN ne peut pas faire. Comme les fonctions de fenêtre. Comme ici
Evan Carroll
@EvanCarroll: Je n'ai pas trouvé de sous-requêtes corrélées dans le lien. Mais j'ai ajouté une autre réponse pour démontrer une fonction de fenêtre dans une LATERALsous - requête: gis.stackexchange.com/a/230070/7244
Erwin Brandstetter
1
Plus propre et plus rapide? Comme des magnitudes plus rapides dans certains cas. J'ai eu une requête qui est passée de quelques jours à quelques secondes après le passage à LATERAL.
rovyko
52

La différence entre un non lateralet une lateraljointure réside dans le fait que vous pouvez regarder la ligne de la table de gauche. Par exemple:

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

Ce "regard vers l'extérieur" signifie que la sous-requête doit être évaluée plus d'une fois. Après tout, t1.col1peut prendre de nombreuses valeurs.

En revanche, la sous-requête après une non- lateraljointure peut être évaluée une fois:

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

Comme cela est requis sans lateral, la requête interne ne dépend en aucun cas de la requête externe. Une lateralrequête est un exemple de correlatedrequête, en raison de sa relation avec les lignes en dehors de la requête elle-même.

Andomar
la source
5
C'est l'explication la plus claire de la jointure latérale.
1valdis
explication facile à comprendre, merci.
arilwan le
comment select * from table1 left join t2 using (col1)comparer? Je ne sais pas quand une jointure utilisant / sur condition est insuffisante et il serait plus logique d'utiliser latéral.
No_name
9

Premièrement, les applications latérales et croisées sont la même chose . Par conséquent, vous pouvez également lire sur Cross Apply. Comme il a été implémenté dans SQL Server depuis des lustres, vous trouverez plus d'informations à ce sujet puis Lateral.

Deuxièmement, selon ma compréhension , il n'y a rien que vous ne puissiez pas faire en utilisant une sous-requête au lieu d'utiliser latéral. Mais:

Pensez à la requête suivante.

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 

Vous pouvez utiliser latéral dans cette condition.

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

Dans cette requête, vous ne pouvez pas utiliser de jointure normale, en raison de la clause limit. L'application latérale ou croisée peut être utilisée lorsqu'il n'y a pas de condition de jointure simple .

Il y a plus d'utilisations pour les applications latérales ou croisées, mais c'est la plus courante que j'ai trouvée.

Atilla Ozgur
la source
1
Exactement, je me demande pourquoi PostgreSQL utilise à la lateralplace de apply. Peut-être que Microsoft a breveté la syntaxe?
Andomar
9
@Andomar AFAIK lateralest dans le standard SQL mais applyne l'est pas.
mu est trop court le
Le LEFT JOINnécessite une condition de jointure. Faites-le ON TRUEsauf si vous souhaitez restreindre d'une manière ou d'une autre.
Erwin Brandstetter
Erwin a raison, vous obtiendrez une erreur sauf si vous utilisez cross joinune oncondition ou une condition
Andomar
1
@Andomar: Poussé par cette désinformation, j'ai ajouté une autre réponse pour clarifier.
Erwin Brandstetter
4

Personne n'a souligné que vous pouvez utiliser des LATERALrequêtes pour appliquer une fonction définie par l'utilisateur sur chaque ligne sélectionnée.

Par exemple:

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

C'est la seule façon dont je sais comment faire ce genre de chose dans PostgreSQL.

Théodore R. Smith
la source