PostgreSQLLa base de données la plus sophistiquée au monde.
Documentation PostgreSQL 12.22 » Langage SQL » RequĂȘtes » Expressions de table

7.2. Expressions de table

Une expression de table calcule une table. L'expression de table contient une clause FROM qui peut ĂȘtre suivie des clauses WHERE, GROUP BY et HAVING. Les expressions triviales de table font simplement rĂ©fĂ©rence Ă  une table sur le disque, une table de base, mais des expressions plus complexes peuvent ĂȘtre utilisĂ©es pour modifier ou combiner des tables de base de diffĂ©rentes façons.

Les clauses optionnelles WHERE, GROUP BY et HAVING dans l'expression de table spĂ©cifient un tube de transformations successives rĂ©alisĂ©es sur la table dĂ©rivĂ©e de la clause FROM. Toutes ces transformations produisent une table virtuelle fournissant les lignes Ă  passer Ă  la liste de sĂ©lection qui choisira les lignes Ă  afficher de la requĂȘte.

7.2.1. Clause FROM

La la section intitulĂ©e « Clause FROM Â» dĂ©rive une table Ă  partir d'une ou plusieurs tables donnĂ©es dans une liste de rĂ©fĂ©rence dont les tables sont sĂ©parĂ©es par des virgules.

FROM reference_table [, reference_table [, ...]]

Une rĂ©fĂ©rence de table pourrait ĂȘtre un nom de table (avec en option le nom du schĂ©ma) ou de table dĂ©rivĂ©e, telle qu'une sous-requĂȘte, une construction JOIN ou une combinaison complexe de ces possibilitĂ©s. Si plus d'une rĂ©fĂ©rence de table est listĂ©e dans la clause FROM, les tables sont jointes en croisĂ© (autrement dit, cela rĂ©alise un produit cartĂ©sien de leurs lignes ; voir ci-dessous). Le rĂ©sultat de la liste FROM est une table virtuelle intermĂ©diaire pouvant ĂȘtre sujette aux transformations des clauses WHERE, GROUP BY et HAVING, et est finalement le rĂ©sultat des expressions de table.

Lorsqu'une référence de table nomme une table qui est la table parent d'une table suivant la hiérarchie de l'héritage, la référence de table produit les lignes non seulement de la table, mais aussi des descendants de cette table, sauf si le mot-clé ONLY précÚde le nom de la table. Néanmoins, la référence produit seulement les colonnes qui apparaissent dans la table nommée... Toute colonne ajoutée dans une sous-table est ignorée.

Au lieu d'écrire ONLY avant le nom de la table, vous pouvez écrire * aprÚs le nom de la table pour indiquer spécifiquement que les tables filles sont incluses. Il n'y a plus de vraie raison pour encore utiliser cette syntaxe, car chercher dans les tables descendantes est maintenant le comportement par défaut. C'est toutefois supporté pour compatibilité avec des versions plus anciennes.

7.2.1.1. Tables jointes

Une table jointe est une table dĂ©rivĂ©e de deux autres tables (rĂ©elles ou dĂ©rivĂ©es) suivant les rĂšgles du type de jointure particulier. Les jointures internes (inner), externes (outer) et croisĂ©es (cross) sont disponibles. La syntaxe gĂ©nĂ©rale d'une table jointe est :

T1 type_jointure T2 [ condition_jointure ]
   

Des jointures de tous types peuvent ĂȘtre chaĂźnĂ©es ensemble ou imbriquĂ©es : une des deux tables ou les deux tables peuvent ĂȘtre des tables jointes. Des parenthĂšses peuvent ĂȘtre utilisĂ©es autour des clauses JOIN pour contrĂŽler l'ordre de jointure. Dans l'absence des parenthĂšses, les clauses JOIN s'imbriquent de gauche Ă  droite.

Types de jointures

Jointure croisée (cross join)
T1 CROSS JOIN T2

