Mirajv1.0
FR

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#

  • Noms insensibles à la casse. upper, UPPER et Upper désignent la même fonction. Ce chapitre les écrit en minuscules, comme la documentation de PostgreSQL. Le préfixe pg_catalog. est accepté et ignoré (pg_catalog.version()).
  • Propagation de NULL. Sauf mention contraire, une fonction qui reçoit un argument NULL rend NULL. Les exceptions (coalesce, concat, concat_ws, format, quote_nullable, array_to_string…) sont signalées dans leur description ; les agrégats, eux, ignorent les NULL (sauf array_agg, json_agg et jsonb_agg).
  • Positions et longueurs se comptent en caractères, à partir de 1. octet_length et bit_length comptent 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 BY range les majuscules avant les minuscules et les lettres accentuées après z, 'a' et 'A' sont deux valeurs distinctes pour DISTINCT, GROUP BY ou une clé UNIQUE. Pour comparer sans égard à la casse ni aux accents : ILIKE, ~*, lower(x) = lower(y), unaccent ou la collation COLLATE utf8mb4_general_ci (voir 36.3).
  • Fonction inconnue : erreur 42883 (FUNCTION … does not exist) ; mauvais nombre d'arguments : 42883 aussi, 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 client nomme la colonne upper(nom), là où PostgreSQL écrit upper) : 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érateurRôleExempleRésultat
