36. PostgreSQL : Fonctions SQL
Ce chapitre décrit les opérateurs et les fonctions qu'une instance MIRAJ en langage PostgreSQL met à la disposition des requêtes : chaînes, recherche de motifs, nombres, dates et intervalles, mise en forme (to_char, to_date, to_number), expressions conditionnelles, agrégats (statistiques et à tri interne compris), fonctions de fenêtrage, JSON et JSONB (chemins SQL/JSON compris), conversions, octets et hachage, fonctions du système, tableaux et fonctions ensemblistes. Chaque exemple a été exécuté sur une instance réelle ; les résultats affichés sont ceux qu'elle a rendus.
Le chapitre décrit le langage PostgreSQL tel que MIRAJ le parle. Les fonctions de PostgreSQL ne sont pas toutes présentes et quelques-unes se comportent autrement : la section 36.16 en fait la liste, avec le SQLSTATE et le message reçus.
Table des matières#
- 36.0 Généralités
- 36.1 Opérateurs
- 36.2 Fonctions de chaînes
- 36.3 Recherche de motifs
- 36.4 Fonctions numériques
- 36.5 Dates, heures et intervalles
- 36.6 Mise en forme :
to_char,to_date,to_timestamp,to_number - 36.7 Expressions conditionnelles
- 36.8 Fonctions d'agrégation
- 36.9 Fonctions de fenêtrage
- 36.10 JSON et JSONB
- 36.11 Conversions
- 36.12 Octets, hachage et encodage
- 36.13 Système, session, séquences et séries
- 36.14 Tableaux et fonctions ensemblistes
- 36.15 Fonctions du langage MIRAJ
- 36.16 Non pris en charge et écarts
36.0 Généralités#
- Noms insensibles à la casse.
upper,UPPERetUpperdésignent la même fonction. Ce chapitre les écrit en minuscules, comme la documentation de PostgreSQL. Le préfixepg_catalog.est accepté et ignoré (pg_catalog.version()). - Propagation de
NULL. Sauf mention contraire, une fonction qui reçoit un argumentNULLrendNULL. Les exceptions (coalesce,concat,concat_ws,format,quote_nullable,array_to_string…) sont signalées dans leur description ; les agrégats, eux, ignorent lesNULL(saufarray_agg,json_aggetjsonb_agg). - Positions et longueurs se comptent en caractères, à partir de 1.
octet_lengthetbit_lengthcomptent des octets et des bits de l'encodage UTF-8. - Comparaisons de textes octet par octet. Comme PostgreSQL sous la collation « C », les textes se comparent et se trient octet par octet :
'abc' = 'ABC'et'é' = 'e'sont faux,ORDER BYrange les majuscules avant les minuscules et les lettres accentuées aprèsz,'a'et'A'sont deux valeurs distinctes pourDISTINCT,GROUP BYou une cléUNIQUE. Pour comparer sans égard à la casse ni aux accents :ILIKE,~*,lower(x) = lower(y),unaccentou la collationCOLLATE utf8mb4_general_ci(voir 36.3). - Fonction inconnue : erreur
42883(FUNCTION … does not exist) ; mauvais nombre d'arguments :42883aussi, au message de PostgreSQL (function btrim(unknown, unknown, unknown) does not exist). - Type d'un résultat :
pg_typeof(expr)le donne. Les tableaux de ce chapitre indiquent le type annoncé au client. - Une colonne calculée sans alias porte le texte écrit (
SELECT upper(nom) FROM clientnomme la colonneupper(nom), là où PostgreSQL écritupper) : donner un alias (AS …) aux colonnes lues par leur nom.
Base d'exemple#
Les exemples utilisent la base ventes du manuel. Les lignes ci-dessous donnent les résultats affichés dans ce chapitre.
CREATE SCHEMA ventes;
SET search_path TO ventes;
CREATE TABLE client (
id serial PRIMARY KEY, nom varchar(80) NOT NULL, ville varchar(60),
email varchar(120), cree_le timestamp NOT NULL);
CREATE TABLE article (
id serial PRIMARY KEY, reference varchar(20) NOT NULL UNIQUE, designation varchar(120) NOT NULL,
prix numeric(10,2) NOT NULL, stock integer NOT NULL DEFAULT 0, attributs jsonb);
CREATE TABLE commande (
id serial PRIMARY KEY, client_id integer NOT NULL REFERENCES client (id),
date_commande date NOT NULL, statut varchar(20) NOT NULL DEFAULT 'en cours', livree_le timestamp);
CREATE TABLE ligne_commande (
commande_id integer NOT NULL REFERENCES commande (id), article_id integer NOT NULL REFERENCES article (id),
quantite integer NOT NULL, prix_unitaire numeric(10,2) NOT NULL, PRIMARY KEY (commande_id, article_id));
INSERT INTO client (nom, ville, email, cree_le) VALUES
('Dupont', 'Lyon', '[email protected]', '2026-01-15 09:30:00'),
('Martin', 'Paris', '[email protected]', '2026-02-03 14:05:00'),
('Bernard', 'Lyon', NULL, '2026-03-21 08:00:00'),
('Élodie Petit', NULL, '[email protected]', '2026-04-02 17:45:30');
INSERT INTO article (reference, designation, prix, stock, attributs) VALUES
('STY-001', 'Stylo bille bleu', 1.20, 500, '{"couleur": "bleu", "tags": ["bureau", "ecriture"], "dim": {"l": 14, "d": 0.8}}'),
('CAH-096', 'Cahier 96 pages', 2.50, 120, '{"couleur": "rouge", "tags": ["papier"], "pages": 96}'),
('CLA-A4', 'Classeur A4', 4.90, 35, '{"couleur": "noir", "tags": [], "recycle": true}'),
('AGR-024', 'Agrafeuse', 12.00, 0, NULL);
INSERT INTO commande (client_id, date_commande, statut, livree_le) VALUES
(1, '2026-09-28', 'livrée', '2026-10-01 10:15:00'), (1, '2026-10-05', 'en cours', NULL),
(2, '2026-10-06', 'livrée', '2026-10-08 16:40:00'), (3, '2026-10-09', 'annulée', NULL);
INSERT INTO ligne_commande VALUES
(1, 1, 100, 1.20), (1, 2, 10, 2.50), (2, 3, 4, 4.90), (3, 1, 50, 1.10), (3, 4, 2, 12.00), (4, 2, 5, 2.50);Les types de colonnes sont décrits au chapitre 32. Types de données, les requêtes au chapitre 35. Requêtes SELECT.
36.1 Opérateurs#
Arithmétique#
| Opérateur | Rôle | Exemple | Résultat |
|---|---|---|---|
+, -, * | addition, soustraction, multiplication | 7 * 2 | 14 |
/ | division ; entière (tronquée vers zéro) entre deux entiers | 7 / 2, -7 / 2, 7 / 2.0 | 3, -3, 3.5000000000000000 |
DIV, div(a, b) | division entière (tronquée vers zéro) | 7 DIV 2, div(-17, 5) | 3, -3 |
%, mod(a, b) | reste, du signe du dividende | 17 % 5, mod(-17, 5) | 2, -2 |
^, power(a, b), pow(a, b) | puissance | 2 ^ 10 | 1024 |
-x | opposé | -(3 + 2) | -5 |
SELECT 7 / 2, -7 / 2, 7 / 2.0, 1.0 / 3, 7::float8 / 2, 7 % 2, 2 ^ 3;3 | -3 | 3.5000000000000000 | 0.33333333333333333333 | 3.5 | 1 | 8Comme dans PostgreSQL, la division de deux entiers est entière : 7 / 2 vaut 3, du type des opérandes. Pour un quotient décimal, rendre un opérande décimal ou flottant : 7 / 2.0, 7::numeric / 2, 7::float8 / 2.
Types des résultats :
| Opération | Type |
|---|---|
integer + - * / % integer | integer ; smallint entre deux smallint, bigint dès qu'un opérande est un bigint |
avec un numeric | numeric : échelle des opérandes pour + - * (2.50 * 3 vaut 7.50, 1.10 * 1.1 vaut 1.210) ; pour /, seize chiffres significatifs au moins, jamais moins de décimales qu'un opérande (7 / 2.0 vaut 3.5000000000000000, 1.0 / 3 vaut 0.33333333333333333333, 2.50 / 2 vaut 1.25000000000000000000) |
avec un double precision ou un real | double precision |
integer ^ integer, power(2, 10) | double precision ; numeric exact à 16 chiffres dès qu'un opérande est un décimal (2 ^ 0.5 vaut 1.4142135623730950) |
Un entier écrit qui tient sur 32 bits est un integer, un entier plus grand un bigint, un nombre à virgule ou à exposant (1.5, 1e3) un numeric.
Erreurs de calcul :
| Instruction | Réponse |
|---|---|
SELECT 1 / 0, 5 % 0, 1.0 / 0.0, div(1, 0) | 22012 : division by zero |
SELECT 2147483647 + 1 | 22003 : integer out of range (convertir un opérande : 2147483647::bigint + 1 vaut 2147483648) |
SELECT 9223372036854775807 + 1 | 22003 : BIGINT value is out of range in '9223372036854775807 + 1' |
SELECT power(0, -1) | 2201F : zero raised to a negative power is undefined |
SELECT power(-8, 0.5) | 2201F : a negative number raised to a non-integer power yields a complex result |
mod(a, 0) rend la même erreur 22012 que a % 0.
Comparaison#
| Opérateur | Rôle |
|---|---|
=, <>, !=, <, <=, >, >= | comparaison ; NULL si un opérande est NULL |
a BETWEEN x AND y, a NOT BETWEEN x AND y | intervalle fermé |
a IN (v1, v2, …), a NOT IN (…), a IN (SELECT …) | appartenance à une liste ou à une sous-requête |
a IS NULL, a IS NOT NULL, a ISNULL, a NOTNULL | test de NULL |
a IS DISTINCT FROM b, a IS NOT DISTINCT FROM b | comparaison qui traite NULL comme une valeur |
b IS TRUE, IS NOT TRUE, IS FALSE, IS NOT FALSE, IS UNKNOWN | test d'un booléen |
a = ANY (tableau), a = SOME (tableau), a <> ALL (tableau), a > ANY (tableau) | comparaison à chaque élément d'un tableau (voir 36.14) |
a = ANY (SELECT …), a < ALL (SELECT …), EXISTS (SELECT …) | comparaison à une sous-requête |
(a, b) = (c, d), (a, b) < (c, d), (a, b) IN ((1, 'x'), (2, 'y')), ROW(a, b) = ROW(c, d) | comparaison de lignes |
SELECT 5 BETWEEN 1 AND 10, 5 IN (1, 5), 1 IS DISTINCT FROM NULL, NULL IS NOT DISTINCT FROM NULL,
1 = ANY (ARRAY[1, 2]), 3 <> ALL (ARRAY[1, 2]), (1, 2) < (1, 3);t | t | t | t | t | t | tSELECT x ISNULL, x NOTNULL, 'abc' ~~ 'a%', 'abc' ~~* 'A%', 'abc' !~~ 'b%' FROM (SELECT NULL::int AS x) t;t | f | t | t | t~~, ~~*, !~~ et !~~* sont les opérateurs de LIKE, ILIKE, NOT LIKE et NOT ILIKE (voir 36.3).
Une condition placée dans la liste d'un SELECT est un booléen, rendu t ou f. Placée dans un calcul ou passée à une fonction, elle vaut l'entier 1 ou 0 : (1 = 1) + 1 vaut 2. pg_typeof(1 = 1) rend boolean, et la condition convertie en texte s'écrit true ou false.
Logique#
AND, OR, NOT, avec la logique à trois valeurs du standard :
SELECT true AND NULL, false AND NULL, true OR NULL, NOT true;NULL | f | t | f&&, || (comme OU) et ! ne sont pas des opérateurs logiques en langage PostgreSQL : ils sont refusés (42601) ou ont un autre sens (|| concatène).
Concaténation#
a || b concatène deux valeurs, converties en texte au besoin. Le résultat est NULL si l'une des deux est NULL.
SELECT 'Stylo' || ' ' || 'bille', 'Total : ' || 12.5 || ' €', 'n=' || NULL, coalesce('n=' || NULL, 'sans');Stylo bille | Total : 12.5 € | NULL | sansUn booléen concaténé s'écrit true ou false ('abc' || true vaut abctrue) ; la fonction concat l'écrit t ou f. Entre deux documents jsonb, || fusionne les documents (voir 36.10) ; entre deux json, il est refusé (42883).
Bit à bit#
| Opérateur | Rôle | Exemple | Résultat |
|---|---|---|---|
a & b | ET | 5 & 3 | 1 |
a | b | OU | 5 | 3 | 7 |
a # b | OU exclusif | 5 # 3 | 6 |
~a | complément | ~5 | -6 |
a << n, a >> n | décalages | 1 << 4, -8 >> 1 | 16, -4 |
Les entiers sont signés. bit_count(n) compte les bits à 1 (bit_count(7) vaut 3).
Chaînes de bits#
Sur des chaînes de bits (bit(n), bit varying(n), littéraux B'101' et X'1F', voir 32. Types de données), les mêmes opérateurs travaillent position par position et rendent une chaîne de bits :
| Opérateur ou fonction | Rôle | Exemple | Résultat |
|---|---|---|---|
a & b, a | b, a # b | ET, OU, OU exclusif de deux chaînes de même longueur | B'101' & B'110' | 100 (bit) |
~a | complément, sur les positions de la chaîne | ~B'101' | 010 |
a << n, a >> n | décalages, longueur gardée, zéros entrants | B'10001' >> 2 | 00100 |
a || b | concaténation | B'101' || B'11' | 10111 (bit varying) |
length(a), bit_length(a) | nombre de positions | length(B'10111') | 5 |
octet_length(a) | nombre d'octets | octet_length(B'101100111') | 2 |
bit_count(a) | nombre de bits à 1 | bit_count(B'1101') | 3 |
get_bit(a, n) | position n, à partir de 0 depuis la gauche | get_bit(B'101', 1) | 0 |
set_bit(a, n, v) | chaîne dont la position n vaut v (0 ou 1) | set_bit(B'101', 1, 1) | 111 |
substring(a from d for n) | positions extraites | substring(B'110010111111' from 3 for 2) | 00 |
position(s in a) | rang de la sous-chaîne | position(B'010' in B'000001101011') | 8 |
overlay(a placing s from d for n) | positions remplacées | overlay(B'0101' placing B'11' from 2 for 2) | 0111 |
&, | et # exigent deux chaînes de même longueur (22026 : cannot AND bit strings of different sizes). Face à une chaîne de bits, un texte écrit est lu en chaîne de bits (b & '110') ; un nombre est refusé (42883 : operator does not exist: bit & integer), de même qu'un opérateur arithmétique (b + 1). get_bit hors de la chaîne : 2202E (bit index 3 out of valid range (0..2)). get_bit et set_bit n'existent que pour une chaîne de bits.
Priorité#
De la plus forte à la plus faible : :: ; - unaire ; ^ ; *, /, % ; +, - ; || ; comparaisons ; NOT ; AND ; OR. ^ s'associe à gauche (2 ^ 3 ^ 2 vaut 64). Comme + passe avant ||, 1 + 2 || 'a' vaut 3a et 'a' || 1 + 2 vaut a3.
:: passant avant le moins unaire, -3.7::int se lit -(3.7::int) et vaut -4, comme (-3.7)::int.
Deux intervalles à la suite (d + INTERVAL '1 month' - INTERVAL '1 day') se lisent sans parenthèses ; INTERVAL '1 hour' * 2 multiplie l'intervalle.
36.2 Fonctions de chaînes#
Récapitulatif :
| Fonction | Rôle | Type |
|---|---|---|
a || b, concat, concat_ws | concaténation | text |
upper, lower, initcap | casse | text |
length, char_length, character_length | longueur en caractères | integer |
octet_length, bit_length | longueur en octets, en bits | integer |
trim, btrim, ltrim, rtrim | retrait de caractères (ou d'octets) aux extrémités | text, bytea |
substring, substr | sous-chaîne | text |
left, right | début, fin | text |
position, strpos | position d'une sous-chaîne | integer |
starts_with | préfixe | boolean |
replace, translate, overlay | remplacement | text |
lpad, rpad, repeat, reverse | remplissage, répétition, inversion | text |
split_part | n-ième champ d'une chaîne découpée | text |
ascii, chr | code d'un caractère, caractère d'un code | integer, text |
format | gabarit %s, %I, %L | text |
quote_ident, quote_literal, quote_nullable | citation pour du SQL construit | text |
normalize, unistr | forme normale Unicode, échappements Unicode | text |
parse_ident | découpage d'un nom qualifié | text[] |
levenshtein, soundex, difference, similarity, unaccent | distance, code phonétique, ressemblance, retrait des accents | integer, text, real |
md5, sha256, to_hex, encode, decode, convert_to, convert_from | hachage, encodage | voir 36.12 |
regexp_replace, regexp_match et les expressions régulières | motifs | voir 36.3 |
to_char, to_number | mise en forme et lecture d'un nombre ou d'une date | voir 36.6 |
string_agg | concaténation d'un groupe | voir 36.8 |
lower(1) et upper(1.5) sont refusés (42883 : function lower(integer) does not exist) : ces deux fonctions n'existent que pour le texte. Les autres convertissent leur argument en texte (length(12) vaut 2).
concat et concat_ws#
concat(valeur1, valeur2, ...)
concat_ws(separateur, valeur1, valeur2, ...)concat met bout à bout ses arguments, convertis en texte ; les arguments NULL sont ignorés (concat('a', NULL, 'b') vaut ab, concat(NULL) une chaîne vide), alors que 'a' || NULL vaut NULL.
concat_ws insère separateur entre les arguments non NULL ; les NULL sont ignorés. Un séparateur NULL rend NULL.
Un booléen s'y écrit t ou f, comme dans PostgreSQL : concat('a', true) vaut at, concat_ws(',', true, 1 = 2) vaut t,f (alors que 'a' || true vaut atrue). La distinction est gardée dans la définition d'une vue, d'une colonne générée ou d'une valeur par défaut.
SELECT concat(nom, ' (', ville, ')') AS a,
nom || ' (' || coalesce(ville, '?') || ')' AS b,
concat_ws(' - ', nom, ville, email) AS c
FROM client ORDER BY id; a | b | c
-----------------+------------------+------------------------------------
Dupont (Lyon) | Dupont (Lyon) | Dupont - Lyon - [email protected]
Martin (Paris) | Martin (Paris) | Martin - Paris - [email protected]
Bernard (Lyon) | Bernard (Lyon) | Bernard - Lyon
Élodie Petit () | Élodie Petit (?) | Élodie Petit - [email protected]upper, lower, initcap#
upper(chaine) lower(chaine) initcap(chaine)Majuscules, minuscules (règles Unicode), et première lettre de chaque mot en majuscule, les autres en minuscules.
SELECT upper('café'), lower('CAFÉ'), initcap('jean-pierre de la fontaine');CAFÉ | café | Jean-Pierre De La FontaineLongueurs#
length(chaine) char_length(chaine) character_length(chaine)
octet_length(chaine) bit_length(chaine)length, char_length et character_length comptent des caractères ; octet_length les octets de l'encodage UTF-8 ; bit_length huit fois plus. Sur un bytea, length et octet_length comptent les octets.
SELECT length('café'), char_length('café'), octet_length('café'), bit_length('café'), length('abc'::bytea);4 | 4 | 5 | 40 | 3trim, btrim, ltrim, rtrim#
trim([BOTH | LEADING | TRAILING] [caracteres] FROM chaine)
trim(chaine)
btrim(chaine [, caracteres]) ltrim(chaine [, caracteres]) rtrim(chaine [, caracteres])Retirent des caractères en tête (LEADING, ltrim), en fin (TRAILING, rtrim) ou des deux côtés (BOTH, btrim, valeur par défaut de trim). Sans caracteres, ce sont les espaces.
SELECT '[' || trim(' x ') || ']', '[' || ltrim(' x ') || ']', '[' || rtrim(' x ') || ']';
SELECT trim(BOTH 'x' FROM 'xxaxx'), trim(LEADING 'x' FROM 'xxaxx'), trim(TRAILING 'x' FROM 'xxaxx');
SELECT btrim('yxaxyx', 'xy'), ltrim('yxaxx', 'xy'), rtrim('axyyx', 'xy');[x] | [x ] | [ x]
a | axx | xxa
a | axx | acaracteres est lu comme un ensemble de caractères, comme dans PostgreSQL, dans toutes les formes : trim(BOTH 'ab' FROM 'baxab') et btrim('baxab', 'ab') valent x. trim s'écrit aussi avec une liste (trim(BOTH FROM chaine, caracteres), trim(LEADING chaine, caracteres)) et se garde sous la forme btrim / ltrim / rtrim dans la définition d'une vue ou d'une colonne générée.
Ces fonctions n'existent que pour le texte et pour bytea :
- un argument d'un autre type (nombre, booléen, date, heure, intervalle,
uuid,json,jsonb) est refusé :trim(42)rend42883(function btrim(integer) does not exist) ; le convertir (trim(n::text)) ; - sur un
bytea, ce sont des octets qui sont retirés et le résultat est unbytea:btrim(octets, octets),ltrim,rtrim,trim([BOTH | LEADING | TRAILING] octets FROM octets). Un littéral sans type y est lu enbytea('\x00'). Les octets à retirer sont obligatoires (btrim(octets):42883), et un texte ne se mêle pas à des octets (btrim(octets, colonne_texte):42883) ; - plus de deux arguments :
42883(function btrim(unknown, unknown, unknown) does not exist).
SELECT btrim('\x0000ff1000'::bytea, '\x00'), trim(LEADING '\x00' FROM '\x0000ff1000'::bytea), rtrim('\x0000ff1000'::bytea, '\x00');\xff10 | \xff1000 | \x0000ff10substring et substr#
substring(chaine FROM debut [FOR longueur])
substring(chaine FOR longueur)
substring(chaine, debut [, longueur])
substr(chaine, debut [, longueur])
substring(chaine FROM motif)Sous-chaîne à partir du caractère debut (1 pour le premier), sur longueur caractères ou jusqu'à la fin. Avec un texte à la place de debut, substring(chaine FROM motif) lit motif comme une expression régulière (voir 36.3) et rend la première correspondance, ou le texte du premier groupe capturé si le motif en a un ; NULL s'il n'y a pas de correspondance.
SELECT substring('abcdef' FROM 2 FOR 3), substring('abcdef' FROM 3), substring('abcdef' FOR 2), substr('abcdef', 4);
SELECT substring(email FROM position('@' IN email) + 1) FROM client WHERE id = 1;bcd | cdef | ab | def
exemple.frSELECT substring('abc123def' FROM '[0-9]+'), substring('abc123def' FROM 'c(\d)'), substring('abc' FROM 'z');123 | 1 | NULLUn debut négatif compte depuis la fin (substring('abcdef', -2) vaut ef), et un debut nul rend une chaîne vide : PostgreSQL compte autrement ces positions hors de la chaîne.
left et right#
left(chaine, n) right(chaine, n)Les n premiers ou derniers caractères ; avec n négatif, tout sauf les |n| derniers (left) ou premiers (right).
SELECT left('abcdef', 2), right('abcdef', 2), left('abcdef', -2), right('abcdef', -2);ab | ef | abcd | cdefposition et strpos#
position(sous_chaine IN chaine) strpos(chaine, sous_chaine) starts_with(chaine, prefixe)Position de la première occurrence, 0 si elle est absente. starts_with rend un booléen.
SELECT position('cd' IN 'abcdef'), strpos('abcdef', 'cd'), position('z' IN 'abc'), starts_with('abc', 'ab');3 | 3 | 0 | treplace, translate, overlay#
replace(chaine, recherche, remplacement)
translate(chaine, de, vers)
overlay(chaine PLACING nouveau FROM debut [FOR longueur])replaceremplace toutes les occurrences derecherche.translateremplace chaque caractère dedepar le caractère de même rang devers; un caractère dedesans correspondant est supprimé.overlayremplacelongueurcaractères (la longueur denouveaupar défaut) à partir dedebut.
SELECT replace('aXbXc', 'X', '-'), translate('12345', '143', 'ax'),
overlay('abcdef' PLACING 'ZZ' FROM 2 FOR 3), overlay('abcdef' PLACING 'ZZ' FROM 2);a-b-c | a2x5 | aZZef | aZZdeflpad, rpad, repeat, reverse#
lpad(chaine, longueur [, remplissage]) rpad(chaine, longueur [, remplissage])
repeat(chaine, n) reverse(chaine)lpad et rpad complètent à gauche ou à droite jusqu'à longueur caractères (avec des espaces par défaut) et tronquent une chaîne plus longue.
SELECT lpad('7', 3, '0'), rpad('7', 3, '*'), lpad('abcdef', 3), '[' || lpad('ab', 5) || ']', repeat('ab', 3), reverse('abc');
SELECT reference, lpad(id::text, 6, '0') AS numero, rpad(designation, 20, '.') || to_char(prix, '990.00') AS ligne
FROM article ORDER BY id;007 | 7** | abc | [ ab] | ababab | cba
reference | numero | ligne
-----------+--------+-----------------------------
STY-001 | 000001 | Stylo bille bleu.... 1.20
CAH-096 | 000002 | Cahier 96 pages..... 2.50
CLA-A4 | 000003 | Classeur A4......... 4.90
AGR-024 | 000004 | Agrafeuse........... 12.00split_part#
split_part(chaine, separateur, n)Le n-ième champ de chaine découpée par separateur ; un n négatif compte depuis la fin ; chaîne vide si le champ n'existe pas. n nul : 22023 (field position must not be zero).
SELECT split_part('a,b,c', ',', 2), split_part('a,b,c', ',', 5), split_part('a,b,c', ',', -1);
SELECT reference, split_part(reference, '-', 1) AS famille FROM article WHERE id = 1;
SELECT split_part(email, '@', 2) AS domaine FROM client WHERE id = 1;b | | c
STY-001 | STY
exemple.frascii et chr#
ascii(chaine) chr(code)chr rend le caractère du point de code Unicode code (chr(0) : 54000, null character not permitted). ascii rend le point de code Unicode du premier caractère (ascii('é') vaut 233), 0 pour une chaîne vide.
SELECT ascii('A'), ascii('é'), ascii('€uro'), chr(66), chr(233), chr(8364);65 | 233 | 8364 | B | é | €format#
format(gabarit, valeur1, valeur2, ...)Remplace dans gabarit chaque marque par l'argument suivant :
| Marque | Écrit |
|---|---|
%s | la valeur en texte ; rien pour NULL |
%I | la valeur en identifiant SQL, entre guillemets si nécessaire ; NULL : 22004 |
%L | la valeur en littéral SQL entre apostrophes ; NULL s'écrit NULL |
%% | le caractère % |
%2$s | l'argument de rang donné |
%10s, %-10s, %*s | largeur minimale, cadrée à droite, à gauche, ou lue dans l'argument précédent |
SELECT format('%s : %s €', designation, prix) FROM article WHERE id = 1;
SELECT format('%I = %L', 'nom', nom), format('%2$s %1$s', 'a', 'b'), format('[%5s|%-5s]', id, id) FROM client WHERE id = 1;Stylo bille bleu : 1.20 €
nom = 'Dupont' | b a | [ 1|1 ]Trop peu d'arguments : 22023 (too few arguments for format()). Une autre marque (%d) : 22023 (unrecognized format() type specifier).
quote_ident, quote_literal, quote_nullable#
quote_ident(texte) quote_literal(valeur) quote_nullable(valeur)Citent une valeur pour l'insérer dans une instruction SQL construite : identifiant entre guillemets si nécessaire, littéral entre apostrophes (apostrophes doublées). quote_literal(NULL) rend NULL, quote_nullable(NULL) rend le texte NULL.
SELECT quote_ident('Mon Id'), quote_ident('abc'), quote_literal('it''s'), quote_literal(42), quote_nullable(NULL);"Mon Id" | abc | 'it''s' | '42' | NULLnormalize et unistr#
normalize(chaine [, NFC | NFD | NFKC | NFKD])
unistr(chaine)normalize rend la forme normale Unicode demandée (NFC par défaut) : NFD sépare les lettres de leurs accents, NFKC et NFKD remplacent en outre les caractères de compatibilité (ligatures…). La forme s'écrit en mot-clé, sans apostrophes. unistr remplace les échappements Unicode écrits dans le texte : \XXXX et \uXXXX (4 chiffres hexadécimaux), \+XXXXXX et \UXXXXXXXX (6 et 8 chiffres) ; un échappement invalide est refusé (22025 : invalid Unicode escape).
SELECT length('é'), length(normalize('é', NFD)), normalize('fi', NFKC), unistr('caf\00e9 \+01F600');1 | 2 | fi | café 😀parse_ident#
parse_ident(nom_qualifie [, strict])Découpe un nom qualifié en ses éléments, rendus en tableau de textes (voir 36.14). Un élément sans guillemets est ramené en minuscules, un élément entre guillemets est gardé tel quel. Avec strict à false, ce qui suit le nom est ignoré ; sinon, un nom mal formé est refusé (22023 : string is not a valid identifier: "a..b").
SELECT parse_ident('ventes."Mon Client"'), parse_ident('Ventes.Client'), parse_ident('ventes.client()', false);{ventes,"Mon Client"} | {ventes,client} | {ventes,client}Comparaison approchée : levenshtein, soundex, similarity, unaccent#
Fonctions des extensions courantes de PostgreSQL (fuzzystrmatch, pg_trgm, unaccent), présentes sans installation (CREATE EXTENSION est accepté et sans effet) :
| Fonction | Rend | Type |
|---|---|---|
levenshtein(a, b [, insertion, suppression, substitution]) | nombre minimal de modifications pour passer de a à b, avec les coûts donnés (1 par défaut) | integer |
levenshtein_less_equal(a, b [, coûts…], max) | la même distance si elle ne dépasse pas max ; sinon un nombre supérieur à max | integer |
soundex(texte) | code phonétique (lettre et trois chiffres) | text |
difference(a, b) | nombre de caractères communs aux deux codes soundex, de 0 à 4 | integer |
similarity(a, b) | ressemblance par trigrammes, de 0 à 1 | real |
unaccent([dictionnaire,] texte) | le texte sans ses accents ni ligatures | text |
SELECT levenshtein('kitten', 'sitting'), levenshtein('kitten', 'sitting', 1, 1, 2), levenshtein_less_equal('kitten', 'sitting', 2),
soundex('Robert'), soundex('Rupert'), difference('Robert', 'Rupert'), similarity('word', 'words'), unaccent('Élodie à Noël');
SELECT nom FROM client WHERE levenshtein(lower(nom), 'dupond') <= 1;
SELECT nom FROM client WHERE unaccent(lower(nom)) LIKE 'elo%';3 | 5 | 3 | R163 | R163 | 4 | 0.5714286 | Elodie a Noel
Dupont
Élodie Petit36.3 Recherche de motifs#
LIKE et ILIKE#
chaine [NOT] LIKE motif [ESCAPE caractere]
chaine [NOT] ILIKE motif
chaine ~~ motif chaine ~~* motif chaine !~~ motif chaine !~~* motif% remplace une suite quelconque de caractères, _ un caractère. La barre oblique \ (ou le caractère donné par ESCAPE) retire son sens à % et _.
SELECT 'abc' LIKE 'a_c', 'a%b' LIKE 'a\%b', 'a%b' LIKE 'a#%b' ESCAPE '#', 'axb' LIKE 'a\%b';
SELECT nom FROM client WHERE nom ILIKE 'd%';t | t | t | f
DupontLIKE respecte la casse et les accents, comme = : 'ABC' LIKE 'abc' et 'é' LIKE 'e' sont faux ; ILIKE ignore les deux. Les noms des tables du catalogue (pg_class.relname, information_schema.tables.table_name…) font exception : comme tout nom de MIRAJ, ils se comparent sans égard à la casse (relname ~ '^(CLIENT)$' trouve client). Pour comparer sans égard à la casse ni aux accents, appliquer la collation utf8mb4_general_ci à la chaîne ; COLLATE "C", "POSIX" et ucs_basic comparent octet par octet, comme la collation par défaut :
SELECT 'abc' LIKE 'A%', 'abc' ILIKE 'A%', 'abc' = 'ABC', 'abc' COLLATE utf8mb4_general_ci = 'ABC', 'é' COLLATE utf8mb4_general_ci = 'e', 'abc' COLLATE "C" = 'ABC';
SELECT nom FROM client WHERE nom COLLATE utf8mb4_general_ci = 'dupont';f | t | f | t | t | f
DupontExpressions régulières : ~, ~*, !~, !~*#
| Opérateur | Sens |
|---|---|
chaine ~ motif | la chaîne contient une correspondance du motif |
chaine ~* motif | de même, casse et accents ignorés |
chaine !~ motif, chaine !~* motif | aucune correspondance |
SELECT 'a1' ~ '^[a-z]\d$', 'a b' ~ '\s', 'abc' ~ 'a|z', 'aaa' ~ '^a{3}$', 'abc' ~* 'B', 'abc' !~ 'z';
SELECT nom FROM client WHERE nom ~ '^[A-D]' ORDER BY nom;t | t | t | t | t | t
Bernard
DupontLe motif s'écrit dans une chaîne standard, où \ est un caractère ordinaire : '\d' suffit. ~ respecte la casse ('abc' ~ 'B' est faux), ~* l'ignore. Un motif invalide ('abc' ~ '(') est une erreur 2201B (invalid regular expression: error: unclosed group), comme pour les fonctions regexp_….
regexp_replace#
regexp_replace(chaine, motif, remplacement [, debut [, n]] [, drapeaux])Remplace la première correspondance du motif ; toutes avec le drapeau g ; la n-ième avec n (n = 0 : toutes), en cherchant à partir du caractère debut. Dans remplacement, \1 à \9 reprennent les groupes capturés et \& la correspondance entière. Comme ~, regexp_replace respecte la casse, sauf avec le drapeau i.
SELECT regexp_replace('abc123', '[0-9]+', '#'), regexp_replace('aaa', 'a', 'b', 'g'),
regexp_replace('Hello', 'L', 'x'), regexp_replace('Hello', 'L', 'x', 'i'),
regexp_replace('2026-10-11', '(\d+)-(\d+)-(\d+)', '\3/\2/\1'),
regexp_replace('a1b2c3', '[0-9]', '#', 1, 2),
regexp_replace(' a b ', '\s+', ' ', 'g');abc# | bbb | Hello | Hexlo | 11/10/2026 | a1b#c3 | a b Motif invalide : 2201B (invalid regular expression: error: unclosed group).
SELECT reference, regexp_replace(reference, '^([A-Z]+)-0*', '\1:') AS court FROM article ORDER BY id; reference | court
-----------+--------
STY-001 | STY:1
CAH-096 | CAH:96
CLA-A4 | CLA:A4
AGR-024 | AGR:24Drapeaux#
Les fonctions regexp_… acceptent une chaîne de drapeaux :
| Drapeau | Effet |
|---|---|
i | casse ignorée |
c | casse respectée (par défaut) |
g | toutes les correspondances : regexp_replace, regexp_matches seulement (regexp_match le refuse : 22023, regexp_match() does not support the "global" option) |
n | . ne reconnaît pas le saut de ligne, ^ et $ reconnaissent les débuts et fins de ligne |
x | syntaxe étendue : les blancs du motif sont ignorés |
Un autre drapeau est refusé (22023 : invalid regular expression option: "q"), de même qu'un debut inférieur à 1 (22023 : invalid value for parameter "start": 0).
regexp_match et regexp_matches#
regexp_match(chaine, motif [, drapeaux])
regexp_matches(chaine, motif [, drapeaux])regexp_match rend la première correspondance sous la forme d'un tableau de textes (voir 36.14) : un élément par groupe capturé, ou la correspondance entière si le motif n'a pas de groupe ; NULL s'il n'y en a pas. Un élément se lit par son rang : (regexp_match(…))[2].
regexp_matches est une fonction ensembliste : elle rend une ligne par correspondance (toutes avec le drapeau g, la première sinon), aucune ligne s'il n'y en a pas. Elle s'emploie dans un FROM ou dans la liste du SELECT.
SELECT regexp_match('Commande 2026-10-11', '(\d+)-(\d+)-(\d+)'), regexp_match('abc', 'z'), (regexp_match('ref STY-001', '([A-Z]+)-(\d+)'))[2];
SELECT regexp_matches('ab12cd34', '([a-z]+)(\d+)', 'g');{2026,10,11} | NULL | 001
{ab,12}
{cd,34}regexp_split_to_array, regexp_split_to_table, regexp_count#
regexp_split_to_array(chaine, motif [, drapeaux])
regexp_split_to_table(chaine, motif [, drapeaux])
regexp_count(chaine, motif [, debut [, drapeaux]])regexp_split_to_array découpe la chaîne à chaque correspondance du motif et rend le tableau des morceaux ; regexp_split_to_table rend les mêmes morceaux en lignes (fonction ensembliste, WITH ORDINALITY numérote les lignes). regexp_count compte les correspondances, à partir du caractère debut.
SELECT regexp_split_to_array('a, b,c', ',\s*'), regexp_count('a1b22c333', '\d+'), regexp_count('AbAb', 'a', 1, 'i');
SELECT * FROM regexp_split_to_table('Stylo bille bleu', '\s+') WITH ORDINALITY AS t(mot, rang);{a,b,c} | 3 | 2
Stylo | 1
bille | 2
bleu | 3regexp_substr, regexp_instr, regexp_like#
regexp_substr(chaine, motif [, debut [, n [, drapeaux [, groupe]]]])
regexp_instr(chaine, motif [, debut [, n [, fin [, drapeaux [, groupe]]]]])
regexp_like(chaine, motif [, drapeaux])regexp_substrrend lan-ième correspondance (la première par défaut) trouvée à partir du caractèredebut, ou le texte du groupe capturé de ranggroupe;NULLs'il n'y en a pas ;regexp_instrrend la position de cette correspondance (avecfinà 1, la position qui la suit), 0 s'il n'y en a pas ;regexp_likerend un booléen ; comme~, il respecte la casse, sauf avec le drapeaui.
SELECT regexp_substr('a1b22c333', '\d+', 1, 3), regexp_substr('STY-001', '([A-Z]+)-(\d+)', 1, 1, '', 2),
regexp_instr('a1b22c333', '\d+', 1, 2), regexp_instr('a1b22c333', '\d+', 1, 2, 1),
regexp_like('abc', 'B'), regexp_like('abc', 'B', 'i');333 | 001 | 4 | 6 | f | t36.4 Fonctions numériques#
Récapitulatif :
| Fonction | Rôle | Type du résultat |
|---|---|---|
abs(x) | valeur absolue | celui de x |
sign(x) | -1, 0 ou 1 | entier |
round(x), round(x, n) | arrondi à l'entier, à n décimales | numeric (double precision pour un flottant) |
trunc(x), trunc(x, n) | troncature vers zéro | numeric |
ceil(x), ceiling(x), floor(x) | entier supérieur, inférieur | entier pour un décimal (double precision pour un flottant) |
mod(a, b), a % b | reste de la division | integer ou numeric |
div(a, b), a DIV b | quotient entier | numeric ; integer pour DIV |
power(a, b), pow(a, b), a ^ b | puissance | double precision ou numeric (voir 36.1) |
sqrt(x), cbrt(x), exp(x), ln(x), log(x), log10(x), log2(x), log(b, x) | racines carrée et cubique, exponentielle, logarithmes | double precision |
pi(), degrees(x), radians(x) | π, conversions d'angles | double precision |
sin, cos, tan, cot, asin, acos, atan, atan2(y, x) | trigonométrie, en radians | double precision |
gcd(a, b), lcm(a, b) | PGCD, PPCM | integer |
factorial(n) | factorielle | numeric |
width_bucket(x, bas, haut, n) | rang de l'intervalle qui contient x | integer |
scale(x), min_scale(x), trim_scale(x) | échelle d'un numeric, échelle sans zéros finals, valeur sans zéros finals | integer, integer, numeric |
greatest(a, b, …), least(a, b, …) | plus grande, plus petite valeur | type commun |
random(), random_normal([moyenne [, ecart_type]]) | nombre aléatoire dans [0, 1[, selon une loi normale | double precision |
setseed(x) | amorce le générateur aléatoire de la session (x de -1 à 1) |
abs, sign#
SELECT abs(-12.50), abs(-3), sign(-12.5), sign(0), sign(7);12.50 | 3 | -1 | 0 | 1round, trunc, ceil, floor#
round(x [, n]) trunc(x [, n]) ceil(x) ceiling(x) floor(x)round arrondit un numeric au plus proche, la demie s'éloignant de zéro ; n négatif arrondit à gauche de la virgule. Un double precision sans n est arrondi à l'entier pair le plus proche, comme dans PostgreSQL.
SELECT round(2.5), round(-2.5), round(2.567, 2), round(1250, -2), round(2.5::float8), round(3.5::float8);
SELECT trunc(2.567, 2), trunc(-2.567), trunc(1299, -2), ceil(2.1), ceil(-2.1), floor(2.9), floor(-2.1);3 | -3 | 2.57 | 1300 | 2 | 4
2.56 | -2 | 1200 | 3 | -2 | 2 | -3Exemple sur la base : montant total, total majoré de 20 % et prix moyen des lignes de commande.
SELECT sum(quantite * prix_unitaire) AS total,
round(sum(quantite * prix_unitaire) * 1.2, 2) AS ttc,
round(avg(prix_unitaire), 2) AS prix_moyen
FROM ligne_commande; total | ttc | prix_moyen
--------+--------+------------
256.10 | 307.32 | 4.03mod, div#
SELECT mod(17, 5), mod(-17, 5), mod(17.5, 5), 17 % 5, div(17, 5), div(-17, 5), 17 DIV 5;2 | -2 | 2.5 | 2 | 3 | -3 | 3power, sqrt, exp, logarithmes#
SELECT power(2, 10), power(2.5, 2), 10 ^ -2, sqrt(2), exp(1), ln(exp(1)), log(1000), log(2, 1024), log10(1000), log2(1024);1024 | 6.2500000000000000 | 0.01 | 1.4142135623730951 | 2.718281828459045 | 1 | 3 | 10 | 3 | 10SELECT cbrt(27), cbrt(-8), scale(1.230), min_scale(1.230), trim_scale(1.230), pow(2, 3);3 | -2 | 3 | 2 | 1.23 | 8log(x) est le logarithme décimal, ln(x) le logarithme népérien, log(b, x) le logarithme de base b. Hors de leur domaine, sqrt(-1), ln(0), log(-1), asin(2) et acos(2) rendent NULL (PostgreSQL signale une erreur).
Trigonométrie#
Angles en radians.
SELECT pi(), degrees(pi()), radians(180), sin(pi() / 2), cos(0), atan2(1, 1);3.141592653589793 | 180 | 3.141592653589793 | 1 | 1 | 0.7853981633974483gcd, lcm, factorial, width_bucket#
SELECT gcd(12, 18), lcm(4, 6), factorial(5), factorial(20);
SELECT width_bucket(5.5, 0, 10, 5), width_bucket(-1, 0, 10, 5), width_bucket(10, 0, 10, 5);6 | 12 | 120 | 2432902008176640000
3 | 0 | 6width_bucket(x, bas, haut, n) découpe [bas, haut[ en n intervalles égaux et rend le rang de celui qui contient x : 0 en dessous de bas, n + 1 à partir de haut. factorial(-1) : 2201E (factorial of a negative number is undefined).
greatest, least#
greatest(valeur1, valeur2, ...) least(valeur1, valeur2, ...)Plus grande et plus petite des valeurs (nombres, textes, dates). Un argument NULL rend NULL (PostgreSQL ignore les NULL).
SELECT greatest(3, 7, 5), least(3, 7, 5), greatest('abc', 'abd'), least(date '2026-01-01', date '2025-01-01'), greatest(1, NULL);7 | 3 | abd | 2025-01-01 | NULLrandom#
random() rend un double precision aléatoire, supérieur ou égal à 0 et inférieur à 1. Pour un entier de 0 à 9 : floor(random() * 10)::int. random_normal(moyenne, ecart_type) tire un nombre selon une loi normale (0 et 1 par défaut). setseed(x) réamorce le générateur de la session : les tirages qui suivent sont alors reproductibles. gen_random_uuid() est décrite en 36.13.
SELECT round(random_normal(100, 0)), random_normal() IS NOT NULL;100 | tHors de [-1, 1], setseed est refusée (22023 : setseed parameter 2 is out of allowed range [-1,1]).
36.5 Dates, heures et intervalles#
Les types date, time, timestamp (sans fuseau) et timestamptz (un instant, affiché dans le fuseau de la session) sont décrits au chapitre 32. Types de données ; SET TIME ZONE au chapitre 37. Transactions et session. Les exemples de cette section qui affichent un timestamptz ont été exécutés après :
SET TIME ZONE 'Europe/Paris';Date et heure courantes#
| Expression | Rend | Type |
|---|---|---|
now(), current_timestamp, transaction_timestamp() | date et heure du début de la transaction (de l'instruction, hors d'un bloc) | timestamptz |
statement_timestamp(), clock_timestamp() | date et heure du début de l'instruction ; de l'horloge à chaque appel | timestamptz |
current_timestamp(n) | de même, à n décimales de seconde | timestamptz |
localtimestamp, localtimestamp(n) | date et heure courantes de la session, sans fuseau | timestamp |
current_date | date du jour | date |
current_time, current_time(n) | heure courante, sans décalage | time |
localtime, localtime(n) | heure courante de la session | time |
timeofday() | date et heure de l'horloge, en texte | text |
SELECT now(), localtimestamp, current_date, current_time;2026-10-11 21:36:38+02 | 2026-10-11 21:36:38 | 2026-10-11 | 21:36:38Littéraux#
SELECT date '2026-10-11', time '14:30:00', timestamp '2026-10-11 14:30:00',
timestamptz '2026-10-11 14:30:00+02', '2026-10-11'::date, '2026-10-11T12:30:00Z'::timestamptz;2026-10-11 | 14:30:00 | 2026-10-11 14:30:00 | 2026-10-11 14:30:00+02 | 2026-10-11 | 2026-10-11 14:30:00+02Les dates s'écrivent au format ISO (AAAA-MM-JJ). Les mots 'today', 'tomorrow', 'yesterday', 'now' et 'epoch' sont lus comme dans PostgreSQL. Un texte qui n'est pas une date valide est refusé ('2026-02-30'::date, '11/10/2026'::date : 22008, date/time field value out of range: "2026-02-30") : pour lire un autre format, employer to_date (36.6).
SELECT 'today'::date = current_date, 'tomorrow'::date - 'yesterday'::date, 'epoch'::timestamp;t | 2 | 1970-01-01 00:00:00Arithmétique#
| Opération | Résultat | Exemple | Valeur |
|---|---|---|---|
date + entier, date - entier | date | date '2026-10-11' + 7 | 2026-10-18 |
date - date | nombre de jours (integer) | date '2026-12-25' - date '2026-10-11' | 75 |
date + time | timestamp | date '2026-10-11' + time '14:30' | 2026-10-11 14:30:00 |
date ± interval | timestamp | date '2026-01-31' + INTERVAL '1 month' | 2026-02-28 00:00:00 |
timestamp ± interval | timestamp | timestamp '2026-10-11 14:30:00' - INTERVAL '1 week' | 2026-10-04 14:30:00 |
timestamptz ± interval | timestamptz | now() + INTERVAL '2 hours' | |
time ± interval | time, ramené sur 24 heures | time '23:30' + INTERVAL '2 hours' | 01:30:00 |
time - time | interval | time '16:00' - time '14:30' | 01:30:00 |
timestamp - timestamp | interval | timestamp '2026-10-13 16:30:00' - timestamp '2026-10-11 14:30:00' | 2 days 02:00:00 |
Un mois ajouté à une fin de mois donne la fin du mois suivant (2026-01-31 + 1 mois = 2026-02-28). Un nombre ajouté à un timestamp, ou un nombre décimal ajouté à une date, est refusé (42883 : operator does not exist: date + numeric) : ajouter un intervalle.
SELECT id, date_commande, livree_le,
livree_le - date_commande AS delai,
livree_le::date - date_commande AS jours
FROM commande WHERE livree_le IS NOT NULL ORDER BY id; id | date_commande | livree_le | delai | jours
----+---------------+---------------------+-----------------+-------
1 | 2026-09-28 | 2026-10-01 10:15:00 | 3 days 10:15:00 | 3
3 | 2026-10-06 | 2026-10-08 16:40:00 | 2 days 16:40:00 | 2timestamptz et changement d'heure. Un intervalle en jours, semaines, mois ou années garde l'heure civile de la session ; un intervalle en heures, minutes ou secondes compte le temps écoulé. La différence de deux timestamptz est la durée réelle entre les deux instants. Autour du passage à l'heure d'hiver du 25 octobre 2026 :
SELECT timestamptz '2026-10-25 00:30:00+02' + INTERVAL '1 day' AS un_jour,
timestamptz '2026-10-25 00:30:00+02' + INTERVAL '24 hours' AS vingt_quatre_heures,
timestamptz '2026-10-26 00:30:00+01' - timestamptz '2026-10-25 00:30:00+02' AS ecart; un_jour | vingt_quatre_heures | ecart
------------------------+------------------------+----------------
2026-10-26 00:30:00+01 | 2026-10-25 23:30:00+01 | 1 day 01:00:00Intervalles#
Un intervalle s'écrit INTERVAL 'quantité unité …' ou '…'::interval (aussi col::interval, $1::interval). Comme en PostgreSQL, il garde à part ses mois, ses jours et sa durée, s'écrit 1 mon 2 days 03:00:00 et est annoncé interval (OID 1186) au client ; une colonne peut être de type interval (le texte écrit y est contrôlé, 22007, et réécrit : '25 hours' devient 25:00:00).
| Écriture | Acceptée |
|---|---|
| unités | microsecond(s), millisecond(s), second(s) / sec, minute(s) / min, hour(s) / h, day(s) / d, week(s), month(s) / mon, year(s) |
| plusieurs unités | '2 hours 30 minutes', '1 day 02:00:00', '1 year 2 months', '2 weeks 3 days' |
| heure | '01:30', '1:30:15.5' |
| quantité décimale ou négative | '1.5 hours', '-1 day', '1 day ago' |
| forme du standard | INTERVAL '3' DAY (après une date : d + INTERVAL '3' DAY) |
| ISO 8601 | 'P1D', 'P1Y2M3DT4H5M6S' (M : mois avant le T, minutes après), 'PT1.5S', 'P2W', 'P0001-02-03T04:05:06' |
SELECT timestamp '2026-10-11 14:30:00' + INTERVAL '90 minutes',
timestamp '2026-10-11 14:30:00' + INTERVAL '1 day 2 hours',
timestamp '2026-10-11 14:30:00' + INTERVAL '1 year 2 months',
timestamp '2026-10-11 14:30:00' + INTERVAL '500 milliseconds',
timestamp '2026-10-11 14:30:00' + '2 hours 30 minutes'::interval;2026-10-11 16:00:00 | 2026-10-12 16:30:00 | 2027-12-11 14:30:00 | 2026-10-11 14:30:00.5 | 2026-10-11 17:00:00Écrit seul dans une liste de SELECT, un intervalle rend son texte, à la manière de PostgreSQL :
SELECT INTERVAL '45 days', INTERVAL '2 hours 30 minutes', INTERVAL '1 year 2 months', INTERVAL '1 week',
INTERVAL '1 day 12 hours', INTERVAL '1 month 1 day', INTERVAL '1.5 days';45 days | 02:30:00 | 1 year 2 mons | 7 days | 1 day 12:00:00 | 1 mon 1 day | 1 day 12:00:00| Opération | Résultat | Exemple | Valeur |
|---|---|---|---|
interval ± interval | champ par champ | INTERVAL '1 mon' - INTERVAL '1 day' | 1 mon -1 days |
interval * n, n * interval, interval / n, -interval | chaque champ, la fraction d'un mois passant en jours de 30, celle d'un jour en heures | INTERVAL '1 mon' * 1.5 | 1 mon 15 days |
=, <>, <, >… | par durée (mois de 30 jours, jour de 24 heures) | INTERVAL '1 day' = INTERVAL '24 hours' | t |
ORDER BY, BETWEEN, IN, greatest, least | par durée (greatest et least ignorent les NULL) | INTERVAL '1 day' IN ('24:00:00') | t |
DISTINCT, GROUP BY (ROLLUP, CUBE, GROUPING SETS compris), count(DISTINCT …), jointure | par durée : '1 day' et '24:00:00' sont une même valeur, la valeur rendue est celle d'une des lignes | ||
UNION, INTERSECT, EXCEPT (WITH RECURSIVE … UNION compris) | par durée ; UNION garde la première écriture rencontrée | SELECT INTERVAL '1 day' UNION SELECT INTERVAL '24:00:00' | 1 day |
clé primaire, UNIQUE, index unique | par durée : '24:00:00' est refusé (23505) si '1 day' existe ; une clé étrangère vers la colonne suit la même égalité | ||
min, max | par durée | max(duree) | 1 mon |
sum, avg | champ par champ ; avg divise comme interval / n (sum(DISTINCT …), avg(DISTINCT …) : 0A000) | avg(duree) | 10 days 17:00:00 |
Un texte face à un intervalle est lu en intervalle (WHERE duree > '1 day') ; interval / 0 : 22012 ; interval + 1 : 42883. justify_*, age et timestamp - timestamp rendent un interval.
Dans un UNION, INTERSECT ou EXCEPT, dans le terme récursif d'un WITH RECURSIVE comme dans une liste VALUES, un texte écrit sans type face à un intervalle est lu en intervalle lui aussi (SELECT INTERVAL '1 day' UNION SELECT '24:00:00' : une ligne 1 day, de type interval) ; illisible, il est refusé (22007). Voir 5.8.
extract et date_part#
extract(champ FROM source)
date_part('champ', source)Rendent un champ d'une date, d'un timestamp, d'un timestamptz ou d'un time. extract rend un numeric, date_part un double precision. Le champ peut s'écrire entre apostrophes dans extract (extract('month' FROM d)).
| Champ | Valeur | Pour 2026-10-11 14:30:45.5 (un dimanche) |
|---|---|---|
year, month, day | année, mois, jour | 2026, 10, 11 |
hour, minute | heure, minute | 14, 30 |
second | secondes avec leur fraction | 45.500000 |
milliseconds, microseconds | secondes en millisecondes, en microsecondes | 45500.000, 45500000 |
dow | jour de la semaine, 0 = dimanche | 0 |
isodow | jour de la semaine ISO, 1 = lundi, 7 = dimanche | 7 |
doy | jour de l'année | 284 |
week | semaine ISO | 41 |
isoyear | année de la semaine ISO | 2026 |
quarter | trimestre | 4 |
decade, century, millennium | décennie, siècle, millénaire | 202, 21, 3 |
julian | jour julien | 2461325 |
epoch | secondes depuis le 1er janvier 1970 à 00:00 UTC | voir ci-dessous |
SELECT extract(year FROM date '2026-10-11'), extract(second FROM timestamp '2026-10-11 14:30:45.5'),
extract(dow FROM date '2026-10-11'), extract(week FROM date '2027-01-01'), extract(isoyear FROM date '2027-01-01'),
date_part('hour', timestamp '2026-10-11 14:30:45.5'), extract(hour FROM time '14:30:45');2026 | 45.500000 | 0 | 53 | 2026 | 14 | 14epoch compte l'instant exact d'un timestamptz ; une date ou un timestamp sans fuseau est lu en UTC, comme dans PostgreSQL. Le résultat est entier pour une date, à six décimales sinon :
SELECT extract(epoch FROM timestamptz '2026-10-11 14:30:00+02') AS instant,
extract(epoch FROM timestamp '2026-10-11 14:30:00') AS sans_fuseau,
extract(epoch FROM date '2026-10-11') AS jour; instant | sans_fuseau | jour
-------------------+-------------------+------------
1791721800.000000 | 1791729000.000000 | 1791676800D'un intervalle, extract lit ses champs comme PostgreSQL : hour n'est pas ramené à la journée (extract(hour FROM INTERVAL '26 hours') vaut 26), epoch compte le mois pour 30 jours et l'année pour 365,25 (extract(epoch FROM a - b) : la durée en secondes). Pour une durée entre deux instants, on peut aussi soustraire les deux epoch :
SELECT id, (extract(epoch FROM livree_le) - extract(epoch FROM date_commande)) / 3600 AS heures
FROM commande WHERE livree_le IS NOT NULL ORDER BY id; id | heures
----+---------------------
1 | 82.2500000000000000
3 | 64.6666666666666667extract(timezone FROM …) : 0A000 (EXTRACT(timezone FROM …) is not supported yet in the PostgreSQL language).
date_trunc#
date_trunc('champ', source)Ramène source au début de l'unité : microseconds, milliseconds, second, minute, hour, day, week (le lundi), month, quarter, year, decade, century, millennium. Le résultat est un timestamp pour une date ou un timestamp, un timestamptz pour un timestamptz (tronqué dans le fuseau de la session). Unité inconnue : 22023 (unit "fortnight" not recognized for type timestamp without time zone).
SELECT date_trunc('month', timestamp '2026-10-11 14:30:45'), date_trunc('week', timestamp '2026-10-11 14:30:45'),
date_trunc('hour', timestamp '2026-10-11 14:30:45'), date_trunc('quarter', date '2026-08-15')::date;2026-10-01 00:00:00 | 2026-10-05 00:00:00 | 2026-10-11 14:00:00 | 2026-07-01Nombre de commandes par mois :
SELECT date_trunc('month', date_commande)::date AS mois, count(*) AS commandes
FROM commande GROUP BY 1 ORDER BY 1; mois | commandes
------------+-----------
2026-09-01 | 1
2026-10-01 | 3Dernier jour du mois d'une date :
SELECT ((date_trunc('month', date '2026-02-10') + INTERVAL '1 month') - INTERVAL '1 day')::date; -- 2026-02-28make_date, make_time, make_timestamp, make_timestamptz, make_interval#
make_date(annee, mois, jour)
make_time(heure, minute, seconde)
make_timestamp(annee, mois, jour, heure, minute, seconde)
make_timestamptz(annee, mois, jour, heure, minute, seconde [, fuseau])
make_interval([years [, months [, weeks [, days [, hours [, mins [, secs]]]]]]])Construisent une date, un time, un timestamp, un timestamptz (heure lue dans fuseau, celui de la session par défaut) ou un interval ; la seconde peut avoir une fraction. Les arguments s'écrivent aussi en notation nommée (make_date(year => 2026, month => 3, day => 1), make_interval(days => 3, hours => 2)) ; ceux de make_interval omis valent 0. Une valeur hors du calendrier est une erreur 22008 (date field value out of range: 2026-02-30, time field value out of range: 25:00:00).
SELECT make_date(2026, 2, 28), make_time(10, 30, 15.5), make_timestamp(2026, 2, 28, 10, 30, 0);
SELECT make_timestamptz(2026, 10, 11, 14, 30, 0), make_timestamptz(2026, 10, 11, 14, 30, 0, 'UTC');
SELECT make_interval(days => 3, hours => 2), make_interval(1, 2), make_interval(weeks => 2, secs => 1.5);2026-02-28 | 10:30:15.5 | 2026-02-28 10:30:00
2026-10-11 14:30:00+02 | 2026-10-11 16:30:00+02
3 days 02:00:00 | 1 year 2 mons | 14 days 00:00:01.5date_bin#
date_bin(pas, source, origine)Ramène source (un timestamp ou un timestamptz) au début de la tranche de longueur pas qui le contient, les tranches étant comptées à partir de origine. Le pas ne contient ni mois ni années (22023 : timestamps cannot be binned into intervals containing months or years) et il est positif (22023 : stride must be greater than zero).
SELECT date_bin('15 minutes', timestamp '2026-10-11 14:38:20', timestamp '2026-01-01'),
date_bin('1 hour', timestamp '2026-10-11 14:38:20', timestamp '2026-01-01 00:30');2026-10-11 14:30:00 | 2026-10-11 14:30:00date_add, date_subtract, timezone#
date_add(instant, intervalle [, fuseau])
date_subtract(instant, intervalle [, fuseau])
timezone(zone, valeur)date_add et date_subtract ajoutent ou retirent un intervalle à un timestamptz ; les jours et les mois se comptent en heure civile du fuseau (celui de la session par défaut), les heures en temps écoulé. timezone(zone, valeur) est la forme fonction de valeur AT TIME ZONE zone (voir plus bas).
SELECT date_add(timestamptz '2026-10-24 12:00+02', interval '1 day'), date_add(timestamptz '2026-10-24 12:00+02', interval '24 hours'),
date_subtract(timestamptz '2026-10-25 12:00+01', interval '1 day', 'UTC');
SELECT timezone('America/New_York', timestamptz '2026-10-11 14:30:00+02'), timezone('UTC', timestamp '2026-10-11 14:30:00');2026-10-25 12:00:00+01 | 2026-10-25 11:00:00+01 | 2026-10-24 13:00:00+02
2026-10-11 08:30:00 | 2026-10-11 16:30:00+02isfinite(date | timestamp | interval) rend t : une date, une heure ou un intervalle est toujours fini ('infinity'::timestamp est refusé, 22007).
age, justify_days, justify_hours, justify_interval#
age(fin, debut) age(date_ou_timestamp)
justify_days(intervalle) justify_hours(intervalle) justify_interval(intervalle)age(fin, debut) rend l'écart en années, mois et jours, sous la forme d'un interval ; age(t) compte depuis la date du jour. justify_days regroupe les jours par mois de 30 jours, justify_hours les heures par jours de 24 heures, justify_interval fait les deux.
SELECT age(date '2026-10-11', date '1990-05-20'), age(timestamp '2026-10-11 10:00', timestamp '2026-10-11 08:30'),
age(timestamp '2024-02-29', timestamp '2026-10-11');
SELECT justify_days(INTERVAL '45 days'), justify_hours(INTERVAL '50 hours'), justify_interval(INTERVAL '400 days');36 years 4 mons 22 days | 01:30:00 | -2 years -7 mons -11 days
1 mon 15 days | 2 days 02:00:00 | 1 year 1 mon 10 daysLe nombre d'années se lit par extract :
SELECT extract(year FROM age(date '2026-10-11', date '1990-05-20')), pg_typeof(age(date '2026-10-11', date '1990-05-20'));36 | intervalAT TIME ZONE#
valeur AT TIME ZONE 'zone'- un
timestamptzdevient letimestampde l'heure civile dezone; - un
timestamp(sans fuseau) est lu comme une heure dezoneet devient untimestamptz.
zone est un nom de fuseau ('Europe/Paris', 'UTC', 'America/New_York') ou un décalage ; un décalage écrit ('+02') se lit à l'ouest de Greenwich (notation POSIX), comme dans PostgreSQL. Fuseau inconnu : 22023 (time zone "Mars/Olympus" not recognized).
SELECT timestamptz '2026-10-11 14:30:00+02' AT TIME ZONE 'America/New_York' AS a_new_york,
timestamptz '2026-07-14 12:00:00+02' AT TIME ZONE 'UTC' AS en_utc,
timestamp '2026-10-11 14:30:00' AT TIME ZONE 'UTC' AS instant; a_new_york | en_utc | instant
---------------------+---------------------+------------------------
2026-10-11 08:30:00 | 2026-07-14 10:00:00 | 2026-10-11 16:30:00+02to_timestamp (secondes)#
to_timestamp(secondes) rend le timestamptz situé à ce nombre de secondes après le 1er janvier 1970 à 00:00 UTC ; c'est l'inverse de extract(epoch FROM …).
SELECT to_timestamp(1760000000), to_timestamp(0) AT TIME ZONE 'UTC';2025-10-09 10:53:20+02 | 1970-01-01 00:00:0036.6 Mise en forme : to_char, to_date, to_timestamp, to_number#
to_char d'une date ou d'une heure#
to_char(date_ou_heure, modele)Écrit une date, un timestamp, un timestamptz (dans le fuseau de la session) ou un time selon modele.
| Motif | Écrit | Pour 2026-10-11 14:05:09.123456 |
|---|---|---|
YYYY, YYY, YY, Y | année, ses 3, 2 ou 1 derniers chiffres | 2026, 026, 26, 6 |
IYYY | année de la semaine ISO | 2026 |
MM | mois, 01 à 12 | 10 |
Month, MONTH, month | nom du mois en anglais, complété à 9 caractères | October |
Mon, MON, mon | nom abrégé | Oct |
DD | jour du mois, 01 à 31 | 11 |
DDD | jour de l'année | 284 |
Day, DAY, day | nom du jour en anglais, complété à 9 caractères | Sunday |
Dy, DY, dy | nom abrégé | Sun |
D | jour de la semaine, 1 = dimanche | 1 |
ID | jour de la semaine ISO, 1 = lundi | 7 |
IW, WW | semaine ISO, semaine de l'année | 41, 41 |
Q | trimestre | 4 |
CC | siècle | 21 |
J | jour julien | 2461325 |
HH24 | heure, 00 à 23 | 14 |
HH12, HH | heure, 01 à 12 | 02 |
MI, SS | minutes, secondes | 05, 09 |
SSSS | secondes depuis minuit | 50709 |
MS, US | millisecondes, microsecondes | 123, 123456 |
AM, PM | indicateur de demi-journée | PM |
TH, th (après un nombre) | suffixe ordinal anglais | 11TH, 11th |
FM (devant un motif) | sans zéros de tête ni espaces de complément | FMDD : 11, FMMonth : October |
"texte" | le texte tel quel |
Tout autre caractère est recopié.
SELECT to_char(timestamp '2026-10-11 14:05:09', 'YYYY-MM-DD HH24:MI:SS'),
to_char(timestamp '2026-10-11 14:05:09', 'DD/MM/YYYY HH12:MI AM'),
to_char(timestamp '2026-10-11 14:05:09', 'FMDay, FMDD FMMonth YYYY'),
to_char(date '2026-03-01', 'FMDD/FMMM/YYYY'),
to_char(time '14:05:09', 'HH24hMI');2026-10-11 14:05:09 | 11/10/2026 02:05 PM | Sunday, 11 October 2026 | 1/3/2026 | 14h05SELECT nom, to_char(cree_le, 'DD/MM/YYYY à HH24"h"MI') AS cree, to_char(cree_le, 'YYYY"-T"Q') AS trimestre
FROM client ORDER BY id; nom | cree | trimestre
--------------+--------------------+-----------
Dupont | 15/01/2026 à 09h30 | 2026-T1
Martin | 03/02/2026 à 14h05 | 2026-T1
Bernard | 21/03/2026 à 08h00 | 2026-T1
Élodie Petit | 02/04/2026 à 17h45 | 2026-T2Les noms de jours et de mois sont en anglais. Motifs de plus :
| Motif | Sens |
|---|---|
FF1 à FF6 | fraction de seconde, de 1 à 6 chiffres (FF3 : millisecondes) |
RM, rm | mois en chiffres romains (XI), complété à 4 caractères sans FM |
TZ, tz | abréviation du fuseau de la session pour un timestamptz (CEST, UTC ; le décalage, +05:30, pour un fuseau sans nom ou celui de la machine) ; vide pour un timestamp |
OF | décalage du fuseau par rapport à UTC (+02, +05:30) ; +00 pour un timestamp |
TZH, TZM | heures et minutes du décalage (+02, 00) |
W | semaine du mois, de 1 à 5 (la première commence le 1er) |
TM (devant Day, Month…) | nom traduit dans la langue de lc_time ; celle-ci vaut C : noms anglais |
SET TIME ZONE 'Europe/Paris';
SELECT to_char(timestamptz '2026-07-04 05:06:07+00', 'HH24:MI TZ OF'),
to_char(timestamp '2026-03-04 05:06:07.123456', 'SS.FF3'), to_char(date '2026-11-04', 'FMRM');
SELECT to_char(timestamptz '2026-07-04 05:06:07+00', 'HH24:MI TZH:TZM'), to_char(date '2026-10-11', 'W'), to_char(date '2026-10-11', 'TMDay TMMonth');07:06 CEST +02 | 07.123 | XI
07:06 +02:00 | 2 | Sunday Octoberto_date et to_timestamp ne lisent pas FF1…FF6, RM, TZ ni OF.
to_char d'un intervalle#
to_char(intervalle, modele)Mêmes motifs que pour une date : YYYY, MM (années et mois tirés des mois), DD (jours), DDD (jours, mois comptés pour 30), HH24 (heures non ramenées à la journée : 50 hours donne 50), HH12 / HH, MI, SS, MS, US, SSSS (secondes de la durée), CC, Q, WW, avec FM, TH et le texte entre guillemets. Un champ négatif garde son signe (-05). Les noms de mois et de jours, AM / PM, D, ID, IW, IYYY et J n'ont pas de sens pour un intervalle : 22007 (invalid format specification for an interval value).
SELECT to_char(interval '15h 2m 12s', 'HH24:MI:SS'), to_char(interval '50 hours 5.25 seconds', 'HH24 HH12 MI SS MS'),
to_char(interval '1 year 2 months 3 days', 'YYYY-MM-DD'), to_char(interval 'P2DT3H', 'FMDD "jours" FMHH24 "h"');15:02:12 | 50 02 00 05 250 | 0001-02-03 | 2 jours 3 hto_char d'un nombre#
to_char(nombre, modele)| Motif | Sens |
|---|---|
9 | un chiffre ; un zéro non significatif est remplacé par un espace |
0 | un chiffre ; les zéros de tête sont écrits |
., D | séparateur décimal (un point) |
,, G | séparateur de milliers (une virgule) |
S | signe + ou -, à la place écrite |
MI | signe - en fin pour un nombre négatif |
L | symbole monétaire de lc_monetary ; celui-ci vaut C : un espace |
PR | nombre négatif entre chevrons (<485>), positif entre deux espaces |
RN, rn | chiffres romains (majuscules, minuscules) de l'entier arrondi, de 1 à 3999, sur 15 positions alignées à droite ; hors de ces bornes : 15 # |
EEEE | notation scientifique, avec autant de décimales que le modèle (9.99EEEE) |
V | nombre multiplié par 10 puissance le nombre de chiffres qui suivent V (99V9) |
FM | sans espaces de tête ni zéros de fin |
Sans FM, le résultat commence par la place du signe : un espace pour un nombre positif. Un nombre nul s'écrit 0 dans un modèle sans décimales (to_char(0, '99') : ' 0') ; devant des décimales, le zéro de la partie entière n'est pas écrit (to_char(0.5, '99.99') : ' .50'), comme PostgreSQL.
SELECT '[' || to_char(1234.5, '9999.99') || ']', '[' || to_char(1234.5, 'FM9999.00') || ']',
'[' || to_char(12, '9999') || ']', '[' || to_char(12, 'FM9999') || ']',
to_char(7, 'FM000'), to_char(-7, '000'), to_char(1234567.891, 'FM9,999,999.99'),
to_char(12.5, 'FMS999.9'), to_char(0.126, 'FM0.00');[ 1234.50] | [1234.50] | [ 12] | [12] | 007 | -007 | 1,234,567.89 | +12.5 | 0.13SELECT reference, to_char(prix, 'FM9990.00') AS prix, to_char(prix * stock, 'FM999G990D00') AS valeur_stock,
to_char(stock, 'FM000000') AS stock
FROM article ORDER BY id; reference | prix | valeur_stock | stock
-----------+-------+--------------+--------
STY-001 | 1.20 | 600.00 | 000500
CAH-096 | 2.50 | 300.00 | 000120
CLA-A4 | 4.90 | 171.50 | 000035
AGR-024 | 12.00 | 0.00 | 000000Le nombre est arrondi au nombre de décimales du modèle. Un nombre trop grand pour le modèle s'écrit en # (to_char(12345, '999') rend ###).
SELECT '[' || to_char(-485, '999PR') || ']', to_char(485, 'FMRN'), to_char(1994, 'FMrn'),
'[' || to_char(0.0004859, '9.99EEEE') || ']', '[' || to_char(-1234.5, '9.99EEEE') || ']',
'[' || to_char(12.45, '99V9') || ']';[<485>] | CDLXXXV | mcmxciv | [ 4.86e-04] | [-1.23e+03] | [ 125]Le motif SG n'est pas reconnu, ni to_char à un seul argument.
to_number#
to_number(texte, modele)Lit un nombre écrit selon un modèle fait des motifs de to_char (9, 0, . / D, , / G, S, MI, L) ; les séparateurs de milliers et le symbole monétaire sont sautés. Le résultat est un numeric.
SELECT to_number('12,454.8-', '99G999D9S'), to_number('$1,234.50', 'L9,999.99'), to_number('-42', '999'), pg_typeof(to_number('1', '9'));-12454.8 | 1234.50 | -42 | numericUn texte sans chiffre lisible est refusé (22P02) ; le motif EEEE n'est pas admis en lecture (0A000 : "EEEE" not supported for input, comme PostgreSQL). Pour un texte au format décimal ordinaire, la conversion ::numeric suffit (voir 36.11).
to_date et to_timestamp#
to_date(texte, modele)
to_timestamp(texte, modele)Lisent un texte selon un modèle écrit avec les motifs de to_char. to_date rend une date, to_timestamp un timestamptz (l'heure lue est celle du fuseau de la session).
SELECT to_date('11/10/2026', 'DD/MM/YYYY'), to_date('20261011', 'YYYYMMDD'), to_date('11 oct 2026', 'DD Mon YYYY'),
to_date('11 October 2026', 'DD Month YYYY'), to_date('11.10.26', 'DD.MM.YY'), to_date('2026-284', 'YYYY-DDD');
SELECT to_timestamp('2026-10-11 14:30:45', 'YYYY-MM-DD HH24:MI:SS'),
to_timestamp('11/10/2026 02:30 PM', 'DD/MM/YYYY HH12:MI AM');2026-10-11 | 2026-10-11 | 2026-10-11 | 2026-10-11 | 2026-10-11 | 2026-10-11
2026-10-11 14:30:45+02 | 2026-10-11 14:30:00+02Un champ absent prend sa valeur de début (to_date('10/2026', 'MM/YYYY') rend 2026-10-01). Erreurs :
| Instruction | Réponse |
|---|---|
SELECT to_date('31/02/2026', 'DD/MM/YYYY') | 22008 : date/time field value out of range: "31/02/2026" |
SELECT to_date('abc', 'DD/MM/YYYY') | 22007 : invalid value "ab" for "DD" |
SELECT to_timestamp('2026-10-11 25:00', 'YYYY-MM-DD HH24:MI') | 22008 : date/time field value out of range: "2026-10-11 25:00" |
36.7 Expressions conditionnelles#
CASE#
CASE WHEN condition THEN resultat [WHEN ...] [ELSE resultat] END
CASE expression WHEN valeur THEN resultat [WHEN ...] [ELSE resultat] ENDRend le résultat de la première branche vraie, celui de ELSE sinon, NULL sans ELSE.
SELECT reference, stock,
CASE WHEN stock = 0 THEN 'rupture' WHEN stock < 50 THEN 'faible' ELSE 'ok' END AS etat
FROM article ORDER BY id; reference | stock | etat
-----------+-------+---------
STY-001 | 500 | ok
CAH-096 | 120 | ok
CLA-A4 | 35 | faible
AGR-024 | 0 | ruptureLa forme CASE expression WHEN valeur compare avec =, donc octet par octet pour un texte : CASE ville WHEN 'lyon' THEN 1 ELSE 0 END vaut 0 pour Lyon.
Le type du résultat est le type commun des branches : integer et numeric donnent un numeric. Des branches de familles différentes (CASE WHEN … THEN 1 ELSE 'a' END) sont rendues en texte, là où PostgreSQL refuse la requête.
coalesce#
coalesce(valeur1, valeur2, ...)La première valeur qui n'est pas NULL ; NULL si toutes le sont.
SELECT nom, coalesce(ville, 'inconnue') AS ville FROM client ORDER BY id; nom | ville
--------------+----------
Dupont | Lyon
Martin | Paris
Bernard | Lyon
Élodie Petit | inconnuenullif#
nullif(a, b)NULL si a = b, a sinon. Sert à éviter une division par zéro :
SELECT reference, prix / nullif(stock, 0) AS prix_par_unite_en_stock FROM article ORDER BY id; reference | prix_par_unite_en_stock
-----------+-------------------------
STY-001 | 0.0024000000000000
CAH-096 | 0.0208333333333333
CLA-A4 | 0.1400000000000000
AGR-024 | NULLLa comparaison respectant la casse, nullif('a', 'A') rend a.
greatest et least#
Voir 36.4. Un argument NULL rend NULL.
Booléens#
TRUE et FALSE sont rendus t et f. Une colonne boolean et la conversion ::boolean d'un texte lisent t, true, yes, on, 1, leurs contraires et leurs préfixes ; un autre texte est refusé (22P02 : invalid input syntax for type boolean: "maybe"). Un entier se convertit aussi (0 : faux) ; un numeric, un flottant, une date ou une heure ne se convertissent pas (2.5::boolean : 42846, cannot cast type numeric to boolean), comme dans PostgreSQL.
SELECT 1::boolean, 0::boolean, 'yes'::boolean, 'off'::boolean, true::int, false::int;t | f | t | f | 1 | 0Un booléen converti en texte s'écrit true ou false : true::text, CAST(actif AS varchar), 'x' || true (xtrue). La conversion ::boolean d'un texte calculé (colonne, valeur extraite d'un document) rend un boolean, et refuse de même un texte qui n'en est pas un (22P02).
SELECT true::text, false::varchar, 'actif : ' || true, ('{"ok": "yes"}'::jsonb ->> 'ok')::boolean;true | false | actif : true | t36.8 Fonctions d'agrégation#
Une fonction d'agrégation calcule une valeur pour un groupe de lignes (GROUP BY, voir 35. Requêtes SELECT) ou pour toutes les lignes. Les valeurs NULL sont ignorées (sauf par array_agg, json_agg et jsonb_agg) ; sur aucune ligne, count et regr_count rendent 0 et les autres agrégats NULL.
| Fonction | Rôle | Type du résultat |
|---|---|---|
count(*), count(expr), count(DISTINCT expr) | nombre de lignes, de valeurs non NULL, de valeurs distinctes | bigint |
sum(expr) | somme | bigint pour des entiers, numeric pour des numeric, double precision pour des flottants |
avg(expr) | moyenne | numeric à 16 décimales au moins ; double precision pour des flottants |
min(expr), max(expr) | plus petite, plus grande valeur | celui de expr |
any_value(expr) | une des valeurs non NULL du groupe | celui de expr |
string_agg(expr, separateur) | concaténation | text |
array_agg(expr) | tableau des valeurs | tableau (integer[], text[]…) |
json_agg, jsonb_agg, json_object_agg, jsonb_object_agg | tableau ou objet JSON | json ou jsonb |
bool_and(cond), bool_or(cond), every(cond) | toutes vraies, une vraie | boolean |
stddev, stddev_samp, stddev_pop, variance, var_samp, var_pop | écart-type, variance | numeric ou double precision |
corr, covar_pop, covar_samp, regr_* | corrélation, covariance, régression linéaire | double precision (regr_count : bigint) |
percentile_cont, percentile_disc, mode | quantiles, valeur la plus fréquente (WITHIN GROUP) | double precision, celui de la valeur |
rank, dense_rank, percent_rank, cume_dist avec WITHIN GROUP | rang qu'aurait une valeur dans le groupe | bigint, double precision |
bit_and(expr), bit_or(expr), bit_xor(expr) | ET, OU, OU exclusif bit à bit (d'une colonne bit(n) : le nombre, et non la chaîne de bits) | bigint |
count, sum, avg, min, max#
SELECT count(*), count(ville), count(DISTINCT ville) FROM client;
SELECT sum(stock), avg(stock), min(prix), max(prix), sum(prix), avg(prix) FROM article;4 | 3 | 2
655 | 163.7500000000000000 | 1.20 | 12.00 | 20.60 | 5.1500000000000000avg d'entiers ou de décimaux est un numeric exact à 16 décimales (ou à l'échelle de l'argument si elle est plus grande) ; arrondir pour l'affichage : round(avg(prix), 2). sum d'une colonne bigint est un numeric. sum(DISTINCT expr) et avg(DISTINCT expr) sont acceptés.
SELECT article_id, sum(quantite) AS quantite, round(avg(prix_unitaire), 2) AS prix_moyen, count(*) AS lignes
FROM ligne_commande GROUP BY article_id ORDER BY article_id; article_id | quantite | prix_moyen | lignes
------------+----------+------------+--------
1 | 150 | 1.15 | 2
2 | 15 | 2.50 | 2
3 | 4 | 4.90 | 1
4 | 2 | 12.00 | 1min et max s'appliquent aussi aux textes, aux dates et aux booléens.
FILTER#
agregat(...) FILTER (WHERE condition)Ne passe à l'agrégat que les lignes qui vérifient la condition.
SELECT count(*) FILTER (WHERE statut = 'livrée') AS livrees,
count(*) FILTER (WHERE statut <> 'livrée') AS autres
FROM commande;
SELECT count(*) FILTER (WHERE stock > 0), sum(stock) FILTER (WHERE prix < 3), avg(prix) FILTER (WHERE stock > 0) FROM article;2 | 2
3 | 620 | 2.8666666666666667Une ligne écartée n'est pas vue par l'agrégat : elle n'apparaît pas dans json_agg (pas de null), et sa clé NULL ne fait pas échouer json_object_agg. Une ligne retenue garde sa valeur, NULL compris. Sur un groupe sans ligne retenue, count rend 0 et les autres agrégats NULL. Le type du résultat est celui de l'agrégat sans FILTER (sum(bigint) : numeric).
SELECT json_agg(nom) FILTER (WHERE ville = 'Lyon') FROM client;
SELECT json_object_agg(reference, stock) FILTER (WHERE stock > 100) FROM article;["Dupont", "Bernard"]
{"STY-001": 500, "CAH-096": 120}FILTER n'accompagne qu'un agrégat : après une autre fonction, 42809 (FILTER specified, but upper is not an aggregate function) ; après une fonction de fenêtrage qui n'est pas un agrégat (row_number, lag…), 0A000. La condition ne contient pas d'agrégat (42803). L'argument de l'agrégat est calculé pour toutes les lignes, écartées comprises : une erreur de calcul sur une ligne écartée (sum(10 / n) FILTER (WHERE n <> 0) : 22012) est levée, là où PostgreSQL ne calcule pas l'argument.
Dans une vue ou un corps de routine, l'agrégat filtré est gardé sous la forme agrégat(PG_AGG_FILTER(condition, argument)), que les deux langages relisent (count(*) : count(CASE WHEN condition THEN 1 END)).
string_agg#
string_agg(expr, separateur [ORDER BY ...])
string_agg(DISTINCT expr, separateur [ORDER BY ...])Concatène les valeurs non NULL du groupe, séparées par separateur, dans l'ordre demandé. Les deux arguments sont obligatoires ; expr est converti en texte ; separateur doit être une constante.
SELECT string_agg(nom, ', ' ORDER BY nom) FROM client;
SELECT string_agg(DISTINCT ville, '/') FROM client;
SELECT ville, string_agg(nom, ' + ' ORDER BY nom) AS clients FROM client GROUP BY ville ORDER BY ville;Bernard, Dupont, Martin, Élodie Petit
Lyon/Paris
ville | clients
-------+------------------
Lyon | Bernard + Dupont
Paris | Martin
NULL | Élodie PetitSELECT string_agg(reference || '=' || stock, '; ' ORDER BY stock DESC) FROM article;STY-001=500; CAH-096=120; CLA-A4=35; AGR-024=0Sur aucune ligne, string_agg rend NULL. Le tri suit l'ordre des octets : Élodie vient après Martin.
array_agg#
array_agg(expr [ORDER BY ...])
array_agg(DISTINCT expr [ORDER BY ...])Rend le tableau des valeurs du groupe (voir 36.14), NULL compris, dans l'ordre demandé ; avec DISTINCT, chaque valeur une fois, triées (NULL en dernier). Sur aucune ligne, NULL. Le type du résultat est le tableau du type de expr (integer[], text[]…).
SELECT array_agg(nom ORDER BY nom) AS noms, array_agg(DISTINCT ville) AS villes, array_agg(id) FILTER (WHERE ville = 'Lyon') AS lyonnais FROM client;
SELECT c.nom, array_agg(o.id ORDER BY o.id) AS commandes FROM client c JOIN commande o ON o.client_id = c.id GROUP BY c.nom ORDER BY c.nom;{Bernard,Dupont,Martin,"Élodie Petit"} | {Lyon,Paris,NULL} | {1,3}
nom | commandes
---------+-----------
Bernard | {4}
Dupont | {1,2}
Martin | {3}Agrégats JSON#
json_agg(expr [ORDER BY ...]) jsonb_agg(expr [ORDER BY ...])
json_object_agg(cle, valeur) jsonb_object_agg(cle, valeur)json_agg et jsonb_agg rendent le tableau JSON des valeurs du groupe (NULL compris, écrit null) ; json_object_agg et jsonb_object_agg un objet. Une clé NULL est refusée (22032 : JSON documents may not contain NULL member names.).
SELECT json_agg(nom ORDER BY nom) FROM client;
SELECT json_object_agg(reference, stock) FROM article;
SELECT c.nom, json_agg(jsonb_build_object('commande', o.id, 'date', o.date_commande) ORDER BY o.id) AS commandes
FROM client c JOIN commande o ON o.client_id = c.id GROUP BY c.nom ORDER BY c.nom;["Bernard", "Dupont", "Martin", "Élodie Petit"]
{"STY-001": 500, "CAH-096": 120, "CLA-A4": 35, "AGR-024": 0}
nom | commandes
---------+--------------------------------------------------------------------------------
Bernard | [{"date": "2026-10-09", "commande": 4}]
Dupont | [{"date": "2026-09-28", "commande": 1}, {"date": "2026-10-05", "commande": 2}]
Martin | [{"date": "2026-10-06", "commande": 3}]bool_and, bool_or, every#
bool_and(condition) (ou every) vaut vrai si la condition est vraie pour toutes les lignes, bool_or(condition) si elle l'est pour une au moins. Le résultat est un boolean (t / f), que l'argument soit une colonne boolean ou une condition ; il est NULL sans ligne. Dans une condition (HAVING bool_or(…)), il s'emploie tel quel.
SELECT bool_and(stock > 0), bool_or(stock > 0), every(prix > 1) FROM article;f | t | tÉcart-type et variance#
| Fonction | Calcule |
|---|---|
stddev(expr), stddev_samp(expr) | écart-type corrigé (d'échantillon) |
stddev_pop(expr) | écart-type de la population |
variance(expr), var_samp(expr) | variance corrigée |
var_pop(expr) | variance de la population |
Sur des entiers ou des décimaux, le résultat est un numeric exact à 16 décimales au moins ; sur des flottants, un double precision.
SELECT stddev(quantite), variance(quantite), stddev_pop(quantite), var_pop(quantite), stddev(quantite::float8)
FROM ligne_commande;39.4246116023988244 | 1554.3000000000000000 | 35.9895818258562154 | 1295.2500000000000000 | 39.42461160239883Corrélation et régression#
Agrégats à deux arguments, y (variable dépendante) puis x, calculés sur les lignes où les deux ne sont pas NULL. Le résultat est un double precision, sauf regr_count (bigint).
| Fonction | Calcule |
|---|---|
corr(y, x) | coefficient de corrélation |
covar_pop(y, x), covar_samp(y, x) | covariance de la population, d'échantillon |
regr_slope(y, x), regr_intercept(y, x) | pente et ordonnée à l'origine de la droite des moindres carrés |
regr_r2(y, x) | carré du coefficient de corrélation |
regr_count(y, x) | nombre de lignes retenues |
regr_avgx(y, x), regr_avgy(y, x) | moyennes de x et de y |
regr_sxx(y, x), regr_syy(y, x), regr_sxy(y, x) | sommes des carrés et des produits des écarts |
SELECT corr(y, x), covar_pop(y, x), covar_samp(y, x), regr_slope(y, x), regr_intercept(y, x), regr_r2(y, x), regr_count(y, x)
FROM (VALUES (1, 3), (2, 5), (3, 7), (4, NULL)) v(x, y);
SELECT regr_avgx(y, x), regr_avgy(y, x), regr_sxx(y, x), regr_syy(y, x), regr_sxy(y, x)
FROM (VALUES (1, 3), (2, 5), (3, 7), (4, NULL)) v(x, y);1 | 1.3333333333333333 | 2 | 2 | 1 | 1 | 3
2 | 5 | 2 | 8 | 4Agrégats à tri interne : WITHIN GROUP#
percentile_cont(fraction) WITHIN GROUP (ORDER BY expr)
percentile_disc(fraction) WITHIN GROUP (ORDER BY expr)
mode() WITHIN GROUP (ORDER BY expr)
rank(valeur) WITHIN GROUP (ORDER BY expr) -- de même dense_rank, percent_rank, cume_distpercentile_contrend le quantilefraction(de 0 à 1) des valeurs triées, interpolé entre les deux valeurs voisines (double precision) ;percentile_cont(0.5)est la médiane ;percentile_discrend la première valeur du groupe dont le rang atteint la fraction, sans interpolation (type deexpr) ;mode()rend la valeur la plus fréquente ;rank,dense_rank,percent_ranketcume_distà argument rendent le rang qu'auraitvaleursi elle était ajoutée au groupe (NULLS FIRST/NULLS LASTacceptés dans l'ORDER BY).
Une fraction hors de [0, 1] est refusée (22023 : percentile value 1.5 is not between 0 and 1). FILTER s'y ajoute comme aux autres agrégats.
Les fractions peuvent être données en tableau : percentile_cont et percentile_disc rendent alors un tableau d'autant de quantiles (double precision[] pour percentile_cont, tableau du type des valeurs pour percentile_disc) ; une fraction NULL donne un élément NULL, un tableau NULL rend NULL.
SELECT percentile_cont(ARRAY[0.25, 0.5]) WITHIN GROUP (ORDER BY v),
percentile_disc(ARRAY[0.5, 1]) WITHIN GROUP (ORDER BY v)
FROM (VALUES (10), (20), (30), (40)) AS w(v);
-- {17.5,25} | {20,40}SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY quantite) AS mediane, percentile_disc(0.5) WITHIN GROUP (ORDER BY quantite) AS mediane_disc,
percentile_cont(0.9) WITHIN GROUP (ORDER BY quantite) AS p90, mode() WITHIN GROUP (ORDER BY article_id) AS article_frequent
FROM ligne_commande;
SELECT rank(10) WITHIN GROUP (ORDER BY quantite), dense_rank(10) WITHIN GROUP (ORDER BY quantite),
percent_rank(10) WITHIN GROUP (ORDER BY quantite), cume_dist(10) WITHIN GROUP (ORDER BY quantite)
FROM ligne_commande; mediane | mediane_disc | p90 | article_frequent
---------+--------------+-----+------------------
7.5 | 5 | 75 | 1
4 | 4 | 0.5 | 0.7142857142857143Regroupements multiples#
GROUP BY ROLLUP (…), GROUP BY CUBE (…), GROUP BY GROUPING SETS (…) et la fonction grouping(colonne) (1 sur une ligne de total) sont décrits au chapitre 35. Requêtes SELECT.
SELECT grouping(ville) AS total, ville, count(*) FROM client GROUP BY ROLLUP (ville); total | ville | count(*)
-------+-------+----------
0 | NULL | 1
0 | Lyon | 2
0 | Paris | 1
1 | NULL | 436.9 Fonctions de fenêtrage#
Une fonction de fenêtrage calcule, pour chaque ligne, une valeur sur un ensemble de lignes voisines sans les regrouper. La clause OVER, les cadres et la clause WINDOW sont décrits au chapitre 35. Requêtes SELECT.
fonction(...) OVER ([PARTITION BY ...] [ORDER BY ...] [cadre])
fonction(...) OVER nom_de_fenetre| Fonction | Rôle | Type |
|---|---|---|
row_number() | numéro de la ligne dans sa partition | bigint |
rank() | rang, avec des trous après des ex æquo | bigint |
dense_rank() | rang sans trous | bigint |
percent_rank() | rang relatif, de 0 à 1 | double precision |
cume_dist() | part des lignes de rang inférieur ou égal | double precision |
ntile(n) | numéro du groupe, la partition étant coupée en n groupes | integer |
lag(expr [, decalage [, defaut]]) | valeur d'une ligne précédente | celui de expr |
lead(expr [, decalage [, defaut]]) | valeur d'une ligne suivante | celui de expr |
first_value(expr), last_value(expr) | première, dernière valeur du cadre | celui de expr |
nth_value(expr, n) | n-ième valeur du cadre | celui de expr |
count, sum, avg, min, max, stddev, variance…, bool_and, bool_or, every | agrégat sur le cadre | celui de l'agrégat |
Rangs#
SELECT id, designation, prix,
row_number() OVER (ORDER BY prix DESC) AS n,
rank() OVER (ORDER BY prix DESC) AS rang,
ntile(2) OVER (ORDER BY prix DESC) AS moitie
FROM article ORDER BY id; id | designation | prix | n | rang | moitie
----+------------------+-------+---+------+--------
1 | Stylo bille bleu | 1.20 | 4 | 4 | 2
2 | Cahier 96 pages | 2.50 | 3 | 3 | 2
3 | Classeur A4 | 4.90 | 2 | 2 | 1
4 | Agrafeuse | 12.00 | 1 | 1 | 1SELECT id, percent_rank() OVER w, cume_dist() OVER w, dense_rank() OVER w
FROM article WINDOW w AS (ORDER BY prix) ORDER BY id;1 | 0 | 0.25 | 1
2 | 0.3333333333333333 | 0.5 | 2
3 | 0.6666666666666666 | 0.75 | 3
4 | 1 | 1 | 4Une fonction de fenêtrage ne peut pas être citée dans WHERE : pour garder les deux articles les plus chers, passer par une table dérivée, ou par la clause QUALIFY (extension de MIRAJ) :
SELECT * FROM (SELECT id, prix, rank() OVER (ORDER BY prix DESC) AS r FROM article) x WHERE r <= 2 ORDER BY r;
SELECT id, prix FROM article QUALIFY row_number() OVER (ORDER BY prix DESC) <= 2 ORDER BY id;4 | 12.00 | 1
3 | 4.90 | 2
3 | 4.90
4 | 12.00lag et lead#
SELECT id, prix,
lag(prix) OVER (ORDER BY id) AS precedent,
lead(prix) OVER (ORDER BY id) AS suivant,
lag(prix, 2, 0) OVER (ORDER BY id) AS avant_dernier
FROM article ORDER BY id; id | prix | precedent | suivant | avant_dernier
----+-------+-----------+---------+---------------
1 | 1.20 | NULL | 2.50 | 0.00
2 | 2.50 | 1.20 | 4.90 | 0.00
3 | 4.90 | 2.50 | 12.00 | 1.20
4 | 12.00 | 4.90 | NULL | 2.50Nombre de jours entre une commande et la précédente :
SELECT id, date_commande, date_commande - lag(date_commande) OVER (ORDER BY date_commande) AS jours
FROM commande ORDER BY id; id | date_commande | jours
----+---------------+-------
1 | 2026-09-28 | NULL
2 | 2026-10-05 | 7
3 | 2026-10-06 | 1
4 | 2026-10-09 | 3first_value, last_value, nth_value#
Ces fonctions lisent le cadre de la ligne. Avec ORDER BY et sans cadre écrit, le cadre s'arrête à la ligne courante : last_value rend alors la valeur de la ligne elle-même. Pour la dernière valeur de la partition, écrire le cadre entier.
SELECT id, prix,
first_value(prix) OVER (ORDER BY id) AS premier,
last_value(prix) OVER (ORDER BY id) AS dernier_du_cadre,
last_value(prix) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS dernier,
nth_value(prix, 2) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS deuxieme
FROM article ORDER BY id; id | prix | premier | dernier_du_cadre | dernier | deuxieme
----+-------+---------+------------------+---------+----------
1 | 1.20 | 1.20 | 1.20 | 12.00 | 2.50
2 | 2.50 | 1.20 | 2.50 | 12.00 | 2.50
3 | 4.90 | 1.20 | 4.90 | 12.00 | 2.50
4 | 12.00 | 1.20 | 12.00 | 12.00 | 2.50Agrégats en fenêtre#
SELECT commande_id, article_id, quantite,
sum(quantite) OVER (PARTITION BY commande_id) AS total_commande,
count(*) OVER (PARTITION BY commande_id) AS lignes
FROM ligne_commande ORDER BY 1, 2; commande_id | article_id | quantite | total_commande | lignes
-------------+------------+----------+----------------+--------
1 | 1 | 100 | 110 | 2
1 | 2 | 10 | 110 | 2
2 | 3 | 4 | 4 | 1
3 | 1 | 50 | 52 | 2
3 | 4 | 2 | 52 | 2
4 | 2 | 5 | 5 | 1Cumul, moyenne glissante sur deux lignes et part du total :
SELECT id, prix,
sum(prix) OVER (ORDER BY id) AS cumul,
avg(prix) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS moyenne_glissante,
round(100 * prix / sum(prix) OVER (), 1) AS part
FROM article ORDER BY id; id | prix | cumul | moyenne_glissante | part
----+-------+-------+--------------------+------
1 | 1.20 | 1.20 | 1.2000000000000000 | 5.8
2 | 2.50 | 3.70 | 1.8500000000000000 | 12.1
3 | 4.90 | 8.60 | 3.7000000000000000 | 23.8
4 | 12.00 | 20.60 | 8.4500000000000000 | 58.3FILTER s'emploie aussi avec OVER (count(*) FILTER (WHERE stock > 0) OVER (ORDER BY id)), de même que les agrégats à tri interne :
SELECT id, prix, percentile_cont(0.5) WITHIN GROUP (ORDER BY prix) OVER () AS mediane FROM article ORDER BY id; id | prix | mediane
----+-------+---------
1 | 1.20 | 3.7
2 | 2.50 | 3.7
3 | 4.90 | 3.7
4 | 12.00 | 3.7Cadres pris en charge : ROWS avec UNBOUNDED PRECEDING, n PRECEDING, CURRENT ROW, n FOLLOWING, UNBOUNDED FOLLOWING ; RANGE sans décalage (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW). RANGE avec un décalage, GROUPS, EXCLUDE, ainsi que string_agg et json_agg avec OVER, ne sont pas pris en charge (voir 36.16).
SELECT DISTINCT ON (clés) … garde la première ligne de chaque groupe de clés dans l'ordre de l'ORDER BY (voir 35. Requêtes SELECT) : la dernière commande de chaque client.
SELECT DISTINCT ON (client_id) client_id, id, date_commande FROM commande ORDER BY client_id, date_commande DESC; client_id | id | date_commande
-----------+----+---------------
1 | 2 | 2026-10-05
2 | 3 | 2026-10-06
3 | 4 | 2026-10-0936.10 JSON et JSONB#
Les types json et jsonb sont décrits au chapitre 32. Types de données. Une colonne jsonb a le stockage et les fonctions d'une colonne json, garde son type annoncé et range ses documents normalisés (clés triées par longueur puis par ordre binaire, doublons retirés). Les exemples utilisent la colonne article.attributs (jsonb).
Opérateurs#
| Opérateur | Rôle | Exemple | Résultat |
|---|---|---|---|
doc -> 'clé' | membre d'un objet, en JSON | '{"a": 1}'::jsonb -> 'a' | 1 |
doc -> n | élément d'un tableau (0 pour le premier, négatif depuis la fin), en JSON | '[1, 2, 3]'::jsonb -> -1 | 3 |
doc ->> 'clé', doc ->> n | de même, en texte | '{"s": "txt"}'::jsonb ->> 's' | txt |
doc #> '{a,0}' | élément au chemin écrit en tableau, en JSON | '{"b": {"c": [10, 20, 30]}}'::jsonb #> '{b,c,2}' | 30 |
doc #>> '{a,0}' | de même, en texte | '{"b": {"c": [10, 20, 30]}}'::jsonb #>> '{b,c,0}' | 10 |
a @> b | a contient b | '{"a": 1, "b": 2}'::jsonb @> '{"a": 1}' | t |
a <@ b | a est contenu dans b | '[1, 2]'::jsonb <@ '[1, 2, 3]' | t |
doc ? 'clé' | l'objet a ce membre, ou le tableau contient cette chaîne | '{"a": 1}'::jsonb ? 'a' | t |
doc ?| ARRAY[…] | au moins une des clés existe | '{"a": 1}'::jsonb ?| ARRAY['x', 'a'] | t |
doc ?& ARRAY[…] | toutes les clés existent | '{"a": 1}'::jsonb ?& ARRAY['a', 'b'] | f |
a || b | fusion de deux objets, mise bout à bout de deux tableaux | '{"a": 1}'::jsonb || '{"b": 2}'::jsonb | {"a": 1, "b": 2} |
doc - 'clé' | objet sans ce membre | '{"a": 1, "b": 2}'::jsonb - 'a' | {"b": 2} |
doc - n | tableau sans cet élément | '[1, 2, 3]'::jsonb - 0 | [2, 3] |
doc #- '{a,b}' | document sans l'élément au chemin | '{"a": {"b": 1, "c": 2}}'::jsonb #- '{a,b}' | {"a": {"c": 2}} |
Les opérateurs s'enchaînent : doc -> 'b' -> 'c' -> 1, doc -> 'a' ->> 'b'.
SELECT reference,
attributs -> 'couleur' AS json,
attributs ->> 'couleur' AS texte,
attributs -> 'tags' ->> 0 AS premier_tag,
attributs #>> '{dim,l}' AS longueur
FROM article ORDER BY id; reference | json | texte | premier_tag | longueur
-----------+---------+-------+-------------+----------
STY-001 | "bleu" | bleu | bureau | 14
CAH-096 | "rouge" | rouge | papier | NULL
CLA-A4 | "noir" | noir | NULL | NULL
AGR-024 | NULL | NULL | NULL | NULL- Un membre ou un élément absent rend
NULL.->d'un membrenullrend le JSONnull;->>rendNULL. ->et#>rendent dujsonbsur une colonne ou une valeurjsonb, dujsonsur dujson;->>et#>>rendent dutext.->>rend toujours un texte : convertir pour comparer des nombres,(attributs ->> 'pages')::int > 50.
Recherche dans une colonne JSON :
SELECT reference FROM article WHERE attributs ->> 'couleur' = 'bleu'; -- STY-001
SELECT reference FROM article WHERE attributs @> '{"couleur": "noir"}'; -- CLA-A4
SELECT reference FROM article WHERE attributs -> 'tags' @> '["papier"]'; -- CAH-096
SELECT reference FROM article WHERE (attributs ->> 'pages')::int > 50; -- CAH-096
SELECT reference FROM article WHERE attributs -> 'pages' IS NOT NULL; -- CAH-096
SELECT attributs ->> 'couleur' AS couleur, count(*) FROM article GROUP BY attributs ->> 'couleur' ORDER BY 1; couleur | count(*)
---------+----------
bleu | 1
noir | 1
rouge | 1
NULL | 1Existence d'une clé. ?, ?| et ?& testent les clés du premier niveau d'un objet, ou les chaînes d'un tableau ; un document NULL rend NULL.
SELECT reference, attributs ? 'pages' AS a_pages, attributs ?| ARRAY['pages', 'recycle'] AS l_un,
attributs ?& ARRAY['couleur', 'tags'] AS les_deux, attributs -> 'tags' ? 'papier' AS tag_papier
FROM article ORDER BY id; reference | a_pages | l_un | les_deux | tag_papier
-----------+---------+------+----------+------------
STY-001 | f | f | t | f
CAH-096 | t | t | t | t
CLA-A4 | f | t | t | f
AGR-024 | NULL | NULL | NULL | NULLLes mêmes tests s'écrivent en fonctions : jsonb_exists(doc, clé), jsonb_exists_any(doc, tableau), jsonb_exists_all(doc, tableau). Les autres opérateurs ont aussi leur forme fonction : jsonb_concat(a, b) (||), jsonb_delete(doc, clé) (-), jsonb_delete_path(doc, chemin) (#-), jsonb_contains(a, b) (@>), jsonb_contained(a, b) (<@).
|| fusionne deux documents. Face à un jsonb, un objet ou un tableau écrit en chaîne sans type est lu en jsonb (attributs || '{"promo": true}') ; entre deux textes, ou entre un json et une chaîne sans type, || reste la concaténation de textes.
UPDATE article SET attributs = attributs || '{"promo": true}'::jsonb WHERE id = 3 RETURNING attributs;
UPDATE article SET attributs = attributs - 'promo' WHERE id = 3 RETURNING attributs;{"tags": [], "promo": true, "couleur": "noir", "recycle": true}
{"tags": [], "couleur": "noir", "recycle": true}Retirer un rang d'un objet ('{"a": 1}'::jsonb - 0) : 22023 (cannot delete from object using integer index).
Construction#
| Fonction | Rend |
|---|---|
jsonb_build_object(clé1, valeur1, …), json_build_object(…) | un objet ; nombre impair d'arguments : 42883 |
jsonb_build_array(valeur1, …), json_build_array(…) | un tableau |
to_jsonb(valeur), to_json(valeur) | la valeur en JSON : nombre, chaîne entre guillemets, true / false, null |
json_object(tableau_de_textes), json_object(cles, valeurs) | un objet aux valeurs texte, depuis {clé1, valeur1, …} ou deux tableaux ; nombre impair d'éléments : 22023 (array must have even number of elements) |
SELECT jsonb_build_object('a', 1, 'b', 'x', 'c', true, 'd', NULL, 'e', 1.5), jsonb_build_array(1, 'x', true, NULL),
jsonb_build_object('n', jsonb_build_object('x', 1), 't', jsonb_build_array(1, 2));
SELECT to_jsonb('txt'::text), to_json(42), to_jsonb(true), to_jsonb(date '2026-10-11'), to_jsonb(NULL::text);
SELECT jsonb_build_object('id', id, 'nom', nom, 'ville', ville) FROM client WHERE id IN (1, 4) ORDER BY id;
SELECT json_object('{a, 1, b, 2}'), json_object('{a, b}', '{x, y}');{"a": 1, "b": "x", "c": true, "d": null, "e": 1.5} | [1, "x", true, null] | {"n": {"x": 1}, "t": [1, 2]}
"txt" | 42 | true | "2026-10-11" | null
{"id": 1, "nom": "Dupont", "ville": "Lyon"}
{"id": 4, "nom": "Élodie Petit", "ville": null}
{"a": "1", "b": "2"} | {"a": "x", "b": "y"}to_jsonb ne s'applique pas à une ligne entière (to_jsonb(c) : 42703) : construire l'objet par jsonb_build_object.
Modification#
jsonb_set(document, chemin, valeur [, creer])Remplace l'élément au chemin (écrit en tableau, '{a,0}') par valeur, un document JSON. Si le dernier élément du chemin n'existe pas, il est ajouté, sauf avec creer à false ; un chemin dont un élément intermédiaire manque laisse le document inchangé.
SELECT jsonb_set('{"a": 1}'::jsonb, '{a}', '5'), jsonb_set('{"a": {"b": 1}}'::jsonb, '{a,b}', '"x"'),
jsonb_set('{"a": 1}'::jsonb, '{b}', '2'), jsonb_set('{"a": 1}'::jsonb, '{b}', '2', false),
jsonb_set('{"a": [1, 2]}'::jsonb, '{a,0}', '9');
SELECT jsonb_set(attributs, '{stock}', to_jsonb(stock)) FROM article WHERE id = 1;{"a": 5} | {"a": {"b": "x"}} | {"a": 1, "b": 2} | {"a": 1} | {"a": [9, 2]}
{"dim": {"d": 0.8, "l": 14}, "tags": ["bureau", "ecriture"], "stock": 500, "couleur": "bleu"}Dans un UPDATE : UPDATE article SET attributs = jsonb_set(attributs, '{couleur}', '"vert"') WHERE id = 3.
jsonb_insert(document, chemin, valeur [, apres])
jsonb_strip_nulls(document) json_strip_nulls(document)jsonb_insert insère valeur au chemin : dans un tableau, avant l'élément de ce rang (après lui avec apres à true) ; dans un objet, comme nouveau membre — un membre existant est refusé (22023 : cannot replace existing key). jsonb_strip_nulls et json_strip_nulls retirent, à tous les niveaux, les membres d'objet de valeur null (les null d'un tableau restent).
SELECT jsonb_insert('{"a": [1, 2]}', '{a, 1}', '9'), jsonb_insert('{"a": [1, 2]}', '{a, 1}', '9', true), jsonb_insert('{"a": 1}', '{b}', '2');
SELECT jsonb_strip_nulls('{"a": 1, "b": null, "c": {"d": null}}'), json_strip_nulls('{"a": null, "b": 2}');{"a": [1, 9, 2]} | {"a": [1, 2, 9]} | {"a": 1, "b": 2}
{"a": 1, "c": {}} | {"b":2}Inspection#
| Fonction | Rend |
|---|---|
jsonb_typeof(doc), json_typeof(doc) | object, array, string, number, boolean, null |
jsonb_array_length(doc), json_array_length(doc) | nombre d'éléments d'un tableau ; autre document : 22023 (cannot get array length of a non-array) |
jsonb_extract_path(doc, clé1, clé2, …), jsonb_extract_path_text(…) | élément au chemin donné clé par clé (un rang pour un tableau), en JSON ou en texte : comme #> et #>> |
jsonb_pretty(doc) | le document mis en page sur plusieurs lignes, indenté de quatre espaces |
SELECT jsonb_typeof('{"a": 1}'::jsonb), jsonb_typeof('[1]'::jsonb), jsonb_typeof('1'::jsonb),
jsonb_typeof('"a"'::jsonb), jsonb_typeof('true'::jsonb), jsonb_typeof('null'::jsonb);
SELECT reference, jsonb_array_length(attributs -> 'tags') AS tags, jsonb_typeof(attributs -> 'dim') AS dim
FROM article ORDER BY id;object | array | number | string | boolean | null
reference | tags | dim
-----------+------+--------
STY-001 | 2 | object
CAH-096 | 1 | NULL
CLA-A4 | 0 | NULL
AGR-024 | NULL | NULLSELECT jsonb_extract_path(attributs, 'dim', 'l'), jsonb_extract_path_text(attributs, 'tags', '1') FROM article WHERE id = 1;
SELECT jsonb_pretty(attributs) FROM article WHERE id = 2;14 | ecriture
{
"tags": [
"papier"
],
"pages": 96,
"couleur": "rouge"
}Les agrégats json_agg, jsonb_agg, json_object_agg et jsonb_object_agg sont décrits en 36.8.
Chemins SQL/JSON#
jsonb_path_exists(doc, chemin [, variables [, silencieux]])
jsonb_path_match(doc, chemin [, variables [, silencieux]])
jsonb_path_query_array(doc, chemin [, variables [, silencieux]])
jsonb_path_query_first(doc, chemin [, variables [, silencieux]])Évaluent un chemin SQL/JSON ($.a.b, $.tags[*], $.a[last], $.dim.*, filtres ? (@ > 2) avec ==, !=, <, >, &&, ||, !), en mode lax (par défaut : membre absent ignoré) ou strict (écrit en tête du chemin : membre absent en erreur 2203A, JSON object does not contain key "b"). variables est un objet JSON dont les membres se citent $nom dans le chemin ; silencieux à true change les erreurs de chemin en NULL.
jsonb_path_exists: le chemin trouve-t-il au moins un élément ?jsonb_path_match: résultat d'un chemin qui est un prédicat ($.pages > 50) ; un chemin qui ne rend pas un booléen unique est refusé (22038:single boolean result is expected) ;jsonb_path_query_array: tous les éléments trouvés, en tableau JSON ;jsonb_path_query_first: le premier élément trouvé,NULLs'il n'y en a pas.
SELECT reference, jsonb_path_exists(attributs, '$.tags[*] ? (@ == "papier")') AS papier, jsonb_path_match(attributs, '$.pages > 50') AS gros,
jsonb_path_query_array(attributs, '$.dim.*') AS dims, jsonb_path_query_first(attributs, '$.tags[last]') AS dernier_tag
FROM article ORDER BY id;
SELECT jsonb_path_query_array('{"a": [1, 2, 3, 4]}', '$.a[*] ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
SELECT jsonb_path_exists('{"a": 1}', 'strict $.b', '{}', true), jsonb_path_exists('{"a": 1}', 'lax $.b'); reference | papier | gros | dims | dernier_tag
-----------+--------+------+-----------+-------------
STY-001 | f | f | [0.8, 14] | "ecriture"
CAH-096 | t | t | [] | "papier"
CLA-A4 | f | f | [] | NULL
AGR-024 | NULL | NULL | NULL | NULL
[2, 3]
NULL | fFonctions ensemblistes JSON#
| Fonction | Rend une ligne par | Colonnes |
|---|---|---|
jsonb_each(doc), json_each(doc) | membre de l'objet | key (texte), value (JSON) |
jsonb_each_text(doc), json_each_text(doc) | membre de l'objet | key, value (texte) |
jsonb_array_elements(doc), json_array_elements(doc) | élément du tableau | value (JSON) |
jsonb_array_elements_text(doc), json_array_elements_text(doc) | élément du tableau | value (texte) |
jsonb_object_keys(doc), json_object_keys(doc) | clé de l'objet | une colonne de texte |
Elles s'emploient dans un FROM, latérales sur les tables qui les précèdent (FROM article a, jsonb_array_elements_text(a.attributs -> 'tags') AS t(tag)), avec WITH ORDINALITY (une colonne de numéros de plus) et des alias de colonnes AS e(cle, valeur). Écrites dans la liste du SELECT avec un document constant, elles rendent une ligne par valeur (voir 36.14) ; avec un argument qui cite une colonne, les placer dans le FROM (sinon 0A000).
SELECT * FROM jsonb_each_text('{"a": 1, "b": "x", "c": null}');
SELECT * FROM jsonb_array_elements('[1, "x", {"k": true}]') WITH ORDINALITY AS e(valeur, rang);
SELECT a.reference, t.tag FROM article a, jsonb_array_elements_text(a.attributs -> 'tags') AS t(tag) ORDER BY a.id, t.tag;
SELECT a.reference, k.cle FROM article a, jsonb_object_keys(a.attributs) AS k(cle) WHERE a.id = 2 ORDER BY 2;a | 1
b | x
c | NULL
1 | 1
"x" | 2
{"k": true} | 3
reference | tag
-----------+----------
STY-001 | bureau
STY-001 | ecriture
CAH-096 | papier
reference | cle
-----------+---------
CAH-096 | couleur
CAH-096 | pages
CAH-096 | tagsFonctions JSON du langage MIRAJ#
Les fonctions JSON de MIRAJ restent disponibles, avec leurs chemins écrits '$.a.b[0]' :
| Fonction | Emploi |
|---|---|
json_keys(doc) | clés d'un objet, en un tableau JSON |
json_remove(doc, '$.a.b') | document sans l'élément au chemin |
json_pretty(doc) | mise en page |
json_table(doc, chemin COLUMNS (…)) | lignes et colonnes typées tirées d'un document, dans le FROM |
json_extract(doc, '$.a[0]'), json_value(doc, '$.a'), json_contains, json_search | lecture et recherche par chemin |
json_valid(texte), json_depth(doc), json_length(doc) | validité, profondeur, taille |
SELECT json_keys(attributs), json_length(attributs), json_remove(attributs, '$.dim'), json_value(attributs, '$.couleur')
FROM article WHERE id = 1;
SELECT a.reference, t.tag
FROM article a, json_table(a.attributs, '$.tags[*]' COLUMNS (tag text PATH '$')) AS t ORDER BY a.id, t.tag;["dim", "tags", "couleur"] | 3 | {"tags": ["bureau", "ecriture"], "couleur": "bleu"} | bleu
reference | tag
-----------+----------
STY-001 | bureau
STY-001 | ecriture
CAH-096 | papierLeur description complète est au chapitre 9. Fonctions SQL (les appels s'écrivent avec des chaînes entre apostrophes).
Différences avec PostgreSQL#
- Un document
jsonbest normalisé, comme dans PostgreSQL : dans chaque objet, une clé répétée ne garde que sa dernière valeur et les clés sont triées par longueur puis par ordre binaire ; l'espacement est canonique et un nombre écrit avec un exposant est développé.'{"b": 1, "a": 2, "a": 3}'::jsonbs'affiche{"a": 3, "b": 1}, et'{"a": 1, "b": 2}'::jsonb = '{"b": 2, "a": 1}'::jsonbest vrai. Unjsongarde le texte écrit. Deuxjsonbse comparent par valeur, dans l'ordre propre au type ('1.0'::jsonb = '1'::jsonbest vrai,'2'::jsonb < '10'::jsonbaussi ; voir 32.8), y compris pourORDER BY,DISTINCT,GROUP BY,UNIONet une clé de jointure. Restes : un document rangé dans une colonnejsonbpar une version antérieure, ou écrit en langage MIRAJ, garde son texte jusqu'à sa prochaine écriture en langage PostgreSQL (UPDATE t SET doc = doc::jsonble normalise) ; une clé primaire, une contrainteUNIQUEet la recherche par un index sur unjsonbportent sur le texte du document. - Un document illisible est refusé (
22P02), dans une colonnejsonoujsonbcomme dans la conversion d'un texte :'{"a": 1'::jsonb. - Les résultats de
||,-,#-,jsonb_set,jsonb_build_object,jsonb_build_array,to_jsonbet des agrégatsjsonb_agg/jsonb_object_aggsont desjsonb(type annoncé au client etpg_typeof), normalisés ; ceux dejson_build_object,json_build_array,to_json,json_aggetjson_object_aggsont desjson, dans l'ordre écrit.||,-,#-etjsonb_setn'existent que pour unjsonb, comme dans PostgreSQL : sur unjson(colonne,::json, résultat d'une fonctionjson_*), ils sont refusés (42883:operator does not exist: json || json) ; convertir par::jsonb.CASE … THEN doc ENDetcoalescegardent le type de leurs valeurs (jsonb,json,uuid, intervalle, énumération) ;max(doc)etmin(doc)d'unjsonb, absents de PostgreSQL, suivent l'ordre du type et rendent unjsonb.
36.11 Conversions#
Les types et leurs littéraux sont décrits au chapitre 32. Types de données. Cette section résume les conversions utiles dans une expression.
expression::type
CAST(expression AS type)
type 'texte'SELECT '42'::int + 1, CAST('42' AS integer), 42::text || '!', '3.14'::numeric(5,1), '3.14'::float8,
int4 '5', boolean 't', ' 42 '::int;43 | 42 | 42! | 3.1 | 3.14 | 5 | t | 42| Conversion | Règle | Exemple | Résultat |
|---|---|---|---|
| nombre décimal → entier | arrondi au plus proche | 3.7::int, 2.5::int, (-3.7)::int | 4, 3, -4 |
→ numeric(p,s) | arrondi à s décimales | 123.456::numeric(5,2) | 123.46 |
→ numeric sans précision | valeur gardée, sans zéros finals | '1.50'::numeric | 1.5 |
→ text | écriture du nombre, de la date | 1.50::text, date '2026-10-11'::text | 1.50, 2026-10-11 |
→ varchar(n) | tronqué à n caractères | 42::varchar(1) | 4 |
timestamp → date, time | partie date, partie heure | '2026-10-11 14:30:45'::timestamp::date | 2026-10-11 |
date → timestamp | à minuit | date '2026-10-11'::timestamp | 2026-10-11 00:00:00 |
→ timestamp(n) | n décimales de seconde | '2026-10-11 14:30:45.123456'::timestamp(0) | 2026-10-11 14:30:45 |
texte → timestamptz | décalage (+02, Z) ou nom de fuseau lus, instant affiché dans le fuseau de la session | '2026-10-11T12:30:00Z'::timestamptz | 2026-10-11 14:30:00+02 |
texte → bytea | octets du texte, ou hexadécimal \x… | '\x414243'::bytea | \x414243 |
bytea → text | texte UTF-8 des octets | '\x414243'::bytea::text | ABC |
| texte hexadécimal, octal, binaire → entier | préfixes 0x, 0o, 0b, séparateur _ | '0x1F'::int, '0b101'::int, '1_000'::int | 31, 5, 1000 |
Une valeur illisible ou hors du type est une erreur, et non un zéro, un préfixe numérique ou un NULL :
| Instruction | Réponse |
|---|---|
SELECT 'abc'::int | 22P02 : invalid input syntax for type integer: "abc" |
SELECT '1.5'::int, SELECT ''::int | 22P02 |
SELECT 'x'::numeric | 22P02 : invalid input syntax for type numeric: "x" |
SELECT 99999::smallint | 22003 : smallint out of range |
SELECT 3000000000::int | 22003 : integer out of range |
SELECT 'maybe'::boolean | 22P02 : invalid input syntax for type boolean: "maybe" |
SELECT 2.5::boolean | 42846 : cannot cast type numeric to boolean |
SELECT '{"a": 1}'::json || '{"b": 2}'::json | 42883 : operator does not exist: json || json |
SELECT 'abc'::interval | 22P02 : invalid input syntax for type interval: "abc" |
SELECT 12345.678::numeric(5,2) | 22003 : numeric field overflow: A field with precision 5, scale 2 must round to an absolute value less than 10^3. |
SELECT '2026-02-30'::date | 22008 : date/time field value out of range: "2026-02-30" |
SELECT 'pas-un-uuid'::uuid | 22P02 : invalid input syntax for type uuid: "pas-un-uuid" |
SELECT '{"a": 1'::jsonb | 22P02 : invalid input syntax for type jsonb: "{"a": 1" |
pg_input_is_valid(texte, type) dit si un texte est lisible dans un type sans lever l'erreur (voir 36.13).
pg_typeof(expression) rend le nom du type d'une expression :
SELECT pg_typeof(1), pg_typeof(3000000000), pg_typeof(1.5), pg_typeof(1.5::float8), pg_typeof('a'::text),
pg_typeof(true), pg_typeof(now()), pg_typeof(current_date);integer | bigint | numeric | double precision | text | boolean | timestamp with time zone | dateLes conversions vers regclass, regtype… et format_type servent à lire le catalogue : voir 40. Catalogue et outils.
36.12 Octets, hachage et encodage#
| Fonction | Rôle | Type |
|---|---|---|
md5(texte_ou_octets) | empreinte MD5, 32 chiffres hexadécimaux | text |
sha224(octets), sha256(octets), sha384(octets), sha512(octets) | empreintes SHA-2 | bytea |
digest(donnees, algorithme) | empreinte md5, sha1, sha224, sha256, sha384 ou sha512 | bytea |
to_hex(entier) | écriture hexadécimale | text |
encode(octets, format) | octets → texte ; formats hex, base64, escape | text |
decode(texte, format) | texte → octets | bytea |
convert_to(texte, encodage) | texte → octets dans l'encodage donné | bytea |
convert_from(octets, encodage) | octets d'un encodage → texte | text |
get_byte(octets, n), set_byte(octets, n, valeur) | octet de rang n (à partir de 0), octets dont cet octet est remplacé | integer, bytea |
length(octets), octet_length(octets) | nombre d'octets | integer |
crypt(mot_de_passe, sel), gen_salt(type) | hachage d'un mot de passe, sel neuf | text |
gen_random_bytes(n) | n octets aléatoires (1 à 1024) | bytea |
SELECT md5('abc'), to_hex(255), to_hex(-1);
SELECT encode('abc'::bytea, 'hex'), encode('abc'::bytea, 'base64'), decode('616263', 'hex'), decode('YWJj', 'base64'),
length(decode('ff00', 'hex'));
SELECT encode(nom::bytea, 'base64'), decode('w4lsb2RpZSBQZXRpdA==', 'base64')::text FROM client WHERE id = 4;900150983cd24fb0d6963f7d28e17f72 | ff | ffffffff
616263 | YWJj | \x616263 | \x616263 | 2
w4lsb2RpZSBQZXRpdA== | Élodie PetitUn bytea est rendu au client en hexadécimal (\x…). Le format escape écrit les octets non imprimables en \ooo (octal) et les lit de même (decode('a\000b', 'escape') vaut \x610062). Format inconnu : 22023 (unrecognized encoding: "bin") ; texte hexadécimal invalide : 22023 (invalid hexadecimal digit).
Empreintes#
sha224 à sha512 rendent l'empreinte en octets : encode(…, 'hex') l'écrit en hexadécimal. digest (de l'extension pgcrypto) accepte un texte ou des octets ; un algorithme inconnu est refusé (22023 : Cannot use "sha3": No such hash algorithm). Les fonctions de MIRAJ sha1(texte) et sha2(texte, bits) rendent directement l'hexadécimal.
SELECT sha256('abc'), encode(sha224('abc'), 'hex'), length(sha384('abc')), length(sha512('abc'));
SELECT encode(digest('abc', 'sha256'), 'hex'), digest('abc', 'md5') = decode(md5('abc'), 'hex'), sha2('a', 256);\xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad | 23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7 | 48 | 64
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad | t | ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bbEncodages et octets#
convert_to et convert_from connaissent les encodages UTF8, LATIN1, WIN1252 et SQL_ASCII (un autre nom : 22023, invalid source encoding name "XYZ"). Un caractère sans équivalent dans l'encodage visé est refusé (22P05 : character with byte sequence 0xe2 0x82 0xac in encoding "UTF8" has no equivalent in encoding "LATIN1"), une suite d'octets invalide aussi (22021 : invalid byte sequence for encoding "UTF8": 0xff). get_byte et set_byte hors des octets : 2202E (index 5 out of valid range, 0..0).
SELECT convert_to('café', 'LATIN1'), convert_to('café', 'UTF8'), convert_from('\x636166e9'::bytea, 'LATIN1'), convert_from('\x80'::bytea, 'WIN1252');
SELECT get_byte('\x1234ff'::bytea, 2), set_byte('\x1234ff'::bytea, 0, 0);\x636166e9 | \x636166c3a9 | café | €
255 | \x0034ffMots de passe et octets aléatoires#
crypt(mot_de_passe, sel) hache un mot de passe selon le sel : un sel neuf de gen_salt('md5') ($1$…) ou gen_salt('des') (2 caractères), ou un hachage existant — ce qui permet de vérifier un mot de passe : crypt(saisi, hachage) = hachage. gen_salt('bf') et gen_salt('xdes') sont refusés (0A000), un autre type aussi (22023 : gen_salt: Unknown salt algorithm). gen_random_bytes(n) hors de 1 à 1024 : 22023 (Length not in range).
SELECT crypt('secret', '$1$abcdefgh'), crypt('secret', '$1$abcdefgh$cHJi5PXp/ki/ktXzqlk6I1') = '$1$abcdefgh$cHJi5PXp/ki/ktXzqlk6I1',
length(gen_salt('des')), gen_salt('md5') LIKE '$1$%', length(gen_random_bytes(16));$1$abcdefgh$cHJi5PXp/ki/ktXzqlk6I1 | t | 2 | t | 1636.13 Système, session, séquences et séries#
Session et serveur#
| Fonction | Rend |
|---|---|
version() | PostgreSQL 16.4 (MIRAJ 1.0.0) on aarch64-macos, 64-bit (la plate-forme du serveur) |
current_database(), current_catalog | miraj : la seule base PostgreSQL de l'instance |
current_schema, current_schema() | la base MIRAJ courante (ventes) |
current_schemas(true) | {pg_catalog,ventes} |
current_user, session_user, user, current_role | le compte connecté, sans son hôte (root) |
pg_backend_pid() | numéro de la connexion |
pg_cancel_backend(pid), pg_terminate_backend(pid [, délai]) | interrompt la requête en cours de la session pid, ferme cette session : t ; f avec un avertissement si pid est inconnu ; 42501 sans droit sur elle (voir 37.10) |
txid_current(), pg_current_xact_id() | numéro de la transaction : positif, le même jusqu'à sa fin (propre à MIRAJ) |
txid_current_if_assigned(), pg_current_xact_id_if_assigned() | le même numéro dans un bloc BEGIN … COMMIT, NULL hors d'un bloc |
txid_current_snapshot(), pg_current_snapshot() | instantané xmin:xmax: (les deux bornes valent le numéro de la transaction, la liste des transactions en cours est vide) |
pg_is_in_recovery() | f |
pg_postmaster_start_time(), pg_conf_load_time() | instant du démarrage du serveur (timestamptz) |
inet_client_addr(), inet_server_addr() | adresse du client ; adresse du serveur pour une connexion en boucle locale, NULL sinon |
pg_my_temp_schema() | 0 |
current_setting(nom [, absent_ok]) | valeur d'un paramètre ; paramètre inconnu : 42704, ou NULL avec absent_ok à true |
set_config(nom, valeur, local) | pose le paramètre et rend valeur : comme SET si local est faux, comme SET LOCAL (jusqu'à la fin de la transaction) s'il est vrai ; appliqué à la fin de l'instruction (voir 37.7) |
pg_reload_conf() | t : les paramètres s'appliquent dès leur écriture, il n'y a rien à relire ; 42501 sans le privilège SUPER |
pg_notify(canal, charge) | même effet que NOTIFY canal, 'charge' ; rend une valeur vide |
pg_typeof(expr) | nom du type de l'expression |
pg_column_size(valeur) | octets qu'occupe la valeur rangée (en-tête de longueur compris pour un texte) |
pg_input_is_valid(texte, type) | le texte est-il une valeur valide du type, sans lever l'erreur ? Types entiers, numeric, flottants, boolean, uuid, json, jsonb, bytea, interval, textes ; type inconnu : 42704 ; date, heure et autres : 0A000 |
pg_sleep(secondes) | attend ; rend 0 ; annulé, échoue en 57014 |
SELECT current_database(), current_schema, current_user, current_setting('server_version'), current_setting('TimeZone'),
current_setting('inconnu.param', true);miraj | ventes | root | 16.4 | Europe/Paris | NULLSELECT pg_input_is_valid('42', 'integer'), pg_input_is_valid('4x', 'integer'), pg_input_is_valid('{"a":', 'jsonb'),
pg_input_is_valid('1 day', 'interval'), pg_input_is_valid('maybe', 'boolean');
SELECT pg_column_size('abc'::text), pg_column_size(true), inet_client_addr(), pg_postmaster_start_time() <= now();t | f | f | t | f
4 | 1 | 127.0.0.1 | tLes fonctions qui décrivent les objets (pg_get_indexdef, pg_get_constraintdef, obj_description, has_table_privilege, pg_table_is_visible, to_regclass…) et les tailles (pg_relation_size, pg_total_relation_size, pg_database_size, pg_size_pretty) sont décrites au chapitre 40. Catalogue et outils.
Identifiants#
gen_random_uuid() (ou uuid_generate_v4(), de l'extension uuid-ossp) rend un UUID aléatoire (version 4), de type uuid, écrit en 36 caractères :
SELECT gen_random_uuid(); -- fe6bc144-804b-49de-9ad2-555c3f027f68Séquences#
| Fonction | Rôle |
|---|---|
nextval('sequence') | avance la séquence et rend la nouvelle valeur (bigint) |
currval('sequence') | dernière valeur rendue par nextval dans la session ; 55000 si la session ne l'a pas encore appelé sur cette séquence |
setval('sequence', n [, appelee]) | fixe la valeur : le nextval suivant rend la valeur d'après, ou n lui-même si appelee est false |
La création d'une séquence est décrite au chapitre 33. Langage DDL.
CREATE SEQUENCE seq_facture START 100 INCREMENT 10;
SELECT nextval('seq_facture'), nextval('seq_facture'); -- 100 | 110
SELECT currval('seq_facture'); -- 110
SELECT setval('seq_facture', 500), nextval('seq_facture'); -- 500 | 510
SELECT setval('seq_facture', 1000, false), nextval('seq_facture'); -- 1000 | 1000
DROP SEQUENCE seq_facture;Séquence inconnue : 42P01. Une colonne serial n'a pas de séquence nommée (pg_get_serial_sequence('article', 'id') rend NULL, et currval('article_id_seq') échoue) : la valeur attribuée se lit par INSERT … RETURNING id (voir 34. Langage DML). lastval() rend la dernière valeur servie à la session, toutes séquences confondues, par nextval ou par une colonne serial / IDENTITY ; tant que la session n'en a reçu aucune, elle rend 55000 (lastval is not yet defined in this session).
generate_series#
generate_series(debut, fin [, pas])Fonction ensembliste : rend les valeurs de debut à fin, de pas en pas (1 par défaut, négatif pour un décompte), une par ligne ; aucune ligne si fin est déjà dépassée. Les bornes sont des entiers, des numeric ou des dates et instants (date, timestamp, timestamptz), le pas étant alors un intervalle (pour un timestamptz, un pas en jours garde l'heure civile de la session). Elle s'emploie dans un FROM (avec WITH ORDINALITY et un alias de colonne AS s(n)) ou dans la liste du SELECT (voir 36.14).
SELECT n, n * n AS carre FROM generate_series(1, 3) AS s(n);
SELECT * FROM generate_series(10, 1, -3) WITH ORDINALITY AS s(n, rang);
SELECT * FROM generate_series(0.5, 1.5, 0.5);
SELECT * FROM generate_series(timestamp '2026-10-11 08:00', timestamp '2026-10-11 10:00', interval '1 hour');
SELECT generate_series(1, 3) AS n, 'x' AS lettre;1 | 1
2 | 4
3 | 9
10 | 1
7 | 2
4 | 3
1 | 4
0.5
1.0
1.5
2026-10-11 08:00:00
2026-10-11 09:00:00
2026-10-11 10:00:00
1 | x
2 | x
3 | xUne série de jours sert de calendrier : nombre de commandes par jour, jours sans commande compris.
SELECT j.jour::date AS jour, count(o.id) AS commandes
FROM generate_series(date '2026-10-04', date '2026-10-07', interval '1 day') AS j(jour)
LEFT JOIN commande o ON o.date_commande = j.jour
GROUP BY j.jour ORDER BY 1; jour | commandes
------------+-----------
2026-10-04 | 0
2026-10-05 | 1
2026-10-06 | 1
2026-10-07 | 0Un pas nul : 22023 (step size cannot equal zero).
Verrous consultatifs#
| Fonction | Rôle |
|---|---|
pg_advisory_lock(clé) | prend le verrou, en attendant s'il est tenu par une autre session |
pg_try_advisory_lock(clé) | prend le verrou sans attendre ; t s'il est pris, f sinon |
pg_advisory_unlock(clé) | rend le verrou ; f, avec un avertissement, si la session ne le tenait pas |
pg_advisory_xact_lock(clé) | prend le verrou jusqu'à la fin de la transaction, en attendant s'il est tenu |
pg_try_advisory_xact_lock(clé) | de même sans attendre ; t s'il est pris, f sinon |
La clé est un bigint ou deux integer (pg_try_advisory_lock(1, 2)). Les verrous de session sont cumulables (pris deux fois, ils se rendent deux fois) et tous rendus à la fin de la session ; ceux de transaction sont rendus au COMMIT ou au ROLLBACK, sans pg_advisory_unlock.
SELECT pg_try_advisory_lock(42), pg_try_advisory_lock(42), pg_advisory_unlock(42), pg_advisory_unlock(42), pg_advisory_unlock(42);
BEGIN;
SELECT pg_try_advisory_xact_lock(8);
COMMIT;t | t | t | t | f
t36.14 Tableaux et fonctions ensemblistes#
Tableaux#
Un tableau est une liste de valeurs d'un même type, écrite ARRAY[v1, v2, …] ou en texte '{v1,v2,…}'::type[]. Il est rendu au client dans l'écriture de PostgreSQL ({1,2,3} ; éléments vides, contenant une virgule, une accolade, un guillemet ou un blanc, entre guillemets ; NULL écrit tel quel) et annoncé sous le type tableau (integer[], text[]…). Le type des éléments est le type commun des valeurs écrites (ARRAY[1, 2.5] est un numeric[]) ; des valeurs incompatibles sont refusées (ARRAY[1, 'a'] : 22P02). Les tableaux sont des valeurs d'expression : une colonne de table ne peut pas être d'un type tableau (0A000).
SELECT ARRAY[1, 2, 3], ARRAY['a', 'b c', NULL], ARRAY[1, 2.5], '{1,2,3}'::int[], ARRAY[]::text[], pg_typeof(ARRAY[1, 2]);
SELECT ARRAY[true, false], ARRAY['', 'a,b', 'q"x'], ARRAY[date '2026-10-11'];{1,2,3} | {a,"b c",NULL} | {1,2.5} | {1,2,3} | {} | integer[]
{t,f} | {"","a,b","q\"x"} | {2026-10-11}| Expression | Rend | Exemple | Résultat |
|---|---|---|---|
tableau[n] | élément de rang n (à partir de 1) ; NULL hors du tableau | (ARRAY[10, 20, 30])[2] | 20 |
array_length(tableau, 1), cardinality(tableau) | nombre d'éléments (array_length : NULL pour une autre dimension ou un tableau vide) | cardinality(ARRAY[1, 2]) | 2 |
array_lower(tableau, 1), array_upper(tableau, 1) | premier et dernier rang | array_upper(ARRAY[1, 2, 3], 1) | 3 |
array_position(tableau, valeur) | rang de la première occurrence ; NULL si absente | array_position(ARRAY['a', 'b'], 'b') | 2 |
array_to_string(tableau, separateur [, texte_null]) | éléments joints par separateur ; les NULL sont sautés, ou remplacés par texte_null | array_to_string(ARRAY[1, NULL, 3], ',', '*') | 1,*,3 |
=, <>, <, >… | comparaison élément par élément | ARRAY[1, 2] < ARRAY[1, 3] | t |
valeur = ANY (tableau), valeur > ALL (tableau) | comparaison à chaque élément | 2 = ANY (ARRAY[1, 2]) | t |
SELECT (ARRAY[10, 20, 30])[2], ('{a,b,c}'::text[])[3], (ARRAY[1])[5], (current_schemas(true))[1];
SELECT array_length(ARRAY[1, 2, 3], 1), array_length(ARRAY[1, 2], 2), cardinality(ARRAY[1, 2]), array_upper(ARRAY[1, 2, 3], 1),
array_position(ARRAY['a', 'b'], 'b'), array_position(ARRAY['a'], 'z');
SELECT array_to_string(ARRAY[1, NULL, 3], ','), array_to_string(ARRAY[1, NULL, 3], ',', '*'), ARRAY[1, 2] = '{1,2}'::int[];
SELECT nom FROM client WHERE ville = ANY (ARRAY['Paris', 'Marseille']);20 | c | NULL | pg_catalog
3 | NULL | 2 | 3 | 2 | NULL
1,3 | 1,*,3 | t
MartinLes fonctions qui rendent un tableau : array_agg, regexp_match, regexp_split_to_array, parse_ident, current_schemas. Les colonnes de tableaux du catalogue (pg_proc.proargnames, pg_constraint.conkey…) se lisent avec les mêmes fonctions.
unnest#
unnest(tableau)Fonction ensembliste : une ligne par élément du tableau, dans l'ordre. Dans un FROM, WITH ORDINALITY ajoute le rang ; le tableau est écrit (ARRAY[…], '{…}'::type[]) ou passé en paramètre (unnest($1::int[])).
SELECT * FROM unnest(ARRAY['rouge', 'vert']) WITH ORDINALITY AS c(couleur, rang);
SELECT max(x), count(*) FROM unnest(ARRAY[3, 1, 2]) AS x;rouge | 1
vert | 2
3 | 3Fonctions 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 du SELECT : la requête rend alors une ligne par valeur, chaque ligne de la source étant répétée. Plusieurs 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 unnest(ARRAY[1, 2]) AS n, unnest(ARRAY['a', 'b', 'c']) AS l;
SELECT unnest(ARRAY[id, id * 10]) AS v FROM client WHERE id <= 2;1 | a
2 | b
NULL | c
1
10
2
20Pour les fonctions JSON et de motifs, un argument qui cite une colonne demande la forme du FROM (0A000 dans la liste du SELECT : jsonb_array_elements_text citing a column in the select list is not supported yet in the PostgreSQL language).
36.15 Fonctions du langage MIRAJ#
Une instance en langage PostgreSQL garde les fonctions du langage MIRAJ, décrites au chapitre 9. Fonctions SQL. Elles s'appellent avec la lexicographie de PostgreSQL (chaînes entre apostrophes, identifiants entre guillemets). Quand une fonction de PostgreSQL porte le même nom, c'est elle qui est appelée : length compte des caractères, format lit un gabarit %s, log(x) est décimal, trunc, to_char, version()… suivent ce chapitre.
Quelques-unes sont utiles là où une fonction de PostgreSQL manque :
| Fonction MIRAJ | Emploi | Exemple | Résultat |
|---|---|---|---|
last_day(date) | dernier jour du mois | last_day(date '2026-02-10') | 2026-02-28 |
datediff(a, b), timestampdiff(unité, a, b) | écart en jours, dans une unité | timestampdiff(day, date '2026-10-01', date '2026-10-11') | 10 |
dayname(d), monthname(d) | nom du jour, du mois | dayname(date '2026-10-11') | Sunday |
sha1(t), sha2(t, bits), crc32(t) | empreintes | sha1('a') | 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 |
substring_index(t, sep, n) | début d'un texte jusqu'au n-ième séparateur | substring_index('a.b.c', '.', 2) | a.b |
find_in_set(t, liste) | rang dans une liste à virgules | find_in_set('b', 'a,b') | 2 |
group_concat(expr ORDER BY … SEPARATOR '…') | concaténation d'un groupe | voir string_agg | |
json_keys, json_remove, json_pretty, json_table… | JSON | voir 36.10 | |
inet_aton(t), inet_ntoa(n) | adresses IPv4 | inet_aton('10.0.0.1') | 167772161 |
Les fonctions vectorielles (vec_distance_euclidean, l2_distance, cosine_distance), avec les opérateurs de pgvector <-> (distance euclidienne), <=> (distance cosinus), <#> (opposé du produit scalaire) et <+> (distance de Manhattan), les fonctions spatiales (st_distance, st_astext…) et la recherche plein texte (MATCH (…) AGAINST (…)) sont aussi disponibles : voir les chapitres 9, 10 et 29.
SELECT '[1,2]'::vector(2) <-> '[1,3]'::vector(2), '[1,2]'::vector(2) <=> '[1,3]'::vector(2), '[1,2]'::vector(2) <#> '[1,3]'::vector(2),
'[1,2]'::vector(2) <+> '[1,3]'::vector(2), l2_distance('[1,2]'::vector(2), '[1,3]'::vector(2)), cosine_distance('[1,2]'::vector(2), '[1,3]'::vector(2));1 | 0.010050506338833531 | -7 | 1 | 1 | 0.010050506338833531Pour comparer en traitant NULL comme une valeur, écrire a IS NOT DISTINCT FROM b.
Une vue, une colonne générée ou une contrainte qui cite une fonction propre à PostgreSQL garde le langage de sa création (voir le 29.10).
36.16 Non pris en charge et écarts#
Fonctions et opérateurs absents#
Sauf mention contraire, la réponse est 42883 (FUNCTION … does not exist).
| Famille | Absent | À la place |
|---|---|---|
| chaînes | string_to_array, string_to_table, to_ascii, pg_client_encoding | regexp_split_to_array, regexp_split_to_table, unaccent |
| chaînes | chaine SIMILAR TO motif, substring(chaine SIMILAR …) : 42601 | ~, LIKE |
| chaînes | chaine ^@ préfixe : 42601 | starts_with(chaine, préfixe) |
| chaînes | littéral Unicode U&'\0041' : 42703 | unistr('\0041'), chr(65) |
| nombres | sinh, cosh, tanh, asinh, acosh, atanh, sind, cosd, tand…, erf, random(a, b), num_nulls, num_nonnulls | floor(random() * n) |
| nombres | opérateurs @ (valeur absolue), |/, ||/, ! : 42601 | abs, sqrt, cbrt |
| comparaison | BETWEEN SYMMETRIC : 42601 ; x LIKE ANY (ARRAY[…]) | BETWEEN least(a, b) AND greatest(a, b) ; OR |
| dates | (a, b) OVERLAPS (c, d) : 42601 | a < d AND c < b |
| dates | date_trunc(champ, t, zone) | date_trunc(champ, t AT TIME ZONE zone) |
| dates | extract(timezone FROM …) : 0A000 | |
| agrégats | xmlagg, range_agg | string_agg, json_agg |
| agrégats | count(DISTINCT a, b) : 42601 | count(DISTINCT a || '/' || b) |
| fenêtres | RANGE avec un décalage : 0A000 (This version of Miraj doesn't yet support 'RANGE with an offset in a window frame') ; GROUPS, EXCLUDE : 42601 | ROWS |
| fenêtres | string_agg(…) OVER, json_agg(…) OVER : 0A000 | |
| JSON | @?, @@ : 42601 | jsonb_path_exists, jsonb_path_match |
| JSON | jsonb_path_query, jsonb_set_lax, jsonb_object, jsonb_to_record, jsonb_populate_record, json_extract_path, row_to_json, array_to_json | jsonb_path_query_array, jsonb_extract_path, json_object, json_table, jsonb_build_object |
| tableaux | colonne d'un type tableau (int[]) : 0A000 ; ARRAY(SELECT …), || entre tableaux : 0A000 | array_agg |
| tableaux | array_append, array_cat, array_remove, array_replace, array_prepend, array_dims, array_ndims, array_fill, trim_array, generate_subscripts | |
| système | pg_get_serial_sequence rend NULL | INSERT … RETURNING, lastval() |
| système | pg_advisory_lock_shared, pg_advisory_unlock_all, inet_server_port, inet_client_port, pg_get_keywords | pg_advisory_lock |
Écarts de comportement#
Ces formes sont acceptées mais ne rendent pas le résultat de PostgreSQL.
| Forme | MIRAJ | PostgreSQL |
|---|---|---|
'a' = 'a ' | vrai (espaces de fin ignorés) | faux |
greatest(1, NULL), least(…) | NULL | le NULL est ignoré |
sqrt(-1), ln(0), asin(2) | NULL | erreur |
ceil(1.2), floor(prix) d'un décimal | integer | numeric |
1 + true | 2 | erreur |
2.5::float8::int | 3 | 2 (arrondi au pair) |
brut || '{"k": 1}' (colonne json, chaîne sans type) | concaténation de textes | erreur (fusion pour un jsonb, ici aussi) |
| nom d'une colonne calculée | le texte écrit (upper(nom)) | le nom de la fonction (upper) ou ?column? |
La liste condensée de ce que la grammaire accepte ou refuse est au chapitre 29.