Pour chaque combinaison possible de lignes provenant de T1 et T2 (c'est-à-dire un produit cartésien), la table jointe contiendra une ligne disposant de toutes les colonnes de T1 suivies par toutes les colonnes de T2. Si les tables ont respectivement N et M lignes, la table jointe en aura N * M.

FROM T1 CROSS JOIN T2 est Ă©quivalent Ă  FROM T1 INNER JOIN T2 ON TRUE (voir ci-dessous). C'est aussi Ă©quivalent Ă  : FROM T1, T2.

Note

Cette derniÚre équivalence ne convient pas exactement quand plusieurs tables apparaissent, car JOIN lie de façon plus profonde que la virgule. Par exemple, FROM T1 CROSS JOIN T2 INNER JOIN T3 ON condition n'est pas identique à FROM T1, T2 INNER JOIN T3 ON condition, car condition peut faire référence à T1 dans le premier cas, mais pas dans le second.

Jointures qualifiées (qualified joins)
T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 ON expression_booleenne
T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 USING ( liste des colonnes jointes )
T1 NATURAL { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2

Les mots INNER et OUTER sont optionnels dans toutes les formes. INNER est la valeur par dĂ©faut ; LEFT, RIGHT et FULL impliquent une jointure externe.

La condition de la jointure est spĂ©cifiĂ©e dans la clause ON ou USING, ou implicitement par le mot NATURAL. La condition de jointure dĂ©termine les lignes des deux tables sources considĂ©rĂ©es comme « correspondante Â», comme l'explique le paragraphe ci-dessous.

Les types possibles de jointures qualifiĂ©es sont :

INNER JOIN

Pour chaque ligne R1 de T1, la table jointe a une ligne pour chaque ligne de T2 satisfaisant la condition de jointure avec R1.

LEFT OUTER JOIN

Tout d'abord, une jointure interne est réalisée. Puis, pour chaque ligne de T1 qui ne satisfait pas la condition de jointure avec les lignes de T2, une ligne jointe est ajoutée avec des valeurs NULL dans les colonnes de T2. Du coup, la table jointe a toujours au moins une ligne pour chaque ligne de T1, quelles que soient les conditions.

RIGHT OUTER JOIN

Tout d'abord, une jointure interne est rĂ©alisĂ©e. Puis, pour chaque ligne de T2 qui ne satisfait pas la condition de jointure avec les lignes de T1, une ligne jointe est ajoutĂ©e avec des valeurs NULL dans les colonnes de T1. C'est l'inverse d'une jointure gauche : la table rĂ©sultante aura toujours une ligne pour chaque ligne de T2, quelles que soient les conditions.

FULL OUTER JOIN

Tout d'abord, une jointure interne est réalisée. Puis, pour chaque ligne de T1 qui ne satisfait pas la condition de jointure avec les lignes de T2, une ligne jointe est ajoutée avec des valeurs NULL dans les colonnes de T2. De plus, pour chaque ligne de T2 qui ne satisfait pas la condition de jointure avec les lignes de T1, une ligne jointe est ajoutée avec des valeurs NULL dans les colonnes de T1.

La clause ON est le type de condition de jointure le plus utilisĂ© : elle prend une valeur boolĂ©enne du mĂȘme type que celle utilisĂ©e dans une clause WHERE. Une paire de lignes provenant de T1 et de T2 correspondent si l'expression de la clause ON vaut true.

La clause USING est un raccourci qui vous permet de prendre avantage d'une situation spĂ©cifique oĂč les deux cĂŽtĂ©s de la jointure utilisent le mĂȘme nom pour la colonne jointe. Elle prend une liste de noms de colonnes partagĂ©es, en sĂ©parant les noms par des virgules et forme une condition de jointure qui inclut une comparaison d'Ă©galitĂ© entre chaque. Par exemple, joindre T1 et T2 avec USING (a, b) produit la mĂȘme condition de jointure que la condition ON T1.a = T2.a AND T1.b = T2.b.

De plus, la sortie de JOIN USING supprime les colonnes redondantes : il n'est pas nĂ©cessaire d'imprimer les colonnes de correspondance, puisqu'elles doivent avoir des valeurs identiques. Alors que JOIN ON produit toutes les colonnes de T2, JOIN USING produit une seule colonne pour chaque paire de colonnes listĂ©es (dans l'ordre listĂ©), suivi par chaque colonne restante provenant de T1, suivi par chaque colonne restante provenant de T2.

Enfin, NATURAL est un raccourci de USING : il forme une liste USING constituĂ©e de tous les noms de colonnes apparaissant dans les deux tables en entrĂ©e. Comme avec USING, ces colonnes apparaissent une fois seulement dans la table en sortie. S'il n'existe aucun nom commun de colonne, NATURAL JOIN se comporte comme JOIN ... ON TRUE et produit une jointure croisĂ©e.

Note

USING est raisonnablement protégé contre les changements de colonnes dans les relations jointes, car seuls les noms de colonnes listés sont combinés. NATURAL est considéré comme plus risqué, car toute modification de schéma causant l'apparition d'un nouveau nom de colonne correspondant fera en sorte de joindre la nouvelle colonne.

Pour rassembler tout ceci, supposons que nous avons une table t1 :

 no | nom
----+------
  1 | a
  2 | b
  3 | c

et une table t2 :

 no | valeur
----+-------
  1 | xxx
  3 | yyy
  5 | zzz

Nous obtenons les rĂ©sultats suivants pour les diffĂ©rentes jointures :

=> SELECT * FROM t1 CROSS JOIN t2;
 no | nom | no | valeur
----+-----+----+-------
  1 | a   |  1 | xxx
  1 | a   |  3 | yyy
  1 | a   |  5 | zzz
  2 | b   |  1 | xxx
  2 | b   |  3 | yyy
  2 | b   |  5 | zzz
  3 | c   |  1 | xxx
  3 | c   |  3 | yyy
  3 | c   |  5 | zzz
(9 rows)

=> SELECT * FROM t1 INNER JOIN t2 ON t1.no = t2.no;
 no | nom | no | valeur
----+-----+----+-------
  1 | a   |  1 | xxx
  3 | c   |  3 | yyy
(2 rows)

=> SELECT * FROM t1 INNER JOIN t2 USING (no);
 no | nom | valeur
----+-----+-------
  1 | a   | xxx
  3 | c   | yyy
(2 rows)

=> SELECT * FROM t1 NATURAL INNER JOIN t2;
 no | nom | valeur
----+-----+-------
  1 | a   | xxx
  3 | c   | yyy
(2 rows)

=> SELECT * FROM t1 LEFT JOIN t2 ON t1.no = t2.no;
 no | nom | no | valeur
----+-----+----+-------
  1 | a   |  1 | xxx
  2 | b   |    |
  3 | c   |  3 | yyy
(3 rows)

=> SELECT * FROM t1 LEFT JOIN t2 USING (no);
 no | nom | valeur
----+-----+-------
  1 | a   | xxx
  2 | b   |
  3 | c   | yyy
(3 rows)

=> SELECT * FROM t1 RIGHT JOIN t2 ON t1.no = t2.no;
 no | nom | no | valeur
----+-----+----+-------
  1 | a   |  1 | xxx
  3 | c   |  3 | yyy
    |     |  5 | zzz
(3 rows)

=> SELECT * FROM t1 FULL JOIN t2 ON t1.no = t2.no;
 no | nom | no | valeur
----+-----+----+-------
  1 | a   |  1 | xxx
  2 | b   |    |
  3 | c   |  3 | yyy
    |     |  5 | zzz
(4 rows)

La condition de jointure spĂ©cifiĂ©e avec ON peut aussi contenir des conditions sans relation directe avec la jointure. Ceci est utile pour quelques requĂȘtes, mais son utilisation doit avoir Ă©tĂ© rĂ©flĂ©chie. Par exemple :

=> SELECT * FROM t1 LEFT JOIN t2 ON t1.no = t2.no AND t2.valeur = 'xxx';
 no | nom | no | valeur
----+-----+----+-------
  1 | a   |  1 | xxx
  2 | b   |    |
  3 | c   |    |
(3 rows)

Notez que placer la restriction dans la clause WHERE donne un rĂ©sultat diffĂ©rent :

=> SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num WHERE t2.value = 'xxx';
 num | name | num | value
-----+------+-----+-------
   1 | a    |   1 | xxx
(1 row)
     

Ceci est dû au fait qu'une restriction placée dans la clause ON est traitée avant la jointure, alors qu'une restriction placée dans la clause WHERE est traitée aprÚs la jointure. Ceci n'a pas d'importance avec les jointures internes, mais en a une grande avec les jointures externes.

7.2.1.2. Alias de table et de colonne

Un nom temporaire peut ĂȘtre donnĂ© aux tables et aux rĂ©fĂ©rences de tables complexes, nom qui sera ensuite utilisĂ© pour rĂ©fĂ©rencer la table dĂ©rivĂ©e dans la suite de la requĂȘte. Cela s'appelle un alias de table.

Pour créer un alias de table, écrivez

FROM reference_table AS alias

ou

FROM reference_table alias

Le mot-clĂ© AS n'est pas obligatoire. alias peut ĂȘtre tout identifiant.

Une application typique des alias de table est l'affectation d'identifieurs courts pour les noms de tables longs, ce qui permet de garder des clauses de jointures lisibles. Par exemple :

SELECT * FROM nom_de_table_tres_tres_long s
  JOIN un_autre_nom_tres_long a ON s.id = a.no;

L'alias devient le nouveau nom de la table en ce qui concerne la requĂȘte en cours -- il n'est pas autorisĂ© de faire rĂ©fĂ©rence Ă  la table par son nom original oĂč que ce soit dans la requĂȘte. Du coup, ceci n'est pas valide :

SELECT * FROM mon_table AS m WHERE mon_table.a > 5;    -- mauvais
     

Les alias de table sont disponibles principalement pour aider Ă  l'Ă©criture de requĂȘte, mais ils deviennent nĂ©cessaires pour joindre une table avec elle-mĂȘme, par exemple :

SELECT * FROM personnes AS mere JOIN personnes AS enfant ON mere.id = enfant.mere_id;
     

De plus, un alias est requis si la rĂ©fĂ©rence de la table est une sous-requĂȘte (voir la Section 7.2.1.3).

Les parenthĂšses sont utilisĂ©es pour rĂ©soudre les ambiguĂŻtĂ©s. Dans l'exemple suivant, la premiĂšre instruction affecte l'alias b Ă  la deuxiĂšme instance de ma_table, mais la deuxiĂšme instruction affecte l'alias au rĂ©sultat de la jonction :

SELECT * FROM ma_table AS a CROSS JOIN ma_table AS b ...
SELECT * FROM (ma_table AS a CROSS JOIN ma_table) AS b ...

Une autre forme d'alias de tables donne des noms temporaires aux colonnes de la table ainsi qu'Ă  la table :

FROM reference_table [AS] alias ( colonne1 [, colonne2 [, ...]] )

Si le nombre d'alias de colonnes spĂ©cifiĂ© est plus petit que le nombre de colonnes dont dispose la table rĂ©elle, les colonnes suivantes ne sont pas renommĂ©es. Cette syntaxe est particuliĂšrement utile dans le cas de jointures avec la mĂȘme table ou dans le cas de sous-requĂȘtes.

Quand un alias est appliquĂ© Ă  la sortie d'une clause JOIN, l'alias cache le nom original rĂ©fĂ©rencĂ© Ă  l'intĂ©rieur du JOIN. Par exemple :

SELECT a.* FROM ma_table AS a JOIN ta_table AS b ON ...

est du SQL valide, mais :

SELECT a.* FROM (ma_table AS a JOIN ta_table AS b ON ...) AS c

n'est pas valide ; l'alias de table a n'est pas visible en dehors de l'alias c.

7.2.1.3. Sous-requĂȘtes

Une sous-requĂȘte spĂ©cifiant une table dĂ©rivĂ©e doit ĂȘtre enfermĂ©e dans des parenthĂšses et doit se voir affecter un alias de table (comme dans Section 7.2.1.2). Par exemple :

FROM (SELECT * FROM table1) AS nom_alias

Cet exemple est Ă©quivalent Ă  FROM table1 AS nom_alias. Des cas plus intĂ©ressants, qui ne peuvent pas ĂȘtre rĂ©duits Ă  une jointure pleine, surviennent quand la sous-requĂȘte implique un groupement ou un agrĂ©gat.

Une sous-requĂȘte peut aussi ĂȘtre une liste VALUES :

FROM (VALUES ('anne', 'smith'), ('bob', 'jones'), ('joe', 'blow'))
     AS noms(prenom, nom)
     

De nouveau, un alias de table est requis. Affecter des noms d'alias aux colonnes de la liste VALUES est optionnel, mais c'est une bonne pratique. Pour plus d'informations, voir Section 7.7.

7.2.1.4. Fonctions de table

Les fonctions de table sont des fonctions produisant un ensemble de lignes composĂ©es de types de donnĂ©es de base (types scalaires) ou de types de donnĂ©es composites (lignes de table). Elles sont utilisĂ©es comme une table, une vue ou une sous-requĂȘte de la clause FROM d'une requĂȘte. Les colonnes renvoyĂ©es par les fonctions de table peuvent ĂȘtre incluses dans une clause SELECT, JOIN ou WHERE de la mĂȘme maniĂšre que les colonnes d'une table, vue ou sous-requĂȘte.

Les fonctions de table peuvent aussi ĂȘtre combinĂ©es en utilisant la syntaxe ROWS FROM, avec les rĂ©sultats renvoyĂ©s dans des colonnes parallĂšles ; le nombre de lignes rĂ©sultantes dans ce cas est celui du rĂ©sultat de fonction le plus large. Les rĂ©sultats ayant moins de colonnes sont alignĂ©s avec des valeurs NULL.

appel_fonction [WITH ORDINALITY] [[AS] alias_table [(alias_colonne [, ... ])]]
ROWS FROM( appel_fonction [, ... ] ) [WITH ORDINALITY] [[AS] alias_table [(alias_colonne [, ... ])]]
    

Si la clause WITH ORDINALITY est ajoutĂ©e, une colonne supplĂ©mentaire de type bigint sera ajoutĂ©e aux colonnes de rĂ©sultat de la fonction. Cette colonne numĂ©rote les lignes de l'ensemble de rĂ©sultats de la fonction, en commençant Ă  1. (Ceci est une gĂ©nĂ©ralisation de la syntaxe du standard SQL pour UNNEST ... WITH ORDINALITY.) Par dĂ©faut, la colonne ordinale est appelĂ©e ordinality, mais un nom de colonne diffĂ©rent peut ĂȘtre affectĂ© en utilisant une clause AS.

La fonction de table UNNEST peut ĂȘtre appelĂ©e avec tout nombre de paramĂštres tableaux, et envoie un nombre correspondant de colonnes comme si la fonction UNNEST avait Ă©tĂ© appelĂ©e sur chaque paramĂštre sĂ©parĂ©ment (Section 9.18) et combinĂ©e en utilisant la construction ROWS FROM.

UNNEST( expression_tableau [, ... ] ) [WITH ORDINALITY] [[AS] alias_table [(alias_colonne [, ... ])]]
    

Si aucun alias_table n'est prĂ©cisĂ©, le nom de la fonction est utilisĂ© comme nom de table ; dans le cas d'une construction ROWS FROM(), le nom de la premiĂšre fonction est utilisĂ©.

Si des alias de colonnes ne sont pas fournis pour une fonction renvoyant un type de donnĂ©es de base, alors le nom de la colonne est aussi le mĂȘme que le nom de la fonction. Pour une fonction renvoyant un type composite, les colonnes rĂ©sultats obtiennent les noms des attributs individuels du type.

Quelques exemples :

CREATE TABLE truc (trucid int, trucsousid int, trucnom text);

CREATE FUNCTION recuptruc(int) RETURNS SETOF truc AS $$
    SELECT * FROM truc WHERE trucid = $1;
$$ LANGUAGE SQL;

SELECT * FROM recuptruc(1) AS t1;

SELECT * FROM truc
    WHERE trucsousid IN (
                        SELECT trucsousid
                        FROM recuptruc(truc.trucid) z
                        WHERE z.trucid = truc.trucid);

CREATE VIEW vue_recuptruc AS SELECT * FROM recuptruc(1);
SELECT * FROM vue_recuptruc;

Dans certains cas, il est utile de dĂ©finir des fonctions de table pouvant renvoyer des ensembles de colonnes diffĂ©rentes suivant la façon dont elles sont appelĂ©es. Pour supporter ceci, la fonction de table est dĂ©clarĂ©e comme renvoyant le pseudotype record sans paramĂštres OUT. Quand une telle fonction est utilisĂ©e dans une requĂȘte, la structure de ligne attendue doit ĂȘtre spĂ©cifiĂ©e dans la requĂȘte elle-mĂȘme, de façon Ă  ce que le systĂšme sache comment analyser et planifier la requĂȘte. Cette syntaxe ressemble Ă  ceci :

appel_fonction [AS] alias (définition_colonne [, ... ])
appel_fonction AS [alias] (définition_colonne [, ... ])
ROWS FROM( ... appel_fonction AS (définition_colonne [, ... ]) [, ... ] )
     

Lorsque la syntaxe ROWS FROM() n'est pas utilisĂ©e, la liste dĂ©finition_colonne remplace la liste d'alias de colonnes qui aurait Ă©tĂ© autrement attachĂ©e Ă  la clause FROM ; les noms dans les dĂ©finitions de colonnes servent comme alias de colonnes. Lors de l'utilisation de la syntaxe ROWS FROM(), une liste dĂ©finition_colonne peut ĂȘtre attachĂ©e Ă  chaque fonction membre sĂ©parĂ©ment ; ou s'il existe seulement une fonction membre et pas de clause WITH ORDINALITY, une liste column_definition peut ĂȘtre Ă©crite au lieu de la liste d'alias de colonnes suivant ROWS FROM().

ConsidĂ©rez cet exemple :

SELECT *
    FROM dblink('dbname=mabd', 'SELECT proname, prosrc FROM pg_proc')
      AS t1(proname nom, prosrc text)
    WHERE proname LIKE 'bytea%';

La fonction dblink (qui fait partie du module dblink) exĂ©cute une requĂȘte distante. Elle dĂ©clare renvoyer le type record, car elle pourrait ĂȘtre utilisĂ©e pour tout type de requĂȘte. L'ensemble de colonnes rĂ©elles doit ĂȘtre spĂ©cifiĂ© dans la requĂȘte appelante de façon Ă  ce que l'analyseur sache, par exemple, comment Ă©tendre *.

Cet exemple utilise ROWS FROM :

SELECT *
FROM ROWS FROM
    (
        json_to_recordset('[{"a":40,"b":"foo"},{"a":"100","b":"bar"}]')
            AS (a INTEGER, b TEXT),
        generate_series(1, 3)
    ) AS x (p, q, s)
ORDER BY p;

  p  |  q  | s
-----+-----+---
  40 | foo | 1
 100 | bar | 2
     |     | 3
      

Il joint deux fonctions en une seule cible FROM. json_to_recordset() doit renvoyer deux colonnes, la premiÚre de type integer et la seconde de type text. Le résultat de generate_series() est utilisé directement. La clause ORDER BY trie les valeurs de la colonne en tant qu'entiers.

7.2.1.5. Sous-requĂȘtes LATERAL

Les sous-requĂȘtes apparaissant dans la clause FROM peuvent ĂȘtre prĂ©cĂ©dĂ©es du mot-clĂ© LATERAL. Ceci leur permet de rĂ©fĂ©rencer les colonnes fournies par les Ă©lĂ©ments prĂ©cĂ©dents dans le FROM. (Sans LATERAL, chaque sous-requĂȘte est Ă©valuĂ©e indĂ©pendamment et ne peut donc pas rĂ©fĂ©rencer les autres Ă©lĂ©ments de la clause FROM.)

Les fonctions renvoyant des ensembles et apparaissant dans le FROM peuvent aussi ĂȘtre prĂ©cĂ©dĂ©es du mot-clĂ© LATERAL, mais, pour les fonctions, le mot-clĂ© est optionnel. Les arguments de la fonction peuvent contenir des rĂ©fĂ©rences aux colonnes fournies par les Ă©lĂ©ments prĂ©cĂ©dents dans le FROM.

Un élément LATERAL peut apparaßtre au niveau haut dans la liste FROM ou dans un arbre de jointures (JOIN). Dans ce dernier cas, cela peut aussi faire référence à tout élément qui est sur le cÎté gauche d'un JOIN, alors qu'il est positionné sur sa droite.

Quand un Ă©lĂ©ment FROM contient des rĂ©fĂ©rences croisĂ©es LATERAL, l'Ă©valuation se fait ainsi : pour chaque ligne d'un Ă©lĂ©ment FROM fournissant les colonnes rĂ©fĂ©rencĂ©es, ou pour chaque ensemble de lignes de plusieurs Ă©lĂ©ments FROM fournissant les colonnes, l'Ă©lĂ©ment LATERAL est Ă©valuĂ© en utilisant cette valeur de ligne ou cette valeur d'ensemble de lignes. Les lignes rĂ©sultantes sont jointes comme d'habitude aux lignes rĂ©sultant du calcul. C'est rĂ©pĂ©tĂ© pour chaque ligne ou ensemble de lignes provenant de la table source.

Un exemple trivial de LATERAL est

SELECT * FROM foo, LATERAL (SELECT * FROM bar WHERE bar.id = foo.bar_id) ss;
      

Ceci n'est pas vraiment utile, car cela revient exactement au mĂȘme rĂ©sultat que cette Ă©criture plus conventionnelle :

SELECT * FROM foo, bar WHERE bar.id = foo.bar_id;
      

LATERAL est principalement utile lorsqu'une colonne rĂ©fĂ©rencĂ©e est nĂ©cessaire pour calculer la colonne Ă  joindre. Une utilisation habituelle est de fournir une valeur d'un argument Ă  une fonction renvoyant un ensemble de lignes. Par exemple, supposons que vertices(polygon) renvoie l'ensemble de sommets d'un polygone, nous pouvons identifier les sommets proches des polygones stockĂ©s dans une table avec la requĂȘte suivante :

SELECT p1.id, p2.id, v1, v2
FROM polygones p1, polygones p2,
     LATERAL vertices(p1.poly) v1,
     LATERAL vertices(p2.poly) v2
WHERE (v1 <-> v2) < 10 AND p1.id != p2.id;
      

Cette requĂȘte pourrait aussi ĂȘtre Ă©crite ainsi :

SELECT p1.id, p2.id, v1, v2
FROM polygons p1 CROSS JOIN LATERAL vertices(p1.poly) v1,
     polygons p2 CROSS JOIN LATERAL vertices(p2.poly) v2
WHERE (v1 <-> v2) < 10 AND p1.id != p2.id;
      

ou dans diverses autres formulations équivalentes. (Nous l'avons déjà mentionné, le mot-clé LATERAL est inutile dans cet exemple, mais nous l'utilisons pour plus de clarté.)

Il est souvent particuliĂšrement utile d'utiliser LEFT JOIN sur une sous-requĂȘte LATERAL, pour que les lignes sources apparaissent dans le rĂ©sultat mĂȘme si la sous-requĂȘte LATERAL ne produit aucune ligne pour elles. Par exemple, si get_product_names() renvoie les noms des produits rĂ©alisĂ©s par un manufacturier, mais que quelques manufacturiers dans notre table ne rĂ©alisent aucun produit, nous pourrions les trouver avec cette requĂȘte :

SELECT m.name
FROM manufacturers m LEFT JOIN LATERAL get_product_names(m.id) pname ON true
WHERE pname IS NULL;
      

7.2.2. Clause WHERE

La syntaxe de la la section intitulĂ©e « Clause WHERE Â» est

WHERE condition_recherche

oĂč condition_recherche est toute expression de valeur (voir la Section 4.2) renvoyant une valeur de type boolean.

AprĂšs le traitement de la clause FROM, chaque ligne de la table virtuelle dĂ©rivĂ©e est vĂ©rifiĂ©e avec la condition de recherche. Si le rĂ©sultat de la vĂ©rification est positif (true), la ligne est conservĂ©e dans la table de sortie, sinon (c'est-Ă -dire si le rĂ©sultat est faux ou nul), la ligne est abandonnĂ©e. La condition de recherche rĂ©fĂ©rence typiquement au moins une colonne de la table gĂ©nĂ©rĂ©e dans la clause FROM ; ceci n'est pas requis, mais, dans le cas contraire, la clause WHERE n'aurait aucune utilitĂ©.

Note

La condition de jointure d'une jointure interne peut ĂȘtre Ă©crite soit dans la clause WHERE soit dans la clause JOIN. Par exemple, ces expressions de tables sont Ă©quivalentes :

FROM a, b WHERE a.id = b.id AND b.val > 5

et :

FROM a INNER JOIN b ON (a.id = b.id) WHERE b.val > 5

ou mĂȘme peut-ĂȘtre :

FROM a NATURAL JOIN b WHERE b.val > 5

Laquelle utiliser est plutĂŽt une affaire de style. La syntaxe JOIN dans la clause FROM n'est probablement pas aussi portable vers les autres systĂšmes de gestion de bases de donnĂ©es SQL, mĂȘme si cela fait partie du standard SQL. Pour les jointures externes, il n'y a pas d'autres choix : elles doivent ĂȘtre faites dans la clause FROM. La clause ON ou USING d'une jointure externe n'est pas Ă©quivalente Ă  une condition WHERE parce qu'elle dĂ©termine l'ajout de lignes (pour les lignes qui ne correspondent pas en entrĂ©e) ainsi que pour la suppression de lignes dans le rĂ©sultat final.

Voici quelques exemples de clauses WHERE :

SELECT ... FROM fdt WHERE c1 > 5

SELECT ... FROM fdt WHERE c1 IN (1, 2, 3)

SELECT ... FROM fdt WHERE c1 IN (SELECT c1 FROM t2)

SELECT ... FROM fdt WHERE c1 IN (SELECT c3 FROM t2 WHERE c2 = fdt.c1 + 10)

SELECT ... FROM fdt WHERE c1 BETWEEN (SELECT c3 FROM t2 WHERE c2 = fdt.c1 + 10) AND 100

SELECT ... FROM fdt WHERE EXISTS (SELECT c1 FROM t2 WHERE c2 > fdt.c1)

fdt est la table dĂ©rivĂ©e dans la clause FROM. Les lignes qui ne correspondent pas Ă  la condition de recherche de la clause WHERE sont Ă©liminĂ©es de la table fdt. Notez l'utilisation de sous-requĂȘtes scalaires en tant qu'expressions de valeurs. Comme n'importe quelle autre requĂȘte, les sous-requĂȘtes peuvent employer des expressions de tables complexes. Notez aussi comment fdt est rĂ©fĂ©rencĂ©e dans les sous-requĂȘtes. Qualifier c1 comme fdt.c1 est seulement nĂ©cessaire si c1 est aussi le nom d'une colonne dans la table d'entrĂ©e dĂ©rivĂ©e de la sous-requĂȘte. Mais qualifier le nom de colonne ajoute de la clartĂ© mĂȘme lorsque cela n'est pas nĂ©cessaire. Cet exemple montre comment le nom de colonne d'une requĂȘte externe est Ă©tendu dans les requĂȘtes internes.

7.2.3. Clauses GROUP BY et HAVING

AprĂšs avoir passĂ© le filtre WHERE, la table d'entrĂ©e dĂ©rivĂ©e peut ĂȘtre sujette Ă  un regroupement en utilisant la clause GROUP BY et Ă  une Ă©limination de groupe de lignes avec la clause HAVING.

SELECT liste_selection
    FROM ...
    [WHERE ...]
    GROUP BY reference_colonne_regroupement[,reference_colonne_regroupement]...

La la section intitulĂ©e « Clause GROUP BY Â» est utilisĂ©e pour regrouper les lignes d'une table qui ont les mĂȘmes valeurs dans toutes les colonnes prĂ©cisĂ©es. L'ordre dans lequel ces colonnes sont indiquĂ©es importe peu. L'effet est de combiner chaque ensemble de lignes partageant des valeurs communes en un seul groupe de lignes reprĂ©sentant toutes les lignes du groupe. Ceci est fait pour Ă©liminer les redondances dans la sortie et/ou pour calculer les agrĂ©gats s'appliquant Ă  ces groupes. Par exemple :

=> SELECT * FROM test1;
 x | y
---+---
 a | 3
 c | 2
 b | 5
 a | 1
(4 rows)

=> SELECT x FROM test1 GROUP BY x;
 x
---
 a
 b
 c
(3 rows)

Dans la seconde requĂȘte, nous n'aurions pas pu Ă©crire SELECT * FROM test1 GROUP BY x parce qu'il n'existe pas une seule valeur pour la colonne y pouvant ĂȘtre associĂ©e avec chaque autre groupe. Les colonnes de regroupement peuvent ĂȘtre rĂ©fĂ©rencĂ©es dans la liste de sĂ©lection, car elles ont une valeur constante unique par groupe.

En gĂ©nĂ©ral, si une table est groupĂ©e, les colonnes qui ne sont pas listĂ©es dans le GROUP BY ne peuvent pas ĂȘtre rĂ©fĂ©rencĂ©es sauf dans les expressions d'agrĂ©gats. Voici un exemple d'expression d'agrĂ©gat :

=> SELECT x, sum(y) FROM test1 GROUP BY x;
 x | sum
---+-----
 a |   4
 b |   5
 c |   2
(3 rows)

Ici, sum est la fonction d'agrĂ©gat qui calcule une seule valeur pour le groupe entier. La Section 9.20 propose plus d'informations sur les fonctions d'agrĂ©gats disponibles.

Astuce

Le regroupement sans expressions d'agrĂ©gats calcule effectivement l'ensemble des valeurs distinctes d'une colonne. Ceci peut aussi se faire en utilisant la clause DISTINCT (voir la Section 7.3.3).

Voici un autre exemple : il calcule les ventes totales pour chaque produit (plutĂŽt que le total des ventes sur tous les produits) :

SELECT id_produit, p.nom, (sum(v.unite) * p.prix) AS ventes
    FROM produits p LEFT JOIN ventes v USING (id_produit)
    GROUP BY id_produit, p.nom, p.prix;

Dans cet exemple, les colonnes id_produit, p.nom et p.prix doivent ĂȘtre dans la clause GROUP BY, car elles sont rĂ©fĂ©rencĂ©es dans la liste de sĂ©lection de la requĂȘte (mais voir plus loin). La colonne v.unite n'a pas besoin d'ĂȘtre dans la liste GROUP BY, car elle est seulement utilisĂ©e dans l'expression de l'agrĂ©gat (sum(...)) reprĂ©sentant les ventes d'un produit. Pour chaque produit, la requĂȘte renvoie une ligne de rĂ©sumĂ© sur les ventes de ce produit.

Si la table produits est configurée de façon à ce que id_produit soit la clé primaire, alors il serait suffisant de grouper par la colonne id_produit dans l'exemple ci-dessus, car le nom et le prix seraient dépendants fonctionnellement de l'identifiant du produit, et donc il n'y aurait pas d'ambiguïté sur le nom et le prix à renvoyer pour chaque groupe d'identifiants de produits.

En SQL strict, GROUP BY peut seulement grouper les colonnes de la table source, mais PostgreSQL étend ceci en autorisant GROUP BY à grouper aussi les colonnes de la liste de sélection. Grouper par expressions de valeurs au lieu de simples noms de colonnes est aussi permis.

Si une table a Ă©tĂ© groupĂ©e en utilisant la clause GROUP BY, mais que seuls certains groupes sont intĂ©ressants, la clause HAVING peut ĂȘtre utilisĂ©e, comme une clause WHERE, pour Ă©liminer les groupes du rĂ©sultat. Voici la syntaxe :

SELECT liste_selection FROM ... [WHERE ...] GROUP BY ... HAVING expression_booléenne

Les expressions de la clause HAVING peuvent référer à la fois aux expressions groupées et aux expressions non groupées (ce qui implique nécessairement une fonction d'agrégat).

Exemple :

=> SELECT x, sum(y) FROM test1 GROUP BY x HAVING sum(y) > 3;
 x | sum
---+-----
 a |   4
 b |   5
(2 rows)

=> SELECT x, sum(y) FROM test1 GROUP BY x HAVING x < 'c';
 x | sum
---+-----
 a |   4
 b |   5
(2 rows)

De nouveau, un exemple plus rĂ©aliste :

SELECT id_produit, p.nom, (sum(v.unite) * (p.prix - p.cout)) AS profit
    FROM produits p LEFT JOIN ventes v USING (id_produit)
    WHERE v.date > CURRENT_DATE - INTERVAL '4 weeks'
    GROUP BY id_produit, p.nom, p.prix, p.cout
    HAVING sum(p.prix * s.unite) > 5000;

Dans l'exemple ci-dessus, la clause WHERE sĂ©lectionne les lignes par une colonne qui n'est pas groupĂ©e (l'expression est vraie seulement pour les ventes des quatre derniĂšres semaines) alors que la clause HAVING restreint la sortie aux groupes dont le total des ventes dĂ©passe 5000. Notez que les expressions d'agrĂ©gats n'ont pas besoin d'ĂȘtre identiques dans toutes les parties d'une requĂȘte.

Si une requĂȘte contient des appels Ă  des fonctions d'agrĂ©gat, mais pas de clause GROUP BY, le regroupement a toujours lieu : le rĂ©sultat est une seule ligne de regroupement (ou peut-ĂȘtre pas de ligne du tout si la ligne unique est ensuite Ă©liminĂ©e par la clause HAVING). Ceci est vrai aussi si elle comporte une clause HAVING, mĂȘme sans fonction d'agrĂ©gat ou GROUP BY.

7.2.4. GROUPING SETS, CUBE et ROLLUP

Des opĂ©rations de regroupements plus complexes que celles dĂ©crites ci-dessus sont possibles en utilisant la notion d'ensembles de regroupement. Les donnĂ©es sĂ©lectionnĂ©es par les clauses FROM et WHERE sont regroupĂ©es sĂ©parĂ©ment pour chaque ensemble de regroupement indiquĂ©, les agrĂ©gats calculĂ©s pour chaque ensemble de la mĂȘme maniĂšre que pour la clause simple GROUP BY, puis le rĂ©sultat est retournĂ©. Par exemple:

=> SELECT * FROM ventes;
 produit | taille | vendus
-------+------+-------
 Foo   | L    |  10
 Foo   | M    |  20
 Bar   | M    |  15
 Bar   | L    |  5
(4 rows)

=> SELECT produit, taille, sum(vendus) FROM ventes GROUP BY GROUPING SETS ((produit), (taille), ());
 produit | taille | sum
-------+------+-----
 Foo   |      |  30
 Bar   |      |  20
       | L    |  15
       | M    |  35
       |      |  50
(5 rows)
     

Chaque sous-liste de GROUPING SETS peut indiquer 0 ou plusieurs colonnes ou expressions et est interprĂ©tĂ©e de la mĂȘme maniĂšre que si elle Ă©tait directement dans la clause GROUP BY. Un ensemble de regroupement vide signifie que toutes les lignes sont agrĂ©gĂ©es pour former un simple groupe (qui est renvoyĂ© quand bien mĂȘme aucune ligne ne serait sĂ©lectionnĂ©e), comme dĂ©crit ci-dessus dans le cas de fonctions d'agrĂ©gat sans clause GROUP BY.

Les rĂ©fĂ©rences aux colonnes de regroupement ou expressions sont remplacĂ©es par des valeurs NULL dans les lignes renvoyĂ©es pour les ensembles de regroupement oĂč ces colonnes n'apparaissent pas. Pour identifier Ă  quel ensemble de regroupement une ligne en particulier appartient, rĂ©fĂ©rez-vous Ă  Tableau 9.59.

Une notation raccourcie est fournie pour indiquer deux types classiques d'ensembles de regroupement. Une clause sous la forme

ROLLUP ( e1, e2, e3, ... )
     

représente la liste indiquée d'expressions ainsi que l'ensemble des préfixes de la liste, y compris la liste vide. C'est donc équivalent à

GROUPING SETS (
    ( e1, e2, e3, ... ),
    ...
    ( e1, e2 ),
    ( e1 ),
    ( )
)
     

Cette notation est communĂ©ment utilisĂ©e avec des donnĂ©es hiĂ©rarchiques ; par exemple, le total des salaires par dĂ©partement, division et sur l'ensemble de l'entreprise.

Une clause sous la forme

CUBE ( e1, e2, ... )
     

représente la liste indiquée ainsi que l'ensemble des sous-ensembles possibles. De ce fait,

CUBE ( a, b, c )
     

est équivalent à

GROUPING SETS (
    ( a, b, c ),
    ( a, b    ),
    ( a,    c ),
    ( a       ),
    (    b, c ),
    (    b    ),
    (       c ),
    (         )
)
     

Les Ă©lĂ©ments individuels des clauses CUBE ou ROLLUP peuvent ĂȘtre des expressions individuelles, ou des sous-listes d'Ă©lĂ©ments entre parenthĂšses. Dans ce dernier cas, les sous-listes sont traitĂ©es comme simple Ă©lĂ©ment pour la gĂ©nĂ©ration des ensembles de regroupements individuels. Par exemple :

CUBE ( (a, b), (c, d) )
     

est équivalent à

GROUPING SETS (
    ( a, b, c, d ),
    ( a, b       ),
    (       c, d ),
    (            )
)
     

et

ROLLUP ( a, (b, c), d )
     

est équivalent à

GROUPING SETS (
    ( a, b, c, d ),
    ( a, b, c    ),
    ( a          ),
    (            )
)
     

Les Ă©lĂ©ments CUBE et ROLLUP peuvent ĂȘtre utilisĂ©s directement dans la clause GROUP BY, ou imbriquĂ©s Ă  l'intĂ©rieur d'une clause GROUPING SETS. Si une clause GROUPING SETS est imbriquĂ©e dans une autre, l'effet est le mĂȘme que si tous les Ă©lĂ©ments de la clause la plus imbriquĂ©e avaient Ă©tĂ© Ă©crits directement dans la clause de niveau supĂ©rieur.

Si de multiples clauses de regroupement sont indiquĂ©es dans une simple clause GROUP BY, alors la liste finale des ensembles de regroupements est le produit cartĂ©sien des Ă©lĂ©ments individuels. Par exemple :

GROUP BY a, CUBE (b, c), GROUPING SETS ((d), (e))
     

est équivalent à

GROUP BY GROUPING SETS (
    (a, b, c, d), (a, b, c, e),
    (a, b, d),    (a, b, e),
    (a, c, d),    (a, c, e),
    (a, d),       (a, e)
)
     

Note

La syntaxe (a, b) est normalement reconnue dans les expressions comme un constructeur de ligne. À l'intĂ©rieur d'une clause GROUP BY, cette rĂšgle ne s'applique pas au premier niveau d'expressions, et (a, b) est reconnu comme une liste d'expressions, comme dĂ©crit ci-dessus. Si pour une quelconque raison vous avez besoin d'un constructeur de ligne dans une expression de regroupement, utilisez ROW(a, b).

7.2.5. Traitement de fonctions Window

Si la requĂȘte contient une des fonctions Window (voir Section 3.5, Section 9.21 et Section 4.2.8), ces fonctions sont Ă©valuĂ©es aprĂšs que sont effectuĂ©s les regroupements, les agrĂ©gations, les filtrages par HAVING. C'est-Ă -dire que si la requĂȘte comporte des agrĂ©gats, GROUP BY ou HAVING, alors les enregistrements vus par les fonctions Window sont les lignes regroupĂ©es Ă  la place des enregistrements originaux provenant de FROM/WHERE.

Quand des fonctions Window multiples sont utilisĂ©es, toutes les fonctions Window ayant des clauses PARTITION BY et ORDER BY syntaxiquement Ă©quivalentes seront Ă  coup sĂ»r Ă©valuĂ©es en une seule passe sur les donnĂ©es. Par consĂ©quent, elles verront le mĂȘme ordre de tri, mĂȘme si ORDER BY ne dĂ©termine pas de façon unique un tri. Toutefois, aucune garantie n'est faite Ă  propos de l'Ă©valuation de fonctions ayant des spĂ©cifications de PARTITION BY ou ORDER BY diffĂ©rentes. (Dans ces cas, une Ă©tape de tri est gĂ©nĂ©ralement nĂ©cessaire entre les passes d'Ă©valuations de fonctions Window, et le tri ne garantit pas la prĂ©servation de l'ordre des enregistrements que son ORDER BY estime comme identiques.)

À l'heure actuelle, les fonctions Window nĂ©cessitent toujours des donnĂ©es prĂ©triĂ©es, ce qui fait que la sortie de la requĂȘte sera triĂ©e suivant l'une ou l'autre des clauses PARTITION BY/ORDER BY des fonctions Window. Il n'est toutefois pas recommandĂ© de s'en servir. Utilisez une clause ORDER BY au plus haut niveau de la requĂȘte si vous voulez ĂȘtre sĂ»r que vos rĂ©sultats soient triĂ©s d'une certaine façon.