36. PostgreSQL : Requêtes SELECT
Ce chapitre décrit l'instruction SELECT sur une instance MIRAJ en langage PostgreSQL : liste de sélection, FROM et jointures, WHERE, GROUP BY / HAVING, ORDER BY, LIMIT / OFFSET / FETCH FIRST, sous-requêtes, opérations ensemblistes, expressions de table (WITH), fonctions de fenêtrage, verrous de lecture et EXPLAIN. Il décrit le langage PostgreSQL tel que MIRAJ le parle : tous les exemples ont été exécutés sur une instance réelle et les résultats recopiés ; ce qui diffère de PostgreSQL est signalé au fil du texte et rassemblé en 36.15.
Les exemples s'appuient sur la base (le schéma) ventes, remplie une fois pour tout le chapitre :
CREATE SCHEMA ventes;
SET search_path TO ventes;
CREATE TABLE client (
id serial PRIMARY KEY,
nom varchar(50) NOT NULL,
ville varchar(50),
email varchar(80) UNIQUE,
solde numeric(10,2) NOT NULL DEFAULT 0,
cree_le timestamp NOT NULL DEFAULT now()
);
CREATE TABLE article (
id serial PRIMARY KEY,
code varchar(20) NOT NULL UNIQUE,
libelle text NOT NULL,
prix numeric(10,2) NOT NULL CHECK (prix >= 0),
stock integer NOT NULL DEFAULT 0,
actif boolean NOT NULL DEFAULT true
);
CREATE TABLE commande (
id serial PRIMARY KEY,
client_id integer NOT NULL REFERENCES client (id),
date_cmd date NOT NULL DEFAULT current_date,
etat varchar(12) NOT NULL DEFAULT 'ouverte'
);
CREATE TABLE ligne_commande (
commande_id integer NOT NULL REFERENCES commande (id) ON DELETE CASCADE,
article_id integer NOT NULL REFERENCES article (id),
quantite integer NOT NULL CHECK (quantite > 0),
prix_unit numeric(10,2) NOT NULL,
total numeric(12,2) GENERATED ALWAYS AS (quantite * prix_unit) STORED,
PRIMARY KEY (commande_id, article_id)
);
INSERT INTO client (nom, ville, email, solde) VALUES
('Alice', 'Oran', '[email protected]', 100.50),
('Bob', 'Alger', '[email protected]', -20.00),
('Chloé', 'Oran', NULL, 0.00),
('David', 'Constantine', '[email protected]', 250.00),
('Émile', 'Alger', NULL, 75.25),
('Fatima', NULL, '[email protected]', 10.00);
INSERT INTO article (code, libelle, prix, stock, actif) VALUES
('ST-01', 'Stylo bille', 1.20, 500, true),
('CA-01', 'Cahier 96 pages', 3.50, 200, true),
('CL-01', 'Classeur', 6.90, 80, true),
('AG-01', 'Agrafeuse', 14.00, 0, false),
('GO-01', 'Gomme', 0.80, 150, true),
('TR-01', 'Trombones', 2.10, 40, true);
INSERT INTO commande (client_id, date_cmd, etat) VALUES
(1, '2026-09-01', 'livrée'),
(1, '2026-09-15', 'ouverte'),
(2, '2026-09-20', 'livrée'),
(3, '2026-10-02', 'ouverte'),
(5, '2026-10-05', 'annulée');
INSERT INTO ligne_commande (commande_id, article_id, quantite, prix_unit) VALUES
(1, 1, 10, 1.20), (1, 2, 2, 3.50),
(2, 3, 1, 6.90),
(3, 1, 100, 1.10), (3, 5, 20, 0.80),
(4, 2, 5, 3.50), (4, 4, 1, 14.00);Les clients 4 (David) et 6 (Fatima) n'ont pas de commande, la commande 5 n'a pas de ligne et l'article TR-01 n'a jamais été commandé : ces cas servent aux jointures externes.
Les résultats sont présentés à la manière de psql (NULL y est écrit en toutes lettres pour la lisibilité). Les fonctions et opérateurs cités sont décrits au chapitre 37. Fonctions SQL, les types au chapitre 33. Types de données.
36.1 Synopsis#
[WITH [RECURSIVE] nom [(colonne, ...)] AS [[NOT] MATERIALIZED] (requête) [, ...]]
SELECT [ALL | DISTINCT] élément [[AS] alias] [, ...]
[FROM source [, ...]]
[WHERE condition]
[GROUP BY {expr | rang | ROLLUP (...) | CUBE (...) | GROUPING SETS (...)} [, ...]]
[HAVING condition]
[WINDOW nom AS (définition) [, ...]]
[{UNION | INTERSECT | EXCEPT} [ALL | DISTINCT] requête]
[ORDER BY expr [ASC | DESC] [NULLS {FIRST | LAST}] [, ...]]
[LIMIT {n | ALL}] [OFFSET m [ROW | ROWS]]
[FETCH {FIRST | NEXT} [n] {ROW | ROWS} {ONLY | WITH TIES}]
[FOR {UPDATE | NO KEY UPDATE | SHARE | KEY SHARE} [OF table [, ...]] [NOWAIT | SKIP LOCKED]]où source est :
[ONLY] [schéma.]table [*] [[AS] alias [(colonne, ...)]]
| (requête) [AS] alias [(colonne, ...)]
| (VALUES (...), ...) [AS] alias [(colonne, ...)]
| [LATERAL] fonction(arguments) [WITH ORDINALITY] [[AS] alias [(colonne, ...)]]
| LATERAL (requête) [AS] alias [(colonne, ...)]
| source [INNER | LEFT [OUTER] | RIGHT [OUTER] | FULL [OUTER]] JOIN source {ON condition | USING (colonne, ...)}
| source NATURAL [INNER | LEFT | RIGHT | FULL] JOIN source
| source CROSS JOIN sourceUne requête rend son résultat puis l'étiquette SELECT n, où n est le nombre de lignes.
36.2 Liste de sélection#
Chaque élément est une expression, avec un alias facultatif (AS alias, ou l'alias seul) ; * désigne toutes les colonnes, table.* celles d'une table.
SELECT nom, solde AS balance, solde * 1.1 marge FROM client WHERE id <= 2 ORDER BY id; nom | balance | marge
-------+---------+---------
Alice | 100.50 | 110.550
Bob | -20.00 | -22.000SELECT * FROM article WHERE id = 1;
SELECT c.*, 1 AS actif FROM client c WHERE c.id = 1; -- toutes les colonnes de c, plus une
SELECT 1 AS a, 'x' AS b, NULL AS c, true AS d; -- sans FROM : une ligneIdentifiants et alias#
- Un identifiant s'écrit nu ou entre guillemets doubles (
"Nom du client") ; les guillemets permettent les espaces, les signes et les mots réservés. Un alias placé aprèsASpeut être un mot réservé (AS select). - Les noms sont insensibles à la casse, même entre guillemets :
"Client",clientetCLIENTdésignent la même table,"ID"la colonneid(dans PostgreSQL, un nom entre guillemets est sensible à la casse). L'alias écrit est rendu tel quel au client. - Une colonne se qualifie par sa table ou l'alias de celle-ci (
c.nom), une table par son schéma (ventes.client) ;public.clientdésigne la table de la base courante.
SELECT nom AS "Nom du client", solde AS "Solde (DA)" FROM client WHERE id = 1; Nom du client | Solde (DA)
---------------+------------
Alice | 100.50Nom d'une colonne calculée#
Une expression sans alias porte le texte écrit dans la requête, là où PostgreSQL la nomme ?column? ou du nom de la fonction :
SELECT 1 + 1, 'a' || 'b', upper('x'), solde * 2, solde::int FROM client WHERE id = 1; 1 + 1 | 'a' || 'b' | upper('x') | solde * 2 | solde::int
-------+------------+------------+-----------+------------
2 | ab | X | 201.00 | 101De même count(*) se nomme count(*) (PostgreSQL : count) et sum(solde) se nomme sum(solde). Donner un alias à toute colonne calculée que l'application lit par son nom : le code reste alors valable sur les deux serveurs.
Conditions dans la liste#
Une condition placée dans la liste est un booléen, rendu t / f (ou NULL) :
SELECT nom, solde > 0 AS crediteur, ville IS NULL AS sans_ville, ville = 'Oran' AS oran
FROM client ORDER BY id; nom | crediteur | sans_ville | oran
--------+-----------+------------+------
Alice | t | f | t
Bob | f | f | f
Chloé | f | f | t
David | t | f | f
Émile | t | f | f
Fatima | t | t | NULLDISTINCT#
DISTINCT retire les lignes identiques sur tous les éléments de la liste ; deux NULL y sont considérés égaux.
SELECT DISTINCT ville FROM client ORDER BY ville; ville
-------------
Alger
Constantine
Oran
NULLSELECT count(DISTINCT ville) AS villes, count(ville) AS renseignees, count(*) AS clients FROM client;
-- 3 | 5 | 6Comme toute comparaison de texte, DISTINCT distingue la casse et les accents (36.4) : 'a' et 'A' sont deux valeurs.
SELECT DISTINCT x FROM (VALUES ('a'), ('A'), ('a')) AS v (x) ORDER BY x; -- A, aSELECT DISTINCT ON (expr, …) garde la première ligne de chaque groupe de valeurs des expressions, selon l'ORDER BY ; celui-ci doit commencer par ces expressions (dans un ordre quelconque), sinon 42P10 (SELECT DISTINCT ON expressions must match initial ORDER BY expressions). Une expression peut être un rang (DISTINCT ON (1)) ou l'alias d'une colonne de la liste ; le filtre vient après GROUP BY, HAVING et les fonctions de fenêtrage, avant LIMIT. Sans ORDER BY, la ligne gardée dans chaque groupe est quelconque et les lignes sortent dans l'ordre des expressions.
SELECT DISTINCT ON (ville) ville, nom, solde FROM client ORDER BY ville, solde DESC; ville | nom | solde
-------------+--------+--------
Alger | Émile | 75.25
Constantine | David | 250.00
Oran | Alice | 100.50
NULL | Fatima | 10.00VALUES#
Une liste VALUES est une requête à part entière ; ses colonnes se nomment column1, column2… Dans un FROM, un alias de table et de colonnes les renomme :
VALUES (1, 'a'), (2, 'b'); -- colonnes column1, column2
SELECT * FROM (VALUES (1, 'un'), (2, 'deux')) AS v (n, mot) ORDER BY n; n | mot
---+------
1 | un
2 | deux36.3 FROM et jointures#
Types de jointure#
| Syntaxe | Sémantique |
|---|---|
a, b ou a CROSS JOIN b | produit cartésien |
a [INNER] JOIN b ON cond | les paires de lignes qui satisfont cond |
a LEFT [OUTER] JOIN b ON cond | toutes les lignes de a ; colonnes de b à NULL sans correspondance |
a RIGHT [OUTER] JOIN b ON cond | toutes les lignes de b ; colonnes de a à NULL sans correspondance |
a FULL [OUTER] JOIN b ON cond | toutes les lignes des deux côtés |
a JOIN b USING (col, …) | égalité sur les colonnes citées, fusionnées dans le résultat ; aussi avec LEFT, RIGHT, FULL |
a NATURAL [LEFT | RIGHT | FULL] JOIN b | USING sur toutes les colonnes de même nom |
a, LATERAL (requête) x, a {JOIN | LEFT JOIN | CROSS JOIN} LATERAL (requête) x | sous-requête qui cite les colonnes de a, évaluée pour chaque ligne |
SELECT c.nom, co.id AS commande, co.date_cmd
FROM client c JOIN commande co ON co.client_id = c.id
ORDER BY co.id; nom | commande | date_cmd
-------+----------+------------
Alice | 1 | 2026-09-01
Alice | 2 | 2026-09-15
Bob | 3 | 2026-09-20
Chloé | 4 | 2026-10-02
Émile | 5 | 2026-10-05Jointures externes#
SELECT c.nom, co.id AS commande
FROM client c LEFT JOIN commande co ON co.client_id = c.id
ORDER BY c.id, co.id; nom | commande
--------+----------
Alice | 1
Alice | 2
Bob | 3
Chloé | 4
David | NULL
Émile | 5
Fatima | NULL-- compter sans perdre les clients sans commande
SELECT c.nom, count(co.id) AS commandes
FROM client c LEFT OUTER JOIN commande co ON co.client_id = c.id
GROUP BY c.id, c.nom ORDER BY c.id;
-- Alice 2, Bob 1, Chloé 1, David 0, Émile 1, Fatima 0
-- tous les articles, commandés ou non
SELECT a.code, l.commande_id
FROM ligne_commande l RIGHT JOIN article a ON a.id = l.article_id
ORDER BY a.id, l.commande_id;
-- … GO-01 | 3, TR-01 | NULLUne condition sur la table optionnelle n'a pas le même effet dans ON et dans WHERE : dans ON elle restreint les correspondances (les lignes sans correspondance restent, complétées de NULL) ; dans WHERE elle élimine ces lignes, et la jointure externe se comporte comme une jointure interne.
-- clients sans commande livrée : la condition est dans ON, le WHERE cherche l'absence
SELECT c.id, c.nom
FROM client c LEFT JOIN commande co ON co.client_id = c.id AND co.etat = 'livrée'
WHERE co.id IS NULL AND c.id <= 3; -- 3 | Chloé
-- condition dans WHERE : seuls restent les clients qui ont une commande livrée
SELECT c.id, c.nom, co.id AS commande
FROM client c LEFT JOIN commande co ON co.client_id = c.id
WHERE co.etat = 'livrée' ORDER BY c.id; -- 1 | Alice | 1, 2 | Bob | 3FULL JOIN garde les lignes sans correspondance des deux côtés :
SELECT c.nom, co.id AS commande
FROM client c FULL JOIN commande co ON co.client_id = c.id AND co.etat = 'livrée'
ORDER BY c.id NULLS LAST, co.id; nom | commande
--------+----------
Alice | 1
Bob | 3
Chloé | NULL
David | NULL
Émile | NULL
Fatima | NULL
NULL | 2
NULL | 4
NULL | 5Produit cartésien et jointure par WHERE#
SELECT count(*) AS n FROM client CROSS JOIN article; -- 36
SELECT count(*) AS n FROM client, article; -- 36
SELECT c.nom, co.id
FROM client c, commande co
WHERE co.client_id = c.id AND co.etat = 'ouverte'
ORDER BY co.id; -- Alice | 2, Chloé | 4Plusieurs jointures#
SELECT c.nom, a.libelle, l.quantite, l.total
FROM client c
JOIN commande co ON co.client_id = c.id
JOIN ligne_commande l ON l.commande_id = co.id
JOIN article a ON a.id = l.article_id
WHERE co.etat = 'livrée'
ORDER BY c.nom, a.libelle; nom | libelle | quantite | total
-------+-----------------+----------+--------
Alice | Cahier 96 pages | 2 | 7.00
Alice | Stylo bille | 10 | 12.00
Bob | Gomme | 20 | 16.00
Bob | Stylo bille | 100 | 110.00Une colonne présente dans plusieurs tables doit être qualifiée : WHERE id = 1 dans une jointure de client et commande rend 42702 (Column 'id' in where clause is ambiguous).
USING et NATURAL : colonnes fusionnées#
CREATE TABLE remise (client_id integer PRIMARY KEY, taux numeric(4,2), id integer);
INSERT INTO remise VALUES (1, 0.10, 100), (4, 0.05, 101), (9, 0.50, 102);
SELECT * FROM commande JOIN remise USING (client_id) ORDER BY 2; client_id | id | date_cmd | etat | taux | id
-----------+----+------------+---------+------+-----
1 | 1 | 2026-09-01 | livrée | 0.10 | 100
1 | 2 | 2026-09-15 | ouverte | 0.10 | 100SELECT *rend la colonne de jointure une seule fois, en tête, puis les autres colonnes de gauche, puis celles de droite.- La colonne fusionnée se cite sans qualification (
client_id) ; les colonnes homonymes qui ne sont pas de la jointure restent à qualifier (commande.id,remise.id). - Elle vaut la colonne du côté conservé : gauche pour
JOINetLEFT JOIN, droite pourRIGHT JOIN, et la valeur non NULL des deux pourFULL JOIN:
SELECT client_id, commande.id AS commande, taux
FROM commande FULL JOIN remise USING (client_id)
ORDER BY client_id, commande; client_id | commande | taux
-----------+----------+------
1 | 1 | 0.10
1 | 2 | 0.10
2 | 3 | NULL
3 | 4 | NULL
4 | NULL | 0.05
5 | 5 | NULL
9 | NULL | 0.50NATURAL JOIN joint sur toutes les colonnes de même nom :
CREATE TABLE ville_info (ville varchar(50) PRIMARY KEY, wilaya integer);
INSERT INTO ville_info VALUES ('Oran', 31), ('Alger', 16), ('Annaba', 23);
SELECT ville, nom, wilaya FROM client NATURAL LEFT JOIN ville_info ORDER BY id; ville | nom | wilaya
-------------+--------+--------
Oran | Alice | 31
Alger | Bob | 16
Oran | Chloé | 31
Constantine | David | NULL
Alger | Émile | 16
NULL | Fatima | NULLNATURAL RIGHT JOIN et NATURAL FULL JOIN s'emploient de même (Annaba | NULL | 23 apparaît alors). NATURAL CROSS JOIN n'existe pas (42601).
Auto-jointure#
Une table se joint à elle-même sous deux alias :
SELECT a.nom, b.nom AS meme_ville
FROM client a JOIN client b ON a.ville = b.ville AND a.id < b.id
ORDER BY a.id; nom | meme_ville
-------+------------
Alice | Chloé
Bob | ÉmileTable dérivée#
Une sous-requête dans FROM tient lieu de table. Elle doit porter un alias (PostgreSQL 16 le rend facultatif ; MIRAJ répond 42601, syntax error at end of input). Un alias de colonnes peut suivre.
SELECT t.ville, t.total
FROM (SELECT ville, sum(solde) AS total FROM client GROUP BY ville) AS t (ville, total)
WHERE total > 100
ORDER BY 1; ville | total
-------------+--------
Constantine | 250.00
Oran | 100.50Alias de colonnes#
Un alias de colonnes AS x (a, b, …) s'écrit après toute source du FROM : table nommée, vue, expression WITH, table dérivée (même dont la liste est *), liste VALUES, fonction. Comme dans PostgreSQL, les premières colonnes sont renommées et les suivantes gardent leur nom :
SELECT * FROM article AS q (x, y) WHERE x = 1; x | y | libelle | prix | stock | actif
---+-------+-------------+------+-------+-------
1 | ST-01 | Stylo bille | 1.20 | 500 | tSELECT * FROM (SELECT * FROM remise) AS r (a, b) ORDER BY a; -- colonnes a, b, id
SELECT * FROM remise AS r (a, b, c, d);
-- ERREUR 42P10 : table "r" has 3 columns available but 4 columns specifiedLATERAL#
Une sous-requête précédée de LATERAL peut citer les colonnes des tables écrites avant elle ; elle est évaluée pour chaque ligne. Usage typique : « la dernière commande de chaque client ».
SELECT c.nom, d.id, d.date_cmd
FROM client c,
LATERAL (SELECT id, date_cmd FROM commande co
WHERE co.client_id = c.id ORDER BY date_cmd DESC LIMIT 1) d
ORDER BY c.id; nom | id | date_cmd
-------+----+------------
Alice | 2 | 2026-09-15
Bob | 3 | 2026-09-20
Chloé | 4 | 2026-10-02
Émile | 5 | 2026-10-05Avec LEFT JOIN LATERAL (…) d ON true, les clients sans commande restent (David | NULL, Fatima | NULL). JOIN LATERAL … ON true et CROSS JOIN LATERAL sont équivalents à la virgule.
Limites : LATERAL ne précède qu'une sous-requête ; il n'est admis ni avec RIGHT ou FULL JOIN, ni avec USING ou NATURAL (0A000). La sous-requête étant replanifiée pour chaque ligne, réserver LATERAL à des tables de gauche de taille modérée ; pour un « premier par groupe » sur une grande table, préférer row_number() (36.10).
generate_series#
generate_series(début, fin [, pas]) rend une suite d'entiers, ou de dates et d'horodatages avec un pas interval :
SELECT n, n * n AS carre FROM generate_series(1, 4) AS s (n); n | carre
---+-------
1 | 1
2 | 4
3 | 9
4 | 16SELECT g FROM generate_series(10, 1, -3) AS g; -- 10, 7, 4, 1
SELECT date '2026-10-01' + n AS jour FROM generate_series(0, 2) AS s (n); -- trois dates
SELECT * FROM generate_series(date '2026-10-01', date '2026-10-03', interval '1 day') AS j;
-- 2026-10-01, 2026-10-02, 2026-10-03
SELECT * FROM generate_series(timestamp '2026-10-01 00:00', timestamp '2026-10-01 02:00',
interval '1 hour') AS j;
-- 2026-10-01 00:00:00, 2026-10-01 01:00:00, 2026-10-01 02:00:00Une suite n'est pas bornée par cte_max_recursion_depth (la limite des requêtes récursives, voir 36.9) : generate_series(1, 2000) rend ses 2 000 valeurs. Sa propre limite est de 10 000 000 de valeurs (54000 : generate_series cannot return more than 10000000 rows). Un pas nul rend 22023 (step size cannot equal zero). Les bornes sont des constantes : elles ne citent pas une autre table du FROM (42703).
Fonctions ensemblistes dans la liste du SELECT#
generate_series, unnest, regexp_matches, regexp_split_to_table, json[b]_array_elements[_text] et json[b]_object_keys s'écrivent aussi dans la liste d'un SELECT, comme dans PostgreSQL 10 et suivants : la requête rend une ligne par valeur, chaque ligne source étant répétée. La colonne porte le nom de la fonction, ou son alias.
SELECT generate_series(1, 3); -- colonne generate_series : 1, 2, 3
SELECT generate_series(1, 3) * 10 AS dizaine; -- 10, 20, 30
SELECT nom, generate_series(1, 2) AS tour FROM client WHERE id <= 2 ORDER BY nom, tour; nom | tour
-------+------
Alice | 1
Alice | 2
Bob | 1
Bob | 2Plusieurs fonctions d'une même liste avancent ensemble : la plus longue fixe le nombre de lignes, les autres sont complétées par NULL.
SELECT generate_series(1, 3) AS a, generate_series(1, 2) AS b; a | b
---+------
1 | 1
2 | 2
3 | NULLLes arguments de generate_series y sont des constantes (une colonne : 0A000, generate_series citing a column in the select list) ; unnest(ARRAY[…]) peut citer les colonnes de la ligne (SELECT nom, unnest(ARRAY[solde, solde * 2]) AS v FROM client). La forme vaut aussi dans INSERT … SELECT, une union ou une sous-requête ; elle n'est pas admise dans une liste regroupée, agrégée ou fenêtrée (0A000).
Fonction dans un FROM#
Une fonction — stockée ou fournie par le serveur — peut être appelée dans un FROM. Une fonction scalaire y rend une ligne d'une colonne, comme dans PostgreSQL ; c'est la forme qu'envoie cursor.callproc de psycopg2.
CREATE FUNCTION total_client(cid integer) RETURNS numeric(12,2) AS $$
SELECT coalesce(sum(l.total), 0)
FROM commande co JOIN ligne_commande l ON l.commande_id = co.id
WHERE co.client_id = cid
$$ LANGUAGE sql;
SELECT * FROM total_client(3); -- colonne « total_client » : 31.50
SELECT * FROM total_client(3) AS t; -- colonne « t »
SELECT t.montant FROM total_client(3) AS t (montant); -- colonne « montant »
SELECT c.nom, t FROM client c, total_client(c.id) AS t ORDER BY c.nom; nom | t
--------+--------
Alice | 25.90
Bob | 126.00
Chloé | 31.50
David | 0.00
Fatima | 0.00
Émile | 0.00La colonne porte le nom de l'alias, sinon celui de la fonction. Une fonction scalaire voit les tables qui la précèdent dans le FROM (dernier exemple), sans qu'il faille écrire LATERAL ; le mot est admis devant elle, en première position aussi (FROM LATERAL f(…)). Une liste de définitions de colonnes (AS t (a int, b text)) rend 0A000.
Fonctions ensemblistes stockées. Une fonction déclarée RETURNS TABLE (…), RETURNS SETOF type, RETURNS SETOF table (ou à paramètres OUT avec SETOF record) rend des lignes, lues comme celles d'une table (voir 40. Routines et déclencheurs pour RETURN NEXT et RETURN QUERY) :
CREATE FUNCTION commandes_de(cid integer) RETURNS TABLE (num integer, jour date, etat varchar) AS $$
SELECT id, date_cmd, etat FROM commande WHERE client_id = cid ORDER BY id
$$ LANGUAGE sql;
SELECT * FROM commandes_de(1); num | jour | etat
-----+------------+---------
1 | 2026-09-01 | livrée
2 | 2026-09-15 | ouverteCREATE FUNCTION articles_actifs() RETURNS SETOF article AS $$
SELECT * FROM article WHERE actif ORDER BY id
$$ LANGUAGE sql;
SELECT code, prix FROM articles_actifs() WHERE prix > 2; -- CA-01 3.50, CL-01 6.90, TR-01 2.10Une telle fonction s'appelle dans un FROM, avec des arguments constants : appelée dans la liste d'un SELECT (0A000 : set-valued function called in context that cannot accept a set), avec des arguments qui citent une autre table du FROM, ou suivie de WITH ORDINALITY, elle est refusée (0A000).
Fonctions du serveur qui rendent plusieurs lignes. Elles admettent WITH ORDINALITY, qui ajoute le rang de chaque ligne (colonne ordinality, ou le dernier nom de l'alias) :
| Fonction | Colonnes |
|---|---|
generate_series(début, fin [, pas]) | la valeur |
unnest(tableau) | un élément par ligne |
regexp_split_to_table(texte, motif [, options]) | un morceau par ligne |
regexp_matches(texte, motif [, options]) | un tableau des groupes par correspondance |
json_each, jsonb_each, json_each_text, jsonb_each_text | key, value |
json[b]_array_elements, json[b]_array_elements_text | value |
json[b]_object_keys | la clé |
SELECT * FROM unnest(ARRAY['rouge', 'vert']) WITH ORDINALITY AS c (couleur, rang);
-- rouge | 1 ; vert | 2
SELECT c.nom, v FROM client c, unnest(ARRAY[c.solde, c.solde * 2]) AS v
WHERE c.id <= 2 ORDER BY c.nom, v;
-- Alice 100.50, Alice 201.00, Bob -40.00, Bob -20.00
SELECT jour, n FROM generate_series(1, 3) WITH ORDINALITY AS g (jour, n);
SELECT * FROM regexp_split_to_table('a,b;c', '[,;]') WITH ORDINALITY AS t (morceau, n);
-- a | 1 ; b | 2 ; c | 3
SELECT * FROM jsonb_each('{"b": 2, "a": 1}') AS e; -- a | 1 ; b | 2 (clés triées)
SELECT * FROM json_each_text('{"b": 2, "a": "x"}') AS e (k, v); -- b | 2 ; a | x
SELECT c.nom, e.key FROM client c, jsonb_each(jsonb_build_object('id', c.id)) AS e
WHERE c.id = 1; -- Alice | idLes fonctions JSON sont latérales sur les tables qui les précèdent (LATERAL écrit ou non, LEFT JOIN LATERAL … ON true compris) ; un document NULL ne rend aucune ligne.
unnest déplie un tableau construit ARRAY[a, b, …], dont les éléments peuvent citer les colonnes des tables qui précèdent (converti ou non : ARRAY[…]::int[] convertit chaque élément ; ARRAY[]::int[] ne rend aucune ligne), et un tableau écrit en texte {a,b} : paramètre lié (unnest($1), unnest($1::int[]), la forme qu'envoient les pilotes pour une liste liée, en format texte) ou littéral (unnest('{1,2,NULL}'::int[])). Sans ::type[], les éléments d'un texte {…} restent du texte ; un élément NULL rend NULL, un tableau NULL ou {} aucune ligne. Le tableau d'une colonne (unnest(t.col)), ARRAY[] sans conversion et un paramètre tableau envoyé en binaire rendent 0A000.
FROM ONLY table et FROM table * sont acceptés : ONLY et * sont sans effet, MIRAJ n'ayant pas d'héritage de tables.
36.4 WHERE#
WHERE filtre les lignes produites par FROM, avant tout regroupement.
| Forme | Exemple |
|---|---|
comparaisons =, <>, !=, <, <=, >, >= | solde >= 10 AND solde <> 75.25 |
AND, OR, NOT | solde != 0 AND NOT (ville = 'Oran') |
[NOT] BETWEEN a AND b (bornes comprises) | solde BETWEEN 0 AND 100 |
[NOT] IN (liste) | ville IN ('Oran', 'Constantine') |
= ANY (ARRAY[…]), <> ALL (ARRAY[…]), > ALL (ARRAY[…]) | ville = ANY (ARRAY['Oran', 'Constantine']) |
IS [NOT] NULL | email IS NOT NULL |
IS [NOT] DISTINCT FROM | ville IS DISTINCT FROM 'Oran' |
[NOT] LIKE, [NOT] ILIKE, ESCAPE | nom LIKE 'A%' |
~, ~*, !~, !~* (expressions rationnelles) | nom ~ '^[a-c]' |
booléen seul, IS [NOT] TRUE, IS [NOT] FALSE | WHERE actif, WHERE NOT actif |
| comparaison de lignes | (ville, nom) IN (('Oran', 'Alice'), ('Alger', 'Bob')), (id, solde) > (3, 100) |
| sous-requêtes (36.7) | id IN (SELECT …), EXISTS (SELECT …) |
SELECT nom, solde FROM client WHERE solde >= 10 AND solde <> 75.25 ORDER BY id;
-- Alice 100.50, David 250.00, Fatima 10.00
SELECT libelle FROM article WHERE actif IS TRUE AND stock > 100 ORDER BY id;
-- Stylo bille, Cahier 96 pages, Gomme
SELECT id FROM commande WHERE date_cmd BETWEEN date '2026-09-01' AND date '2026-09-30' ORDER BY id; -- 1, 2, 3
SELECT id FROM commande WHERE date_cmd > date '2026-10-05' - interval '1 month' ORDER BY id; -- 2, 3, 4, 5
SELECT id FROM commande WHERE extract(month FROM date_cmd) = 9 ORDER BY id; -- 1, 2, 3AND est prioritaire sur OR : solde > 0 OR ville = 'Oran' AND email IS NULL se lit solde > 0 OR (ville = 'Oran' AND email IS NULL). Un alias de la liste de sélection n'est pas visible dans WHERE (42703), comme dans PostgreSQL.
NULL et logique à trois valeurs#
Une comparaison avec NULL ne vaut ni vrai ni faux mais NULL, et WHERE ne garde que les lignes dont la condition est vraie :
SELECT NULL = NULL AS a, NULL IS NULL AS b, 1 = NULL AS c, NULL AND false AS d,
NULL OR true AS e, NOT NULL AS f, 1 IN (2, NULL) AS g, 1 NOT IN (2, NULL) AS h; a | b | c | d | e | f | g | h
------+---+------+---+---+------+------+------
NULL | t | NULL | f | t | NULL | NULL | NULLConséquences pratiques :
SELECT nom FROM client WHERE ville <> 'Oran' ORDER BY id; -- Bob, David, Émile
SELECT nom FROM client WHERE ville IS DISTINCT FROM 'Oran' ORDER BY id; -- Bob, David, Émile, Fatima
SELECT nom FROM client WHERE ville IS NULL; -- FatimaFatima, dont la ville est NULL, n'est rendue que par IS DISTINCT FROM, qui traite NULL comme une valeur. x NOT IN (sous-requête) ne rend rien dès que la sous-requête contient un NULL (36.7).
Texte : casse, accents, LIKE et expressions rationnelles#
Les textes sont comparés octet par octet (collation « C », comme PostgreSQL en C.UTF-8) : =, <, IN, LIKE, ~, DISTINCT, GROUP BY, ORDER BY, les jointures et les contraintes d'unicité distinguent la casse et les accents. Les espaces de fin sont ignorés par =. ILIKE et ~* ignorent la casse.
SELECT 'a' = 'A' AS casse, 'e' = 'é' AS accent, 'a' = 'a ' AS espace, 'a' < 'B' AS ordre; casse | accent | espace | ordre
-------+--------+--------+-------
f | f | t | fSELECT nom FROM client WHERE nom = 'emile'; -- aucune ligne
SELECT nom FROM client WHERE nom = 'Émile'; -- Émile| Opérateur | Sur MIRAJ |
|---|---|
LIKE, NOT LIKE (~~, !~~) | % : toute suite de caractères, _ : un caractère ; sensible à la casse et aux accents |
ILIKE, NOT ILIKE (~~*, !~~*) | comme LIKE, casse et accents ignorés |
~, !~ | expression rationnelle, sensible à la casse et aux accents |
~*, !~* | comme ~, casse ignorée (les accents restent distingués) |
SELECT nom FROM client WHERE nom LIKE 'A%'; -- Alice
SELECT nom FROM client WHERE nom LIKE 'a%'; -- aucune ligne
SELECT nom FROM client WHERE nom LIKE '_o_'; -- Bob
SELECT nom FROM client WHERE nom ILIKE 'ALI%'; -- Alice
SELECT nom FROM client WHERE nom ILIKE 'emile'; -- Émile
SELECT nom FROM client WHERE nom ~ '^[A-C]' ORDER BY id; -- Alice, Bob, Chloé
SELECT nom FROM client WHERE nom ~* '^[a-c]' ORDER BY id; -- Alice, Bob, Chloé
SELECT nom FROM client WHERE nom ~* '^e'; -- aucune ligne
SELECT nom FROM client WHERE nom !~ 'e$' ORDER BY id; -- Bob, Chloé, David, Fatima
SELECT nom FROM client WHERE nom !~~* 'b%' ORDER BY id; -- Alice, Chloé, David, Émile, FatimaLe caractère d'échappement de LIKE est la barre oblique, ou celui donné par ESCAPE :
CREATE TABLE fichier (nom text);
INSERT INTO fichier VALUES ('rapport_2026.pdf'), ('rapport-2026.pdf'), ('100%.txt'), ('Rapport_final.PDF');
SELECT nom FROM fichier WHERE nom ILIKE 'rapport\_%' ORDER BY nom; -- Rapport_final.PDF, rapport_2026.pdf
SELECT nom FROM fichier WHERE nom LIKE 'rapport#_%' ESCAPE '#' ORDER BY nom; -- rapport_2026.pdf
SELECT nom FROM fichier WHERE nom LIKE '100\%%'; -- 100%.txt
SELECT nom FROM fichier WHERE nom ILIKE '%.pdf' ORDER BY nom; -- 3 lignes, dont Rapport_final.PDF
SELECT nom FROM fichier WHERE nom LIKE '%.pdf' ORDER BY nom; -- rapport-2026.pdf, rapport_2026.pdf
SELECT nom FROM fichier WHERE nom ~ '^rapport' ORDER BY nom; -- rapport-2026.pdf, rapport_2026.pdfCollations. COLLATE "C" (comme "POSIX" et ucs_basic, qualifiés ou non de pg_catalog) compare octet par octet, comme le défaut. COLLATE "default" et les autres collations de PostgreSQL ("fr_FR"…) sont acceptées et sans effet. Pour une comparaison qui ignore la casse et les accents, appliquer la collation utf8mb4_general_ci de MIRAJ, ou comparer lower(…) :
SELECT nom FROM client WHERE nom COLLATE "C" = 'alice'; -- aucune ligne
SELECT nom FROM client WHERE nom = 'alice' COLLATE utf8mb4_general_ci; -- Alice
SELECT nom FROM client WHERE lower(nom) = 'alice'; -- AliceComparaison d'un nombre et d'un texte#
Un texte comparé à une colonne numérique est lu comme un nombre (WHERE id = '1', WHERE solde = '100.5'). Un texte qui n'est pas un nombre est une erreur, comme dans PostgreSQL :
SELECT nom FROM client WHERE solde = 'abc';
-- ERREUR 22P02 : invalid input syntax for type numeric: "abc"36.5 GROUP BY et HAVING#
GROUP BY regroupe les lignes qui ont les mêmes valeurs pour les expressions citées ; les agrégats calculent une valeur par groupe. HAVING filtre les groupes, après le calcul.
SELECT ville, count(*) AS n, sum(solde) AS total, avg(solde) AS moyenne,
min(solde) AS mini, max(solde) AS maxi
FROM client GROUP BY ville ORDER BY ville; ville | n | total | moyenne | mini | maxi
-------------+---+--------+----------------------+--------+--------
Alger | 2 | 55.25 | 27.6250000000000000 | -20.00 | 75.25
Constantine | 1 | 250.00 | 250.0000000000000000 | 250.00 | 250.00
Oran | 2 | 100.50 | 50.2500000000000000 | 0.00 | 100.50
NULL | 1 | 10.00 | 10.0000000000000000 | 10.00 | 10.00Les NULL forment un groupe. avg d'entiers ou de décimaux est un numeric à seize décimales (round(avg(prix), 2) pour l'affichage) ; sum d'un integer est un bigint.
SELECT ville, sum(solde) AS total FROM client
WHERE solde > 0 -- filtre les lignes, avant le regroupement
GROUP BY ville
HAVING sum(solde) > 50 -- filtre les groupes
ORDER BY total DESC; ville | total
-------------+--------
Constantine | 250.00
Oran | 100.50
Alger | 75.25-- chiffre d'affaires par client
SELECT c.nom, count(DISTINCT co.id) AS commandes, sum(l.total) AS montant
FROM client c
JOIN commande co ON co.client_id = c.id
JOIN ligne_commande l ON l.commande_id = co.id
GROUP BY c.id, c.nom
ORDER BY montant DESC; nom | commandes | montant
-------+-----------+---------
Bob | 1 | 126.00
Chloé | 1 | 31.50
Alice | 2 | 25.90Ce que GROUP BY accepte#
Une expression, un alias de la liste de sélection ou le rang d'un élément de la liste :
SELECT extract(month FROM date_cmd) AS mois, count(*) AS n
FROM commande GROUP BY extract(month FROM date_cmd) ORDER BY mois; -- 9 | 3, 10 | 2
SELECT extract(month FROM date_cmd) AS mois, count(*) AS n
FROM commande GROUP BY mois ORDER BY mois; -- alias
SELECT to_char(date_cmd, 'YYYY-MM') AS mois, count(*) AS n
FROM commande GROUP BY 1 ORDER BY 1; -- rang : 2026-09 | 3, 2026-10 | 2Colonne hors GROUP BY#
Toute colonne de la liste qui n'est pas dans un agrégat doit figurer dans GROUP BY :
SELECT ville, nom FROM client GROUP BY ville;
-- ERREUR 42803 : 'nom' isn't in GROUP BYLa clé primaire dispense de citer les autres colonnes de sa table (dépendance fonctionnelle, comme dans PostgreSQL) : quand GROUP BY porte toutes les colonnes de la clé primaire d'une table, ses autres colonnes sont admises dans la liste, dans HAVING et dans ORDER BY, table.* compris.
SELECT c.id, c.nom, c.ville, count(o.id) AS commandes
FROM client c LEFT JOIN commande o ON o.client_id = c.id
GROUP BY c.id
ORDER BY c.nom;Cela ne vaut que pour une table lue directement et sa clé primaire entière : les colonnes d'une autre table de la jointure, une clé primaire composée citée en partie, une contrainte UNIQUE, une vue ou une table dérivée restent soumises à la règle (42803).
Sans GROUP BY#
Un agrégat sans GROUP BY rend une seule ligne pour toute la table, même vide ; HAVING peut alors l'écarter :
SELECT count(*) AS c, sum(solde) AS s, avg(solde) AS a FROM client WHERE id > 100; -- 0 | NULL | NULL
SELECT count(*) AS n FROM client HAVING count(*) > 100; -- aucune ligneAgrégats#
| Agrégat | Rôle |
|---|---|
count(*), count(expr), count(DISTINCT expr) | lignes, valeurs non NULL, valeurs distinctes |
sum, avg, min, max | sum(integer) : bigint ; avg : numeric |
stddev, variance, stddev_pop, var_pop, stddev_samp, var_samp | écart-type et variance |
string_agg(expr, séparateur [ORDER BY …]) | concaténation |
array_agg(expr [ORDER BY …]), array_agg(DISTINCT expr) | tableau des valeurs ({1,2,NULL}), NULL sur zéro ligne |
bool_and, bool_or, every | conjonction, disjonction |
json_agg, jsonb_agg, json_object_agg, jsonb_object_agg | documents JSON |
percentile_cont(f), percentile_disc(f), mode() suivis de WITHIN GROUP (ORDER BY …) | percentile continu, discret, valeur la plus fréquente (22023 hors de 0..1) |
rank(v), dense_rank(v), percent_rank(v), cume_dist(v) suivis de WITHIN GROUP (ORDER BY …) | rang qu'aurait la valeur v |
corr, covar_pop, covar_samp, regr_slope, regr_intercept, regr_r2, regr_count, regr_avgx, regr_avgy, regr_sxx, regr_syy, regr_sxy | statistiques sur les paires non NULL |
SELECT ville, string_agg(nom, ', ' ORDER BY nom) AS noms FROM client GROUP BY ville ORDER BY ville; ville | noms
-------------+--------------
Alger | Bob, Émile
Constantine | David
Oran | Alice, Chloé
NULL | FatimaLa clause FILTER (WHERE …) restreint les lignes vues par un agrégat :
SELECT ville,
count(*) FILTER (WHERE solde > 0) AS crediteurs,
sum(solde) FILTER (WHERE solde > 50) AS gros
FROM client GROUP BY ville ORDER BY ville; ville | crediteurs | gros
-------------+------------+--------
Alger | 1 | 75.25
Constantine | 1 | 250.00
Oran | 1 | 100.50
NULL | 1 | NULLSELECT ville, array_agg(id ORDER BY id) AS ids FROM client GROUP BY ville ORDER BY ville; ville | ids
-------------+-------
Alger | {2,5}
Constantine | {4}
Oran | {1,3}
NULL | {6}SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY solde) AS mediane,
percentile_disc(0.5) WITHIN GROUP (ORDER BY solde) AS mediane_d,
rank(100) WITHIN GROUP (ORDER BY solde DESC) AS rang_de_100
FROM client; mediane | mediane_d | rang_de_100
---------+-----------+-------------
42.625 | 10.00 | 3Le détail des agrégats est au chapitre 37. Fonctions SQL.
ROLLUP, CUBE, GROUPING SETS#
SELECT ville, etat, count(*) AS n
FROM client c JOIN commande co ON co.client_id = c.id
GROUP BY ROLLUP (ville, etat)
ORDER BY ville NULLS LAST, etat NULLS LAST; ville | etat | n
-------+---------+---
Alger | annulée | 1
Alger | livrée | 1
Alger | NULL | 2
Oran | livrée | 1
Oran | ouverte | 2
Oran | NULL | 3
NULL | NULL | 5ROLLUP (a, b) ajoute un sous-total par a et un total général ; CUBE (a, b) ajoute aussi les sous-totaux par b (dix lignes ici) ; GROUPING SETS ((ville), (etat), ()) ne rend que les regroupements demandés. La fonction grouping(col) vaut 1 sur une ligne de total où col est agrégée, ce qui la distingue d'un vrai NULL :
SELECT ville, count(*) AS n, grouping(ville) AS g FROM client GROUP BY ROLLUP (ville) ORDER BY g, ville; ville | n | g
-------------+---+---
Alger | 2 | 0
Constantine | 1 | 0
Oran | 2 | 0
NULL | 1 | 0
NULL | 6 | 136.6 ORDER BY, LIMIT, OFFSET, FETCH FIRST#
ORDER BY#
Sans ORDER BY, l'ordre des lignes n'est pas garanti. Une clé de tri est une expression, un alias de la liste ou le rang d'un élément ; ASC (défaut) ou DESC.
SELECT nom, solde FROM client ORDER BY solde DESC, nom; -- David, Alice, Émile, Fatima, Chloé, Bob
SELECT nom, solde * 2 AS double FROM client ORDER BY double DESC LIMIT 2; -- alias
SELECT nom, solde FROM client ORDER BY 2 DESC LIMIT 2; -- rang
SELECT nom FROM client ORDER BY length(nom), nom; -- expression
SELECT ville, count(*) AS n FROM client GROUP BY ville ORDER BY count(*) DESC, ville; -- agrégatPlace des NULL. Comme dans PostgreSQL, les NULL sont derniers en ordre croissant et premiers en ordre décroissant ; NULLS FIRST et NULLS LAST imposent leur place.
SELECT nom, ville FROM client ORDER BY ville, nom; nom | ville
--------+-------------
Bob | Alger
Émile | Alger
David | Constantine
Alice | Oran
Chloé | Oran
Fatima | NULLSELECT nom, ville FROM client ORDER BY ville NULLS FIRST, nom; -- Fatima en tête
SELECT nom, ville FROM client ORDER BY ville DESC NULLS LAST, nom; -- Alice, Chloé, David, Bob, Émile, FatimaOrdre des textes. Le tri suit l'ordre des octets (collation « C », comme PostgreSQL en C.UTF-8) : majuscules avant minuscules, lettres accentuées après les lettres non accentuées.
SELECT nom FROM client ORDER BY nom; -- Alice, Bob, Chloé, David, Fatima, Émile
SELECT x FROM (VALUES ('b'), ('B'), ('a'), ('A'), ('é'), ('e')) AS v (x) ORDER BY x;
-- A, B, a, b, e, éPour un ordre qui ignore la casse et les accents, trier par x COLLATE utf8mb4_general_ci (collation de MIRAJ), puis par x pour départager : … ORDER BY x COLLATE utf8mb4_general_ci, x rend A, a, B, b, e, é.
LIMIT et OFFSET#
SELECT id FROM client ORDER BY id LIMIT 2; -- 1, 2
SELECT id FROM client ORDER BY id LIMIT 2 OFFSET 3; -- 4, 5
SELECT id FROM client ORDER BY id OFFSET 4; -- 5, 6 (OFFSET seul)
SELECT id FROM client ORDER BY id OFFSET 1 LIMIT 2; -- 2, 3 (OFFSET avant LIMIT)
SELECT id FROM client ORDER BY id LIMIT ALL OFFSET 5; -- 6
SELECT id FROM client ORDER BY id LIMIT 0; -- aucune ligneLIMIT ALL et LIMIT NULL ne limitent pas. La valeur est un entier écrit ou un paramètre, pas une expression (LIMIT 1 + 1 : 42601) :
PREPARE page (int, int) AS SELECT id FROM client ORDER BY id LIMIT $1 OFFSET $2;
EXECUTE page(2, 1); -- 2, 3Pour une pagination stable, trier sur une clé unique (ORDER BY date_cmd, id).
FETCH FIRST#
Forme du standard, équivalente à LIMIT :
SELECT id FROM client ORDER BY id FETCH FIRST 2 ROWS ONLY; -- 1, 2
SELECT id FROM client ORDER BY id OFFSET 2 ROWS FETCH NEXT 2 ROWS ONLY; -- 3, 4
SELECT id FROM client ORDER BY id FETCH FIRST ROW ONLY; -- 1WITH TIES à la place de ONLY rend aussi les lignes ex æquo de la dernière, selon l'ORDER BY (obligatoire : sans lui, 42601 FETCH ... WITH TIES requires ORDER BY clause to be present) :
SELECT ville, nom FROM client ORDER BY ville FETCH FIRST 1 ROWS WITH TIES; ville | nom
-------+-------
Alger | Bob
Alger | Émile36.7 Sous-requêtes#
| Forme | Rôle |
|---|---|
(SELECT …) à la place d'une valeur | sous-requête scalaire : une colonne, une ligne au plus ; NULL sans ligne |
x [NOT] IN (SELECT …), (a, b) IN (SELECT …, …) | appartenance |
[NOT] EXISTS (SELECT …) | existence d'au moins une ligne |
x op ANY (SELECT …), x op SOME (SELECT …), x op ALL (SELECT …) | comparaison à l'une ou à toutes les valeurs |
(SELECT …) alias dans FROM | table dérivée (36.3) |
Une sous-requête est corrélée quand elle cite une colonne de la requête qui l'entoure ; elle est alors évaluée pour chaque ligne.
-- scalaire, non corrélée
SELECT nom, solde FROM client WHERE solde > (SELECT avg(solde) FROM client) ORDER BY id;
-- Alice 100.50, David 250.00, Émile 75.25
-- scalaire corrélée, dans la liste
SELECT nom, (SELECT count(*) FROM commande co WHERE co.client_id = c.id) AS commandes,
(SELECT max(date_cmd) FROM commande co WHERE co.client_id = c.id) AS derniere
FROM client c ORDER BY c.id; nom | commandes | derniere
--------+-----------+------------
Alice | 2 | 2026-09-15
Bob | 1 | 2026-09-20
Chloé | 1 | 2026-10-02
David | 0 | NULL
Émile | 1 | 2026-10-05
Fatima | 0 | NULLSELECT nom FROM client WHERE id IN (SELECT client_id FROM commande WHERE etat = 'livrée') ORDER BY id;
-- Alice, Bob
SELECT nom FROM client c
WHERE EXISTS (SELECT 1 FROM commande co WHERE co.client_id = c.id AND co.etat = 'ouverte') ORDER BY id;
-- Alice, Chloé
SELECT nom FROM client c
WHERE NOT EXISTS (SELECT 1 FROM commande co WHERE co.client_id = c.id) ORDER BY id;
-- David, Fatima
SELECT libelle, prix FROM article WHERE prix >= ALL (SELECT prix FROM article); -- Agrafeuse 14.00
SELECT libelle, prix FROM article
WHERE prix > ANY (SELECT prix_unit FROM ligne_commande WHERE commande_id = 4) ORDER BY id;
-- Classeur 6.90, Agrafeuse 14.00
SELECT nom FROM client WHERE (id, ville) IN (SELECT client_id, 'Oran' FROM commande) ORDER BY id;
-- Alice, Chloé
-- la plus grosse ligne de chaque article
SELECT a.libelle, l.quantite
FROM article a JOIN ligne_commande l ON l.article_id = a.id
WHERE l.quantite = (SELECT max(l2.quantite) FROM ligne_commande l2 WHERE l2.article_id = a.id)
ORDER BY a.id;EXISTS placé dans la liste est un booléen (t / f). Une sous-requête peut porter ORDER BY et LIMIT (id IN (SELECT client_id FROM commande ORDER BY date_cmd DESC LIMIT 2)).
NOT IN et NULL. Si la sous-requête rend un NULL, x NOT IN (…) n'est jamais vrai et la requête ne rend aucune ligne (comme dans PostgreSQL). Préférer NOT EXISTS, qui n'a pas ce piège.
| Requête | Réponse |
|---|---|
sous-requête scalaire qui rend plusieurs lignes : SELECT (SELECT nom FROM client) | 21000 : Subquery returns more than 1 row |
sous-requête scalaire à plusieurs colonnes : SELECT (SELECT nom, ville FROM client WHERE id = 1) | 42601 : Operand should contain 1 column(s) |
ligne comparée à une sous-requête par = : (id, ville) = (SELECT client_id, 'Oran' FROM …) | 42601 : Operand should contain 1 column(s) ; écrire IN |
36.8 Opérations ensemblistes : UNION, INTERSECT, EXCEPT#
requête {UNION | INTERSECT | EXCEPT} [ALL | DISTINCT] requête [...]
[ORDER BY ...] [LIMIT n] [OFFSET m]| Opérateur | Résultat |
|---|---|
UNION | lignes de l'une ou l'autre requête, sans doublon |
UNION ALL | toutes les lignes, doublons gardés |
INTERSECT | lignes présentes des deux côtés, sans doublon |
INTERSECT ALL | lignes présentes des deux côtés, autant de fois que du côté qui en a le moins |
EXCEPT | lignes de gauche absentes de droite, sans doublon |
EXCEPT ALL | lignes de gauche, moins une occurrence par ligne égale de droite |
Avec client.ville (Oran ×2, Alger ×2, Constantine, NULL) et ville_info.ville (Oran, Alger, Annaba) :
SELECT ville FROM client UNION SELECT ville FROM ville_info ORDER BY ville;
-- Alger, Annaba, Constantine, Oran, NULL
SELECT ville FROM client INTERSECT SELECT ville FROM ville_info ORDER BY ville;
-- Alger, Oran
SELECT ville FROM client EXCEPT SELECT ville FROM ville_info ORDER BY ville;
-- Constantine, NULL
SELECT ville FROM client EXCEPT ALL SELECT ville FROM ville_info ORDER BY ville;
-- Alger, Constantine, Oran, NULL- Les requêtes doivent rendre le même nombre de colonnes (
42601:The used SELECT statements have a different number of columns) ; les noms sont ceux de la première. - Deux NULL sont égaux pour l'élimination des doublons ; les textes y sont comparés octet par octet (
SELECT nom FROM client UNION SELECT 'alice'rendAliceetalice). ORDER BY,LIMITetOFFSETécrits à la fin portent sur le résultat entier ; la clé de tri est un nom de colonne, un rang ou une expression sur ces colonnes.- Priorité :
INTERSECTest évalué avantUNIONetEXCEPT, comme dans PostgreSQL ; les parenthèses imposent un autre ordre, et permettent unORDER BY/LIMITpropre à une branche :
SELECT 1 AS n UNION ALL SELECT 2 INTERSECT SELECT 2; -- 1, 2
(SELECT 1 AS n UNION ALL SELECT 2) INTERSECT SELECT 2; -- 2
(SELECT nom FROM client ORDER BY solde DESC LIMIT 1)
UNION ALL
(SELECT nom FROM client ORDER BY solde LIMIT 1); -- David, BobTypes des colonnes. Un entier et un décimal donnent un décimal (SELECT 1 UNION SELECT 2.5 : 1.0, 2.5). Un texte écrit sans type ('2', '24:00:00') prend, comme dans PostgreSQL, le type des requêtes résolues avant lui, deux à deux de gauche à droite (celui de la requête qui le suit s'il est le premier) : entier (smallint, integer ou bigint, dont les bornes sont contrôlées), numeric, flottant, booléen, date, heure, timestamp, timestamptz, bytea, uuid, json, jsonb, interval ou énumération ; un texte illisible dans ce type est refusé, comme par une conversion ::type (22P02 ; 22003 hors des bornes d'un entier ; 22007 ou 22008 pour une date, une heure ou un intervalle). Il en va de même des lignes d'une liste VALUES et du terme récursif d'un WITH RECURSIVE, lu dans le type du terme de départ ; un terme de départ fait de textes sans type est un text, que le terme récursif ne peut changer en un autre type (42804).
SELECT 1 UNION SELECT '2'; -- 1, 2 (integer)
SELECT 1 UNION SELECT 'x'; -- 22P02
SELECT 1 UNION SELECT '99999999999'; -- 22003 (integer)
SELECT '1.5' UNION SELECT 1 UNION SELECT 2.5; -- 22P02 ('1.5' lu en entier)
SELECT INTERVAL '1 day' UNION SELECT '24:00:00'; -- 1 day (interval)
VALUES (DATE '2026-01-02'), ('2026-03-04'); -- deux datesUn texte typé (colonne text, 'x'::text) ne correspond, comme dans PostgreSQL, qu'à un autre texte : face à un nombre, un booléen, une date, un bytea, un uuid, un document JSON, un intervalle ou une énumération, la requête est refusée (42804 : UNION types integer and text cannot be matched).
36.9 Expressions de table communes : WITH#
Une clause WITH nomme une ou plusieurs requêtes, utilisables comme des tables dans la requête principale et dans les expressions qui les suivent.
WITH total_cmd AS (
SELECT commande_id, sum(total) AS montant FROM ligne_commande GROUP BY commande_id
), par_client AS (
SELECT co.client_id, sum(t.montant) AS montant
FROM commande co JOIN total_cmd t ON t.commande_id = co.id
GROUP BY co.client_id
)
SELECT c.nom, p.montant
FROM client c JOIN par_client p ON p.client_id = c.id
ORDER BY p.montant DESC; nom | montant
-------+---------
Bob | 126.00
Chloé | 31.50
Alice | 25.90- Une liste de colonnes renomme le résultat :
WITH v (n, carre) AS (SELECT id, id * id FROM client). - Une expression peut être citée plusieurs fois (
FROM x AS p JOIN x AS q ON …). - Une expression du nom d'une table existante la masque dans la requête (
WITH client AS (SELECT 1 AS id) SELECT * FROM clientrend une ligne). AS MATERIALIZEDetAS NOT MATERIALIZEDsont acceptés.WITHs'emploie dans une sous-requête, dans une table dérivée, dans la requête d'unINSERT … SELECTet dans une vue.
WITH s'écrit aussi en tête d'un INSERT, d'un UPDATE ou d'un DELETE, et une expression de la clause qui ouvre l'instruction peut être elle-même une écriture (WITH d AS (DELETE … RETURNING *) SELECT … FROM d) : elle s'exécute une fois, son nom désigne les lignes rendues. Voir 35. Langage DML.
Expressions récursives#
WITH RECURSIVE définit une expression qui se cite elle-même : un terme de départ, UNION ALL (ou UNION, qui écarte les lignes déjà produites), puis un terme récursif.
WITH RECURSIVE r (n) AS (
SELECT 1
UNION ALL
SELECT n + 1 FROM r WHERE n < 5
)
SELECT n FROM r ORDER BY n; -- 1, 2, 3, 4, 5Parcours d'une hiérarchie :
CREATE TABLE categorie (
id integer PRIMARY KEY,
parent_id integer REFERENCES categorie (id),
nom text NOT NULL
);
INSERT INTO categorie VALUES
(1, NULL, 'Papeterie'), (2, 1, 'Écriture'), (3, 1, 'Classement'), (4, 2, 'Stylos'), (5, 2, 'Crayons');
WITH RECURSIVE arbre (id, nom, niveau, chemin) AS (
SELECT id, nom, 0, nom FROM categorie WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.nom, a.niveau + 1, a.chemin || ' > ' || c.nom
FROM categorie c JOIN arbre a ON c.parent_id = a.id
)
SELECT id, niveau, chemin FROM arbre ORDER BY chemin; id | niveau | chemin
----+--------+--------------------------------
1 | 0 | Papeterie
3 | 1 | Papeterie > Classement
2 | 1 | Papeterie > Écriture
5 | 2 | Papeterie > Écriture > Crayons
4 | 2 | Papeterie > Écriture > StylosÀ connaître :
- La récursion est bornée à 1 000 itérations par la variable
cte_max_recursion_depth(SHOW cte_max_recursion_depth,SET cte_max_recursion_depth TO 100000). Au-delà :ERREUR 54000 : Recursive query aborted after 1001 iterations. Try increasing @@cte_max_recursion_depth to a larger value.
- Une récursion sans condition d'arrêt n'est pas arrêtée par
LIMIT: l'expression est calculée en entier avant la requête principale.WITH RECURSIVE r (n) AS (SELECT 1 UNION ALL SELECT n + 1 FROM r) SELECT n FROM r LIMIT 3rend l'erreur ci-dessus (PostgreSQL rend trois lignes). Écrire la condition d'arrêt dans le terme récursif (WHERE n < 3). - Avec
UNION, la récursion s'arrête d'elle-même quand plus aucune ligne nouvelle n'apparaît (détection de cycle simple). - Les clauses
SEARCHetCYCLEne sont pas reconnues (42601). WITH RECURSIVEn'est pas admis dans une vue (0A000:WITH RECURSIVE in a stored definition is not supported yet in the PostgreSQL language).
36.10 Fonctions de fenêtrage#
Une fonction de fenêtrage calcule, pour chaque ligne, une valeur sur un ensemble de lignes voisines (la fenêtre), sans regrouper le résultat.
fonction(...) [FILTER (WHERE condition)] OVER (
[PARTITION BY expr [, ...]]
[ORDER BY expr [ASC | DESC] [, ...]]
[{ROWS | RANGE} BETWEEN borne AND borne]
)
fonction(...) OVER nom_de_fenêtre -- avec WINDOW nom AS (...)| Fonctions | Rôle |
|---|---|
row_number(), rank(), dense_rank() | numéro, rang avec trous, rang sans trou |
ntile(n), percent_rank(), cume_dist() | répartition |
lag(expr [, décalage [, défaut]]), lead(…) | valeur d'une ligne précédente ou suivante |
first_value(expr), last_value(expr), nth_value(expr, n) | valeur d'une ligne du cadre |
count, sum, avg, min, max, stddev, variance… avec OVER | agrégat sur la fenêtre |
Rangs#
SELECT nom, ville, solde,
row_number() OVER (ORDER BY solde DESC) AS rang,
rank() OVER (PARTITION BY ville ORDER BY solde DESC) AS rang_ville
FROM client ORDER BY rang; nom | ville | solde | rang | rang_ville
--------+-------------+--------+------+------------
David | Constantine | 250.00 | 1 | 1
Alice | Oran | 100.50 | 2 | 1
Émile | Alger | 75.25 | 3 | 1
Fatima | NULL | 10.00 | 4 | 1
Chloé | Oran | 0.00 | 5 | 2
Bob | Alger | -20.00 | 6 | 2Une fenêtre nommée par la clause WINDOW sert à plusieurs fonctions ; une fenêtre peut en compléter une autre (WINDOW w AS (PARTITION BY etat), w2 AS (w ORDER BY id)).
SELECT etat, id, rank() OVER w AS r, dense_rank() OVER w AS dr, row_number() OVER w AS rn
FROM commande WINDOW w AS (ORDER BY etat) ORDER BY id; etat | id | r | dr | rn
---------+----+---+----+----
livrée | 1 | 2 | 2 | 2
ouverte | 2 | 4 | 3 | 4
livrée | 3 | 2 | 2 | 3
ouverte | 4 | 4 | 3 | 5
annulée | 5 | 1 | 1 | 1Agrégats sur une fenêtre#
Avec ORDER BY, le cadre par défaut va du début de la partition à la ligne courante : l'agrégat est un cumul. OVER () porte sur tout le résultat. ROWS BETWEEN … AND … fixe le cadre en nombre de lignes (UNBOUNDED PRECEDING, n PRECEDING, CURRENT ROW, n FOLLOWING, UNBOUNDED FOLLOWING).
SELECT co.id, t.montant,
sum(t.montant) OVER (ORDER BY co.date_cmd) AS cumul,
round(100 * t.montant / sum(t.montant) OVER (), 1) AS part
FROM commande co
JOIN (SELECT commande_id, sum(total) AS montant FROM ligne_commande GROUP BY commande_id) t
ON t.commande_id = co.id
ORDER BY co.date_cmd; id | montant | cumul | part
----+---------+--------+------
1 | 19.00 | 19.00 | 10.4
2 | 6.90 | 25.90 | 3.8
3 | 126.00 | 151.90 | 68.7
4 | 31.50 | 183.40 | 17.2SELECT id, avg(id) OVER (ORDER BY date_cmd ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS moy_glissante
FROM commande ORDER BY date_cmd;
-- 1.5, 2.0, 3.0, 4.0, 4.5 (à seize décimales)
SELECT id, count(*) FILTER (WHERE etat = 'livrée') OVER (ORDER BY id) AS livrees
FROM commande ORDER BY id; -- 1, 1, 2, 2, 2Une fonction de fenêtrage peut porter sur un agrégat d'un GROUP BY :
SELECT ville, count(*) AS n, rank() OVER (ORDER BY count(*) DESC) AS rang
FROM client GROUP BY ville ORDER BY rang, ville;
-- Alger 2 1, Oran 2 1, Constantine 1 3, NULL 1 3Lignes voisines#
SELECT id, date_cmd,
lag(date_cmd) OVER (ORDER BY date_cmd) AS precedente,
date_cmd - lag(date_cmd) OVER (ORDER BY date_cmd) AS jours,
lead(id, 1, 0) OVER (ORDER BY date_cmd) AS suivante
FROM commande ORDER BY date_cmd; id | date_cmd | precedente | jours | suivante
----+------------+------------+-------+----------
1 | 2026-09-01 | NULL | NULL | 2
2 | 2026-09-15 | 2026-09-01 | 14 | 3
3 | 2026-09-20 | 2026-09-15 | 5 | 4
4 | 2026-10-02 | 2026-09-20 | 12 | 5
5 | 2026-10-05 | 2026-10-02 | 3 | 0SELECT code, prix,
first_value(code) OVER w AS moins_cher,
last_value(code) OVER (ORDER BY prix ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS plus_cher,
nth_value(code, 2) OVER w AS deuxieme
FROM article WINDOW w AS (ORDER BY prix) ORDER BY prix; code | prix | moins_cher | plus_cher | deuxieme
-------+-------+------------+-----------+----------
GO-01 | 0.80 | GO-01 | AG-01 | NULL
ST-01 | 1.20 | GO-01 | AG-01 | ST-01
TR-01 | 2.10 | GO-01 | AG-01 | ST-01
CA-01 | 3.50 | GO-01 | AG-01 | ST-01
CL-01 | 6.90 | GO-01 | AG-01 | ST-01
AG-01 | 14.00 | GO-01 | AG-01 | ST-01(last_value exige un cadre étendu jusqu'à la fin, le cadre par défaut s'arrêtant à la ligne courante ; nth_value(code, 2) est NULL tant que le cadre n'a qu'une ligne.)
Premier de chaque groupe#
Une fonction de fenêtrage n'est pas admise dans WHERE (42803) : on filtre sur son résultat au travers d'une table dérivée (ou par SELECT DISTINCT ON, voir 36.2, pour le premier seul).
SELECT ville, nom, solde
FROM (SELECT ville, nom, solde,
row_number() OVER (PARTITION BY ville ORDER BY solde DESC) AS rn
FROM client) x
WHERE rn = 1
ORDER BY ville; ville | nom | solde
-------------+--------+--------
Alger | Émile | 75.25
Constantine | David | 250.00
Oran | Alice | 100.50
NULL | Fatima | 10.00Avec rank() à la place de row_number(), les ex æquo de chaque groupe sont tous rendus.
Limites#
| Forme | Réponse |
|---|---|
RANGE BETWEEN n PRECEDING AND … (cadre RANGE avec un décalage) | 0A000 : This version of Miraj doesn't yet support 'RANGE with an offset in a window frame' |
cadre GROUPS, clause EXCLUDE | 42601 |
string_agg(…) OVER (…) | 0A000 |
fonction de fenêtrage dans WHERE | 42803 : Invalid use of group function |
36.11 Verrous de lecture : FOR UPDATE, FOR SHARE#
Dans une transaction, une lecture verrouillante retient les lignes lues jusqu'au COMMIT ou au ROLLBACK, pour les modifier ensuite sans qu'une autre session ne les change entre-temps.
SELECT ... FOR {UPDATE | NO KEY UPDATE | SHARE | KEY SHARE} [OF table [, ...]] [NOWAIT | SKIP LOCKED]BEGIN;
SELECT id, stock FROM article WHERE id = 1 FOR UPDATE; -- 1 | 500 : la ligne est retenue
UPDATE article SET stock = stock - 10 WHERE id = 1;
COMMIT;| Clause | Effet |
|---|---|
FOR UPDATE, FOR NO KEY UPDATE | verrou exclusif : une autre session ne peut ni verrouiller ni modifier ces lignes |
FOR SHARE, FOR KEY SHARE | verrou partagé : d'autres sessions peuvent le prendre aussi (FOR SHARE), aucune ne peut prendre FOR UPDATE ni modifier |
OF table | dans une jointure, ne verrouille que les lignes de la table citée (par son alias) |
NOWAIT | échoue tout de suite si une ligne est retenue par une autre session |
SKIP LOCKED | saute les lignes retenues par une autre session |
Comportement constaté entre deux sessions, la session A tenant FOR UPDATE sur l'article 1 :
| Session B | Résultat |
|---|---|
SELECT … WHERE id = 1 (lecture simple) | rend la ligne aussitôt : une lecture ordinaire n'attend jamais |
SELECT … WHERE id = 1 FOR UPDATE NOWAIT | 55P03 aussitôt : Lock wait timeout exceeded; try restarting transaction |
SELECT … WHERE id = 1 FOR SHARE NOWAIT | 55P03 aussitôt |
SELECT … WHERE id <= 3 ORDER BY id FOR UPDATE SKIP LOCKED | rend les lignes 2 et 3 |
SELECT … WHERE id = 1 FOR UPDATE | attend la fin de la transaction de A, puis rend la ligne ; 55P03 au bout de lock_wait_timeout secondes |
UPDATE article … WHERE id = 1 | attend de même ; 55P03 au bout de lock_wait_timeout |
L'attente maximale se règle par session : SET lock_wait_timeout = 2 (en secondes). Le SQLSTATE 55P03 est celui de PostgreSQL pour NOWAIT ; il sert aussi ici à l'attente expirée.
SKIP LOCKED permet à plusieurs sessions de se partager une file de travail :
BEGIN;
SELECT id FROM commande WHERE etat = 'ouverte' ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED;
-- traiter la commande rendue, puis :
UPDATE commande SET etat = 'livrée' WHERE id = 2;
COMMIT;Hors d'un bloc de transaction, la clause est acceptée mais le verrou est rendu dès la fin de l'instruction : elle n'a d'intérêt qu'entre BEGIN et COMMIT. Le modèle de concurrence, les conflits de validation et les réessais sont décrits au chapitre 38. Transactions et session.
36.12 Lire une vue#
Une vue (voir 34. Langage DDL) se lit comme une table, dans toutes les formes de ce chapitre : jointures, regroupements, fenêtres, sous-requêtes.
CREATE VIEW v_ca AS
SELECT c.id, c.nom, sum(l.total) AS ca
FROM client c
JOIN commande co ON co.client_id = c.id
JOIN ligne_commande l ON l.commande_id = co.id
GROUP BY c.id, c.nom;
SELECT nom, ca, rank() OVER (ORDER BY ca DESC) AS rang FROM v_ca ORDER BY rang; nom | ca | rang
-------+--------+------
Bob | 126.00 | 1
Chloé | 31.50 | 2
Alice | 25.90 | 3La requête d'une vue admet WITH (non récursif), ORDER BY et LIMIT. Elle n'admet pas FULL JOIN, LATERAL ni WITH RECURSIVE (0A000 : … in a stored definition is not supported yet in the PostgreSQL language), qui restent utilisables dans une requête directe.
36.13 Extensions MIRAJ dans un SELECT#
Ces formes ne sont pas du PostgreSQL ; elles viennent du langage MIRAJ et restent acceptées. Un script qui doit aussi s'exécuter sur un serveur PostgreSQL ne doit pas les employer.
| Forme | Effet |
|---|---|
QUALIFY condition | filtre sur le résultat d'une fonction de fenêtrage, sans table dérivée |
GROUP BY a, b WITH ROLLUP | équivalent de GROUP BY ROLLUP (a, b) |
alias de la liste dans HAVING | HAVING ecart > 0 où ecart est un alias (PostgreSQL le refuse) |
x COLLATE utf8mb4_general_ci | comparaison et tri sans casse ni accents (36.4) |
MATCH (colonnes) AGAINST ('mots') | recherche plein texte, sur un index créé par CREATE FULLTEXT INDEX (voir 9.22) ; les mots sont comparés selon la collation de la colonne, donc en respectant la casse |
SELECT ville, nom, solde FROM client
QUALIFY row_number() OVER (PARTITION BY ville ORDER BY solde DESC) = 1
ORDER BY ville;
-- même résultat que « Premier de chaque groupe » (35.10)
CREATE FULLTEXT INDEX ft_lib ON article (libelle);
SELECT code, libelle FROM article WHERE MATCH (libelle) AGAINST ('pages'); -- CA-01 | Cahier 96 pages
SELECT code, libelle FROM article WHERE MATCH (libelle) AGAINST ('cahier'); -- aucune ligne : « Cahier »36.14 EXPLAIN#
EXPLAIN affiche le plan d'exécution d'une requête sans l'exécuter. Le plan est celui de MIRAJ, dans sa propre présentation : un tableau d'une ligne par table lue, et non l'arbre de nœuds de PostgreSQL. Les outils qui analysent la sortie d'EXPLAIN de PostgreSQL ne la liront pas.
EXPLAIN SELECT c.nom, co.id FROM client c JOIN commande co ON co.client_id = c.id WHERE c.ville = 'Oran'; id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
----+-------------+-------+------+---------------+------+---------+------+------+-------------------------------
1 | SIMPLE | c | ALL | PRIMARY,email | NULL | NULL | NULL | 6 | Using where
1 | SIMPLE | co | ALL | PRIMARY | NULL | NULL | NULL | 5 | Using join buffer (hash join)EXPLAIN SELECT * FROM client WHERE id = 1; id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
----+-------------+--------+--------+---------------+---------+---------+-------+------+-------
1 | SIMPLE | client | eq_ref | PRIMARY,email | PRIMARY | NULL | const | 1 |type indique le mode d'accès (ALL : lecture de toute la table ; eq_ref : une ligne par clé unique), key l'index retenu, rows l'estimation du nombre de lignes lues. La lecture détaillée d'un plan est décrite aux sections 9.20 et 9.21 du manuel général.
| Forme | Résultat |
|---|---|
EXPLAIN requête | tableau ci-dessus |
EXPLAIN (FORMAT JSON) requête | le même plan en un document JSON (une colonne EXPLAIN) |
EXPLAIN ANALYZE requête | exécute la requête et rend l'arbre des opérations avec temps et lignes réels |
EXPLAIN (ANALYZE, VERBOSE, COSTS off, BUFFERS) requête | options acceptées ; même sortie que EXPLAIN ANALYZE |
EXPLAIN (FORMAT XML), EXPLAIN (FORMAT YAML) | 0A000 : EXPLAIN (FORMAT XML | YAML) is not supported yet in the PostgreSQL language |
EXPLAIN ANALYZE SELECT * FROM client WHERE ville = 'Oran';-> Project (actual time=0.010..0.011 rows=2 loops=1)
-> Table scan on client with pushed-down filter (rows=6) (actual time=0.009..0.009 rows=2 loops=1)L'étiquette rendue est EXPLAIN. EXPLAIN s'applique aussi à UPDATE et DELETE (voir 35.9).
36.15 Non pris en charge et écarts#
Formes refusées#
| Forme PostgreSQL | Réponse de MIRAJ | À la place |
|---|---|---|
LIMIT d'une expression (LIMIT 1 + 1), LIMIT -1 | 42601 | entier écrit ou paramètre |
LIMIT 10, 5 (forme du langage MIRAJ) | 42601 : LIMIT offset, count is MIRAJ syntax, not accepted in the PostgreSQL language | LIMIT 5 OFFSET 10 |
a && b, a || b comme opérateurs logiques | 42601 pour && ; || concatène | AND, OR |
table dérivée sans alias : FROM (SELECT …) | 42601 : syntax error at end of input | donner un alias |
alias d'une jointure : JOIN … USING (id) AS j | 42601 | |
NATURAL CROSS JOIN | 42601 | |
LATERAL avec RIGHT ou FULL JOIN, USING, NATURAL | 0A000 : … 'LATERAL with RIGHT or FULL JOIN, USING or NATURAL' | |
generate_series dont les bornes citent une autre table | 42703 | |
unnest d'une colonne, ARRAY(SELECT …), colonnes de type tableau | 0A000 | array_agg, string_agg, json_agg |
TABLE client | 42601 | SELECT * FROM client |
SELECT … INTO nouvelle_table | 42000 : Undeclared variable: copie | CREATE TABLE … AS SELECT … |
TABLESAMPLE | 42601 | |
SIMILAR TO | 42601 | ~ |
ORDER BY … USING opérateur | 42601 | ASC, DESC |
ORDER BY 3 NULLS LAST quand le rang désigne une colonne de * | 0A000 : NULLS FIRST | LAST on this column position is not supported yet in the PostgreSQL language | nommer la colonne |
(a, b) = (SELECT …, …) | 42601 : Operand should contain 1 column(s) | (a, b) IN (SELECT …, …) |
WITH … SEARCH, WITH … CYCLE | 42601 | colonne de chemin calculée dans la récursion |
WITH qui écrit (WITH x AS (INSERT … RETURNING …)) ailleurs qu'en tête de l'instruction | 0A000 | l'écrire en tête, ou deux instructions dans une transaction |
WITH RECURSIVE, FULL JOIN, LATERAL dans une vue | 0A000 | requête directe |
cadre RANGE avec décalage, GROUPS, EXCLUDE ; string_agg … OVER | 0A000, 42601 (36.10) | cadre ROWS |
EXPLAIN (FORMAT XML | YAML) | 0A000 | FORMAT JSON |
Écarts de comportement#
| Sujet | MIRAJ | PostgreSQL |
|---|---|---|
| identifiants entre guillemets | insensibles à la casse | sensibles |
| nom d'une colonne calculée sans alias | texte écrit (count(*), solde * 2) | count, ?column? |
alias de la liste dans HAVING | accepté | refusé |
SELECT DISTINCT a … ORDER BY b (clé absente de la liste) | accepté | refusé |
WITH RECURSIVE sans condition d'arrêt, avec LIMIT | erreur à 1 000 itérations (cte_max_recursion_depth) | s'arrête à LIMIT |
generate_series | au plus 10 000 000 de valeurs, erreur 54000 au-delà | sans limite |
FOR UPDATE hors transaction, ou sur une requête d'agrégat | accepté | sans effet utile, refusé avec un agrégat |
| attente d'un verrou expirée | 55P03 après lock_wait_timeout | attente sans limite par défaut |
EXPLAIN | plan et présentation de MIRAJ | arbre de nœuds PostgreSQL |
avg d'entiers ou de décimaux | numeric à seize décimales | échelle variable selon la valeur |
La liste condensée de ces points est tenue au chapitre 30 (section 30.2).
SQLSTATE rencontrés dans ce chapitre#
| SQLSTATE | Sens | Exemple |
|---|---|---|
0A000 | non pris en charge | FULL JOIN dans une vue |
21000 | sous-requête scalaire à plusieurs lignes | SELECT (SELECT nom FROM client) |
22023 | argument invalide | pas nul de generate_series, percentile hors de 0..1 |
22P02 | écriture invalide | texte non numérique comparé à un nombre ; texte illisible dans le type d'une union |
42601 | syntaxe | formes non reconnues ci-dessus ; nombres de colonnes différents dans un UNION |
42702 | colonne ambiguë | WHERE id = 1 dans une jointure de deux tables qui ont id |
42703 | colonne inconnue | alias de la liste cité dans WHERE ; ORDER BY d'un rang hors de la liste |
42803 | regroupement | colonne hors GROUP BY, fonction de fenêtrage dans WHERE |
42P01 | table inconnue | |
42P10 | liste incohérente | trop d'alias de colonnes ; DISTINCT ON qui ne commence pas l'ORDER BY |
54000 | limite atteinte | récursion au-delà de cte_max_recursion_depth, generate_series de plus de 10 000 000 de valeurs |
55P03 | verrou non obtenu | NOWAIT, attente expirée |