web-dev-qa-db-fra.com

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

Depuis que Postgres est sorti avec la possibilité de faire LATERAL des jointures, je lis des informations à ce sujet, car je fais actuellement des dumps de données complexes pour mon équipe avec de nombreuses sous-requêtes inefficaces qui font que la requête globale prend quatre minutes ou plus. .

Je comprends que LATERAL les jointures puissent m'aider, mais même après avoir lu des articles tels que celui-ci de Heap Analytics, je ne suis toujours pas très à la hauteur.

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

109
jdotjdot

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

Une jointure LATERAL (Postgres 9.3 ou version ultérieure) ressemble davantage à une sous-requête corrélée , et non à une sous-requête simple. Comme Andomar a souligné , une fonction ou une sous-requête à la droite d'une jointure LATERAL doit être évaluée une fois pour chaque ligne restante - comme un corrélé sous-requête - alors qu'une sous-requête simple (expression de table) est évaluée ne fois seulement. (Cependant, le planificateur de requêtes dispose de moyens pour optimiser les performances.)
Cette réponse associée a des exemples de code côte à côte, résolvant le même problème:

Pour renvoyer plus d'une colonne, une jointure LATERAL est généralement plus simple, plus propre et plus rapide.
N'oubliez pas non plus 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 en réponse ici:

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

Il y a il y a des choses qu'une jointure LATERAL peut faire, mais pas une sous-requête (corrélée) (facilement). Une sous-requête corrélée ne peut renvoyer qu'une seule valeur, ni 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 de jeu ne sont autorisées que dans la clause FROM. Comme unnest() avec plusieurs paramètres dans Postgres 9.4 ou version ultérieure. Le manuel:

Ceci n'est autorisé que dans la clause FROM;

Donc, cela fonctionne, mais ne peut pas facilement être 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 clause FROM est une notation courte pour _CROSS JOIN_.
LATERAL est supposé automatiquement pour les fonctions de tableau.
En savoir plus sur le cas particulier de UNNEST( array_expression [, ... ] ):

Fonctions de retour dans la liste SELECT

Vous pouvez également utiliser directement les fonctions de retour de jeu telles que unnest() dans la liste SELECT. Cela présentait un comportement surprenant avec plus d'une telle fonction dans la même liste SELECT jusqu'à Postgres 9.6. Mais il a finalement été nettoyé avec Postgres 1 et est une alternative valable 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 for pg 9.6 ici
dbfiddle for pg 10 ici

Clarifier la désinformation

Le manuel:

Pour les types de jointure INNER et OUTER, une condition de jointure doit être spécifiée, à savoir exactement l'un des NATURAL, ONjoin_condition ou USING (join_column [ ...]). Voir ci-dessous pour le sens.
Pour _CROSS JOIN_, aucune 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;
_

Alors que celui-ci n'est 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 @ Andomar est correct (le _CROSS JOIN_ ne nécessite pas de condition de jointure) et @ Attila est était invalide.

127
Erwin Brandstetter

La différence entre une jointure non -lateral et une lateral réside dans le fait que vous pouvez regarder dans la ligne du tableau 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

Cette "apparence" signifie que la sous-requête doit être évaluée plusieurs fois. Après tout, t1.col1 peut prendre plusieurs valeurs.

En revanche, la sous-requête après une jointure non -lateral 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 requis sans lateral, la requête interne ne dépend en aucune manière de la requête externe. Une requête lateral est un exemple de requête correlated, en raison de sa relation avec les lignes extérieures à la requête elle-même.

33
Andomar

Tout d'abord, appliquer latéralement et en croix est la même chose . Par conséquent, vous pouvez également lire à propos de l'application croisée. Comme il a été implémenté dans SQL Server pendant une éternité, vous trouverez plus d’informations à son sujet, puis latéral.

Deuxièmement, selon ma compréhension , il n’ya rien que vous ne puissiez pas utiliser avec 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 la jointure normale, en raison de la clause limit. L'application latérale ou transversale peut être utilisée lorsqu'il n'y a pas de condition de jointure simple .

Il y a plus d'usages pour l'application latérale ou en croix, mais c'est le plus commun que j'ai trouvé.

9
Atilla Ozgur

Une chose que personne n'a signalée, c'est que vous pouvez utiliser les requêtes LATERAL 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 pour moi de savoir faire ce genre de chose dans PostgreSQL.

1