+, -, *addition, soustraction, multiplication7 * 214
/division ; entière (tronquée vers zéro) entre deux entiers7 / 2, -7 / 2, 7 / 2.03, -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 dividende17 % 5, mod(-17, 5)2, -2
^, power(a, b), pow(a, b)puissance2 ^ 101024
-xopposé-(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 | 8

Comme 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érationType
integer + - * / % integerinteger ; smallint entre deux smallint, bigint dès qu'un opérande est un bigint
avec un numericnumeric : é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 realdouble 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 :

InstructionRéponse
SELECT 1 / 0, 5 % 0, 1.0 / 0.0, div(1, 0)22012 : division by zero
SELECT 2147483647 + 122003 : integer out of range (convertir un opérande : 2147483647::bigint + 1 vaut 2147483648)
SELECT 9223372036854775807 + 122003 : 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érateurRôle
=, <>, !=, <, <=, >, >=comparaison ; NULL si un opérande est NULL
a BETWEEN x AND y, a NOT BETWEEN x AND yintervalle 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 NOTNULLtest de NULL
a IS DISTINCT FROM b, a IS NOT DISTINCT FROM bcomparaison qui traite NULL comme une valeur
b IS TRUE, IS NOT TRUE, IS FALSE, IS NOT FALSE, IS UNKNOWNtest 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 | t
SELECT 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 | sans

Un 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érateurRôleExempleRésultat
a & bET5 & 31
a | bOU5 | 37
a # bOU exclusif5 # 36
~acomplément~5-6
a << n, a >> ndécalages1 << 4, -8 >> 116, -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 fonctionRôleExempleRésultat
a & b, a | b, a # bET, OU, OU exclusif de deux chaînes de même longueurB'101' & B'110'100 (bit)
~acomplément, sur les positions de la chaîne~B'101'010
a << n, a >> ndécalages, longueur gardée, zéros entrantsB'10001' >> 200100
a || bconcaténationB'101' || B'11'10111 (bit varying)
length(a), bit_length(a)nombre de positionslength(B'10111')5
octet_length(a)nombre d'octetsoctet_length(B'101100111')2
bit_count(a)nombre de bits à 1bit_count(B'1101')3
get_bit(a, n)position n, à partir de 0 depuis la gaucheget_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 extraitessubstring(B'110010111111' from 3 for 2)00
position(s in a)rang de la sous-chaîneposition(B'010' in B'000001101011')8
overlay(a placing s from d for n)positions remplacéesoverlay(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 :

FonctionRôleType
a || b, concat, concat_wsconcaténationtext
upper, lower, initcapcassetext
length, char_length, character_lengthlongueur en caractèresinteger
octet_length, bit_lengthlongueur en octets, en bitsinteger
trim, btrim, ltrim, rtrimretrait de caractères (ou d'octets) aux extrémitéstext, bytea
substring, substrsous-chaînetext
left, rightdébut, fintext
position, strposposition d'une sous-chaîneinteger
starts_withpréfixeboolean
replace, translate, overlayremplacementtext
lpad, rpad, repeat, reverseremplissage, répétition, inversiontext
split_partn-ième champ d'une chaîne découpéetext
ascii, chrcode d'un caractère, caractère d'un codeinteger, text
formatgabarit %s, %I, %Ltext
quote_ident, quote_literal, quote_nullablecitation pour du SQL construittext
normalize, unistrforme normale Unicode, échappements Unicodetext
parse_identdécoupage d'un nom qualifiétext[]
levenshtein, soundex, difference, similarity, unaccentdistance, code phonétique, ressemblance, retrait des accentsinteger, text, real
md5, sha256, to_hex, encode, decode, convert_to, convert_fromhachage, encodagevoir 36.12
regexp_replace, regexp_match et les expressions régulièresmotifsvoir 36.3
to_char, to_numbermise en forme et lecture d'un nombre ou d'une datevoir 36.6
string_aggconcaténation d'un groupevoir 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 Fontaine

Longueurs#

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 | 3

trim, 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 | a

caracteres 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) rend 42883 (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 un bytea : btrim(octets, octets), ltrim, rtrim, trim([BOTH | LEADING | TRAILING] octets FROM octets). Un littéral sans type y est lu en bytea ('\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 | \x0000ff10

substring 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.fr
SELECT substring('abc123def' FROM '[0-9]+'), substring('abc123def' FROM 'c(\d)'), substring('abc' FROM 'z');
123 | 1 | NULL

Un 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 | cdef

position 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 | t

replace, translate, overlay#

replace(chaine, recherche, remplacement)
translate(chaine, de, vers)
overlay(chaine PLACING nouveau FROM debut [FOR longueur])
  • replace remplace toutes les occurrences de recherche.
  • translate remplace chaque caractère de de par le caractère de même rang de vers ; un caractère de de sans correspondant est supprimé.
  • overlay remplace longueur caractères (la longueur de nouveau par défaut) à partir de debut.
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 | aZZdef

lpad, 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.00

split_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.fr

ascii 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
%sla valeur en texte ; rien pour NULL
%Ila valeur en identifiant SQL, entre guillemets si nécessaire ; NULL : 22004
%Lla valeur en littéral SQL entre apostrophes ; NULL s'écrit NULL
%%le caractère %
%2$sl'argument de rang donné
%10s, %-10s, %*slargeur 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' | NULL

normalize 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) :

FonctionRendType
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 à maxinteger
soundex(texte)code phonétique (lettre et trois chiffres)text
difference(a, b)nombre de caractères communs aux deux codes soundex, de 0 à 4integer
similarity(a, b)ressemblance par trigrammes, de 0 à 1real
unaccent([dictionnaire,] texte)le texte sans ses accents ni ligaturestext
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 Petit

36.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
Dupont

LIKE 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
Dupont

Expressions régulières : ~, ~*, !~, !~*#

OpérateurSens
chaine ~ motifla chaîne contient une correspondance du motif
chaine ~* motifde même, casse et accents ignorés
chaine !~ motif, chaine !~* motifaucune 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
Dupont

Le 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:24

Drapeaux#

Les fonctions regexp_… acceptent une chaîne de drapeaux :

DrapeauEffet
icasse ignorée
ccasse respectée (par défaut)
gtoutes 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
xsyntaxe é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 | 3

regexp_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_substr rend la n-ième correspondance (la première par défaut) trouvée à partir du caractère debut, ou le texte du groupe capturé de rang groupe ; NULL s'il n'y en a pas ;
  • regexp_instr rend la position de cette correspondance (avec fin à 1, la position qui la suit), 0 s'il n'y en a pas ;
  • regexp_like rend un booléen ; comme ~, il respecte la casse, sauf avec le drapeau i.
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 | t

36.4 Fonctions numériques#

Récapitulatif :

FonctionRôleType du résultat
abs(x)valeur absoluecelui de x
sign(x)-1, 0 ou 1entier
round(x), round(x, n)arrondi à l'entier, à n décimalesnumeric (double precision pour un flottant)
trunc(x), trunc(x, n)troncature vers zéronumeric
ceil(x), ceiling(x), floor(x)entier supérieur, inférieurentier pour un décimal (double precision pour un flottant)
mod(a, b), a % breste de la divisioninteger ou numeric
div(a, b), a DIV bquotient entiernumeric ; integer pour DIV
power(a, b), pow(a, b), a ^ bpuissancedouble 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, logarithmesdouble precision
pi(), degrees(x), radians(x)π, conversions d'anglesdouble precision
sin, cos, tan, cot, asin, acos, atan, atan2(y, x)trigonométrie, en radiansdouble precision
gcd(a, b), lcm(a, b)PGCD, PPCMinteger
factorial(n)factoriellenumeric
width_bucket(x, bas, haut, n)rang de l'intervalle qui contient xinteger
scale(x), min_scale(x), trim_scale(x)échelle d'un numeric, échelle sans zéros finals, valeur sans zéros finalsinteger, integer, numeric
greatest(a, b, …), least(a, b, …)plus grande, plus petite valeurtype commun
random(), random_normal([moyenne [, ecart_type]])nombre aléatoire dans [0, 1[, selon une loi normaledouble 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 | 1

round, 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 | -3

Exemple 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.03

mod, 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 | 3

power, 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 | 10
SELECT cbrt(27), cbrt(-8), scale(1.230), min_scale(1.230), trim_scale(1.230), pow(2, 3);
3 | -2 | 3 | 2 | 1.23 | 8

log(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.7853981633974483

gcd, 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 | 6

width_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 | NULL

random#

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 | t

Hors 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#

ExpressionRendType
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 appeltimestamptz
current_timestamp(n)de même, à n décimales de secondetimestamptz
localtimestamp, localtimestamp(n)date et heure courantes de la session, sans fuseautimestamp
current_datedate du jourdate
current_time, current_time(n)heure courante, sans décalagetime
localtime, localtime(n)heure courante de la sessiontime
timeofday()date et heure de l'horloge, en textetext
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:38

Litté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+02

Les 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:00

Arithmétique#

OpérationRésultatExempleValeur
date + entier, date - entierdatedate '2026-10-11' + 72026-10-18
date - datenombre de jours (integer)date '2026-12-25' - date '2026-10-11'75
date + timetimestampdate '2026-10-11' + time '14:30'2026-10-11 14:30:00
date ± intervaltimestampdate '2026-01-31' + INTERVAL '1 month'2026-02-28 00:00:00
timestamp ± intervaltimestamptimestamp '2026-10-11 14:30:00' - INTERVAL '1 week'2026-10-04 14:30:00
timestamptz ± intervaltimestamptznow() + INTERVAL '2 hours'
time ± intervaltime, ramené sur 24 heurestime '23:30' + INTERVAL '2 hours'01:30:00
time - timeintervaltime '16:00' - time '14:30'01:30:00
timestamp - timestampintervaltimestamp '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 | 2

timestamptz 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:00

Intervalles#

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).

ÉcritureAcceptée
unitésmicrosecond(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 standardINTERVAL '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érationRésultatExempleValeur
interval ± intervalchamp par champINTERVAL '1 mon' - INTERVAL '1 day'1 mon -1 days
interval * n, n * interval, interval / n, -intervalchaque champ, la fraction d'un mois passant en jours de 30, celle d'un jour en heuresINTERVAL '1 mon' * 1.51 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, leastpar 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 …), jointurepar 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éeSELECT INTERVAL '1 day' UNION SELECT INTERVAL '24:00:00'1 day
clé primaire, UNIQUE, index uniquepar 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, maxpar duréemax(duree)1 mon
sum, avgchamp 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)).

ChampValeurPour 2026-10-11 14:30:45.5 (un dimanche)
year, month, dayannée, mois, jour2026, 10, 11
hour, minuteheure, minute14, 30
secondsecondes avec leur fraction45.500000
milliseconds, microsecondssecondes en millisecondes, en microsecondes45500.000, 45500000
dowjour de la semaine, 0 = dimanche0
isodowjour de la semaine ISO, 1 = lundi, 7 = dimanche7
doyjour de l'année284
weeksemaine ISO41
isoyearannée de la semaine ISO2026
quartertrimestre4
decade, century, millenniumdécennie, siècle, millénaire202, 21, 3
julianjour julien2461325
epochsecondes depuis le 1er janvier 1970 à 00:00 UTCvoir 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 | 14

epoch 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 | 1791676800

D'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.6666666666666667

extract(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-01

Nombre 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 | 3

Dernier jour du mois d'une date :

SELECT ((date_trunc('month', date '2026-02-10') + INTERVAL '1 month') - INTERVAL '1 day')::date;   -- 2026-02-28

make_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.5

date_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:00

date_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+02

isfinite(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 days

Le 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 | interval

AT TIME ZONE#

valeur AT TIME ZONE 'zone'
  • un timestamptz devient le timestamp de l'heure civile de zone ;
  • un timestamp (sans fuseau) est lu comme une heure de zone et devient un timestamptz.

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+02

to_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:00

36.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ÉcritPour 2026-10-11 14:05:09.123456
YYYY, YYY, YY, Yannée, ses 3, 2 ou 1 derniers chiffres2026, 026, 26, 6
IYYYannée de la semaine ISO2026
MMmois, 01 à 1210
Month, MONTH, monthnom du mois en anglais, complété à 9 caractèresOctober
Mon, MON, monnom abrégéOct
DDjour du mois, 01 à 3111
DDDjour de l'année284
Day, DAY, daynom du jour en anglais, complété à 9 caractèresSunday
Dy, DY, dynom abrégéSun
Djour de la semaine, 1 = dimanche1
IDjour de la semaine ISO, 1 = lundi7
IW, WWsemaine ISO, semaine de l'année41, 41
Qtrimestre4
CCsiècle21
Jjour julien2461325
HH24heure, 00 à 2314
HH12, HHheure, 01 à 1202
MI, SSminutes, secondes05, 09
SSSSsecondes depuis minuit50709
MS, USmillisecondes, microsecondes123, 123456
AM, PMindicateur de demi-journéePM
TH, th (après un nombre)suffixe ordinal anglais11TH, 11th
FM (devant un motif)sans zéros de tête ni espaces de complémentFMDD : 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 | 14h05
SELECT 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-T2

Les noms de jours et de mois sont en anglais. Motifs de plus :

MotifSens
FF1 à FF6fraction de seconde, de 1 à 6 chiffres (FF3 : millisecondes)
RM, rmmois en chiffres romains (XI), complété à 4 caractères sans FM
TZ, tzabré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
OFdécalage du fuseau par rapport à UTC (+02, +05:30) ; +00 pour un timestamp
TZH, TZMheures et minutes du décalage (+02, 00)
Wsemaine 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 October

to_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 h

to_char d'un nombre#

to_char(nombre, modele)
MotifSens
9un chiffre ; un zéro non significatif est remplacé par un espace
0un chiffre ; les zéros de tête sont écrits
., Dséparateur décimal (un point)
,, Gséparateur de milliers (une virgule)
Ssigne + ou -, à la place écrite
MIsigne - en fin pour un nombre négatif
Lsymbole monétaire de lc_monetary ; celui-ci vaut C : un espace
PRnombre négatif entre chevrons (<485>), positif entre deux espaces
RN, rnchiffres romains (majuscules, minuscules) de l'entier arrondi, de 1 à 3999, sur 15 positions alignées à droite ; hors de ces bornes : 15 #
EEEEnotation scientifique, avec autant de décimales que le modèle (9.99EEEE)
Vnombre multiplié par 10 puissance le nombre de chiffres qui suivent V (99V9)
FMsans 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.13
SELECT 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         | 000000

Le 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 | numeric

Un 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+02

Un champ absent prend sa valeur de début (to_date('10/2026', 'MM/YYYY') rend 2026-10-01). Erreurs :

InstructionRé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] END

Rend 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     | rupture

La 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 | inconnue

nullif#

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   | NULL

La 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 | 0

Un 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 | t

36.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.

FonctionRôleType du résultat
count(*), count(expr), count(DISTINCT expr)nombre de lignes, de valeurs non NULL, de valeurs distinctesbigint
sum(expr)sommebigint pour des entiers, numeric pour des numeric, double precision pour des flottants
avg(expr)moyennenumeric à 16 décimales au moins ; double precision pour des flottants
min(expr), max(expr)plus petite, plus grande valeurcelui de expr
any_value(expr)une des valeurs non NULL du groupecelui de expr
string_agg(expr, separateur)concaténationtext
array_agg(expr)tableau des valeurstableau (integer[], text[]…)
json_agg, jsonb_agg, json_object_agg, jsonb_object_aggtableau ou objet JSONjson ou jsonb
bool_and(cond), bool_or(cond), every(cond)toutes vraies, une vraieboolean
stddev, stddev_samp, stddev_pop, variance, var_samp, var_popécart-type, variancenumeric ou double precision
corr, covar_pop, covar_samp, regr_*corrélation, covariance, régression linéairedouble precision (regr_count : bigint)
percentile_cont, percentile_disc, modequantiles, valeur la plus fréquente (WITHIN GROUP)double precision, celui de la valeur
rank, dense_rank, percent_rank, cume_dist avec WITHIN GROUPrang qu'aurait une valeur dans le groupebigint, 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.1500000000000000

avg 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      | 1

min 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.8666666666666667

Une 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 Petit
SELECT string_agg(reference || '=' || stock, '; ' ORDER BY stock DESC) FROM article;
STY-001=500; CAH-096=120; CLA-A4=35; AGR-024=0

Sur 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#

FonctionCalcule
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.42461160239883

Corré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).

FonctionCalcule
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 | 4

Agré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_dist
  • percentile_cont rend le quantile fraction (de 0 à 1) des valeurs triées, interpolé entre les deux valeurs voisines (double precision) ; percentile_cont(0.5) est la médiane ;
  • percentile_disc rend la première valeur du groupe dont le rang atteint la fraction, sans interpolation (type de expr) ;
  • mode() rend la valeur la plus fréquente ;
  • rank, dense_rank, percent_rank et cume_dist à argument rendent le rang qu'aurait valeur si elle était ajoutée au groupe (NULLS FIRST / NULLS LAST accepté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.7142857142857143

Regroupements 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  | 4

36.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
FonctionRôleType
row_number()numéro de la ligne dans sa partitionbigint
rank()rang, avec des trous après des ex æquobigint
dense_rank()rang sans trousbigint
percent_rank()rang relatif, de 0 à 1double precision
cume_dist()part des lignes de rang inférieur ou égaldouble precision
ntile(n)numéro du groupe, la partition étant coupée en n groupesinteger
lag(expr [, decalage [, defaut]])valeur d'une ligne précédentecelui de expr
lead(expr [, decalage [, defaut]])valeur d'une ligne suivantecelui de expr
first_value(expr), last_value(expr)première, dernière valeur du cadrecelui de expr
nth_value(expr, n)n-ième valeur du cadrecelui de expr
count, sum, avg, min, max, stddev, variance…, bool_and, bool_or, everyagrégat sur le cadrecelui 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    | 1
SELECT 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    | 4

Une 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.00

lag 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.50

Nombre 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    | 3

first_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.50

Agré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              | 1

Cumul, 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.3

FILTER 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.7

Cadres 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-09

36.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érateurRôleExempleRé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 -> -13
doc ->> 'clé', doc ->> nde 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 @> ba contient b'{"a": 1, "b": 2}'::jsonb @> '{"a": 1}'t
a <@ ba 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 || bfusion 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 - ntableau 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 membre null rend le JSON null ; ->> rend NULL.
  • -> et #> rendent du jsonb sur une colonne ou une valeur jsonb, du json sur du json ; ->> et #>> rendent du text.
  • ->> 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    | 1

Existence 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     | NULL

Les 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#

FonctionRend
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#

FonctionRend
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 | NULL
SELECT 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é, NULL s'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 | f

Fonctions ensemblistes JSON#

FonctionRend une ligne parColonnes
jsonb_each(doc), json_each(doc)membre de l'objetkey (texte), value (JSON)
jsonb_each_text(doc), json_each_text(doc)membre de l'objetkey, value (texte)
jsonb_array_elements(doc), json_array_elements(doc)élément du tableauvalue (JSON)
jsonb_array_elements_text(doc), json_array_elements_text(doc)élément du tableauvalue (texte)
jsonb_object_keys(doc), json_object_keys(doc)clé de l'objetune 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   | tags

Fonctions JSON du langage MIRAJ#

Les fonctions JSON de MIRAJ restent disponibles, avec leurs chemins écrits '$.a.b[0]' :

FonctionEmploi
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_searchlecture 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   | papier

Leur 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 jsonb est 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}'::jsonb s'affiche {"a": 3, "b": 1}, et '{"a": 1, "b": 2}'::jsonb = '{"b": 2, "a": 1}'::jsonb est vrai. Un json garde le texte écrit. Deux jsonb se comparent par valeur, dans l'ordre propre au type ('1.0'::jsonb = '1'::jsonb est vrai, '2'::jsonb < '10'::jsonb aussi ; voir 32.8), y compris pour ORDER BY, DISTINCT, GROUP BY, UNION et une clé de jointure. Restes : un document rangé dans une colonne jsonb par une version antérieure, ou écrit en langage MIRAJ, garde son texte jusqu'à sa prochaine écriture en langage PostgreSQL (UPDATE t SET doc = doc::jsonb le normalise) ; une clé primaire, une contrainte UNIQUE et la recherche par un index sur un jsonb portent sur le texte du document.
  • Un document illisible est refusé (22P02), dans une colonne json ou jsonb comme dans la conversion d'un texte : '{"a": 1'::jsonb.
  • Les résultats de ||, -, #-, jsonb_set, jsonb_build_object, jsonb_build_array, to_jsonb et des agrégats jsonb_agg / jsonb_object_agg sont des jsonb (type annoncé au client et pg_typeof), normalisés ; ceux de json_build_object, json_build_array, to_json, json_agg et json_object_agg sont des json, dans l'ordre écrit. ||, -, #- et jsonb_set n'existent que pour un jsonb, comme dans PostgreSQL : sur un json (colonne, ::json, résultat d'une fonction json_*), ils sont refusés (42883 : operator does not exist: json || json) ; convertir par ::jsonb. CASE … THEN doc END et coalesce gardent le type de leurs valeurs (jsonb, json, uuid, intervalle, énumération) ; max(doc) et min(doc) d'un jsonb, absents de PostgreSQL, suivent l'ordre du type et rendent un jsonb.

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
ConversionRègleExempleRésultat
nombre décimal → entierarrondi au plus proche3.7::int, 2.5::int, (-3.7)::int4, 3, -4
→ numeric(p,s)arrondi à s décimales123.456::numeric(5,2)123.46
→ numeric sans précisionvaleur gardée, sans zéros finals'1.50'::numeric1.5
→ textécriture du nombre, de la date1.50::text, date '2026-10-11'::text1.50, 2026-10-11
→ varchar(n)tronqué à n caractères42::varchar(1)4
timestamp → date, timepartie date, partie heure'2026-10-11 14:30:45'::timestamp::date2026-10-11
date → timestampà minuitdate '2026-10-11'::timestamp2026-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 → timestamptzdécalage (+02, Z) ou nom de fuseau lus, instant affiché dans le fuseau de la session'2026-10-11T12:30:00Z'::timestamptz2026-10-11 14:30:00+02
texte → byteaoctets du texte, ou hexadécimal \x…'\x414243'::bytea\x414243
bytea → texttexte UTF-8 des octets'\x414243'::bytea::textABC
texte hexadécimal, octal, binaire → entierpréfixes 0x, 0o, 0b, séparateur _'0x1F'::int, '0b101'::int, '1_000'::int31, 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 :

InstructionRéponse
SELECT 'abc'::int22P02 : invalid input syntax for type integer: "abc"
SELECT '1.5'::int, SELECT ''::int22P02
SELECT 'x'::numeric22P02 : invalid input syntax for type numeric: "x"
SELECT 99999::smallint22003 : smallint out of range
SELECT 3000000000::int22003 : integer out of range
SELECT 'maybe'::boolean22P02 : invalid input syntax for type boolean: "maybe"
SELECT 2.5::boolean42846 : cannot cast type numeric to boolean
SELECT '{"a": 1}'::json || '{"b": 2}'::json42883 : operator does not exist: json || json
SELECT 'abc'::interval22P02 : 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'::date22008 : date/time field value out of range: "2026-02-30"
SELECT 'pas-un-uuid'::uuid22P02 : invalid input syntax for type uuid: "pas-un-uuid"
SELECT '{"a": 1'::jsonb22P02 : 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 | date

Les conversions vers regclass, regtype… et format_type servent à lire le catalogue : voir 40. Catalogue et outils.


36.12 Octets, hachage et encodage#

FonctionRôleType
md5(texte_ou_octets)empreinte MD5, 32 chiffres hexadécimauxtext
sha224(octets), sha256(octets), sha384(octets), sha512(octets)empreintes SHA-2bytea
digest(donnees, algorithme)empreinte md5, sha1, sha224, sha256, sha384 ou sha512bytea
to_hex(entier)écriture hexadécimaletext
encode(octets, format)octets → texte ; formats hex, base64, escapetext
decode(texte, format)texte → octetsbytea
convert_to(texte, encodage)texte → octets dans l'encodage donnébytea
convert_from(octets, encodage)octets d'un encodage → textetext
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'octetsinteger
crypt(mot_de_passe, sel), gen_salt(type)hachage d'un mot de passe, sel neuftext
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 Petit

Un 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 | ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb

Encodages 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 | \x0034ff

Mots 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 | 16

36.13 Système, session, séquences et séries#

Session et serveur#

FonctionRend
version()PostgreSQL 16.4 (MIRAJ 1.0.0) on aarch64-macos, 64-bit (la plate-forme du serveur)
current_database(), current_catalogmiraj : 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_rolele 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 | NULL
SELECT 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 | t

Les 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-555c3f027f68

Séquences#

FonctionRô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 | x

Une 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 | 0

Un pas nul : 22023 (step size cannot equal zero).

Verrous consultatifs#

FonctionRô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
t

36.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}
ExpressionRendExempleRé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 rangarray_upper(ARRAY[1, 2, 3], 1)3
array_position(tableau, valeur)rang de la première occurrence ; NULL si absentearray_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_nullarray_to_string(ARRAY[1, NULL, 3], ',', '*')1,*,3
=, <>, <, >…comparaison élément par élémentARRAY[1, 2] < ARRAY[1, 3]t
valeur = ANY (tableau), valeur > ALL (tableau)comparaison à chaque élément2 = 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
Martin

Les 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 | 3

Fonctions ensemblistes dans la liste du SELECT#

generate_series, unnest, regexp_matches, regexp_split_to_table, json[b]_array_elements[_text] et json[b]_object_keys s'écrivent aussi dans la liste 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
20

Pour 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 MIRAJEmploiExempleRésultat
last_day(date)dernier jour du moislast_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 moisdayname(date '2026-10-11')Sunday
sha1(t), sha2(t, bits), crc32(t)empreintessha1('a')86f7e437faa5a7fce15d1ddcb9eaeaea377667b8
substring_index(t, sep, n)début d'un texte jusqu'au n-ième séparateursubstring_index('a.b.c', '.', 2)a.b
find_in_set(t, liste)rang dans une liste à virgulesfind_in_set('b', 'a,b')2
group_concat(expr ORDER BY … SEPARATOR '…')concaténation d'un groupevoir string_agg
json_keys, json_remove, json_pretty, json_table…JSONvoir 36.10
inet_aton(t), inet_ntoa(n)adresses IPv4inet_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.010050506338833531

Pour 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).

FamilleAbsentÀ la place
chaînesstring_to_array, string_to_table, to_ascii, pg_client_encodingregexp_split_to_array, regexp_split_to_table, unaccent
chaîneschaine SIMILAR TO motif, substring(chaine SIMILAR …) : 42601~, LIKE
chaîneschaine ^@ préfixe : 42601starts_with(chaine, préfixe)
chaîneslittéral Unicode U&'\0041' : 42703unistr('\0041'), chr(65)
nombressinh, cosh, tanh, asinh, acosh, atanh, sind, cosd, tand…, erf, random(a, b), num_nulls, num_nonnullsfloor(random() * n)
nombresopérateurs @ (valeur absolue), |/, ||/, ! : 42601abs, sqrt, cbrt
comparaisonBETWEEN SYMMETRIC : 42601 ; x LIKE ANY (ARRAY[…])BETWEEN least(a, b) AND greatest(a, b) ; OR
dates(a, b) OVERLAPS (c, d) : 42601a < d AND c < b
datesdate_trunc(champ, t, zone)date_trunc(champ, t AT TIME ZONE zone)
datesextract(timezone FROM …) : 0A000
agrégatsxmlagg, range_aggstring_agg, json_agg
agrégatscount(DISTINCT a, b) : 42601count(DISTINCT a || '/' || b)
fenêtresRANGE avec un décalage : 0A000 (This version of Miraj doesn't yet support 'RANGE with an offset in a window frame') ; GROUPS, EXCLUDE : 42601ROWS
fenêtresstring_agg(…) OVER, json_agg(…) OVER : 0A000
JSON@?, @@ : 42601jsonb_path_exists, jsonb_path_match
JSONjsonb_path_query, jsonb_set_lax, jsonb_object, jsonb_to_record, jsonb_populate_record, json_extract_path, row_to_json, array_to_jsonjsonb_path_query_array, jsonb_extract_path, json_object, json_table, jsonb_build_object
tableauxcolonne d'un type tableau (int[]) : 0A000 ; ARRAY(SELECT …), || entre tableaux : 0A000array_agg
tableauxarray_append, array_cat, array_remove, array_replace, array_prepend, array_dims, array_ndims, array_fill, trim_array, generate_subscripts
systèmepg_get_serial_sequence rend NULLINSERT … RETURNING, lastval()
systèmepg_advisory_lock_shared, pg_advisory_unlock_all, inet_server_port, inet_client_port, pg_get_keywordspg_advisory_lock

Écarts de comportement#

Ces formes sont acceptées mais ne rendent pas le résultat de PostgreSQL.

FormeMIRAJPostgreSQL
'a' = 'a 'vrai (espaces de fin ignorés)faux
greatest(1, NULL), least(…)NULLle NULL est ignoré
sqrt(-1), ln(0), asin(2)NULLerreur
ceil(1.2), floor(prix) d'un décimalintegernumeric
1 + true2erreur
2.5::float8::int32 (arrondi au pair)
brut || '{"k": 1}' (colonne json, chaîne sans type)concaténation de texteserreur (fusion pour un jsonb, ici aussi)
nom d'une colonne calculéele 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.