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?
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
:
LATERAL
Il fait plus autorité que tout ce que nous allons mettre en réponse ici:
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 [, ... ] )
:
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
Pour les types de jointure
INNER
etOUTER
, une condition de jointure doit être spécifiée, à savoir exactement l'un desNATURAL
,ON
join_condition ouUSING
(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.
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.
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é.
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.