33. PostgreSQL : Types de données
Une instance MIRAJ en langage PostgreSQL accepte les noms de types de PostgreSQL, dans les définitions de colonnes comme dans les conversions (x::type, CAST(x AS type)). Chaque type est rangé dans un type du moteur MIRAJ, puis annoncé au client sous son nom et son numéro (OID) PostgreSQL : psql, psycopg2, pgjdbc ou Npgsql reçoivent des int4, des numeric, des timestamptz, des jsonb.
Ce chapitre décrit chaque famille de types, les valeurs qu'elle accepte, les erreurs rendues, puis les conversions, NULL, les valeurs par défaut, les colonnes d'identité et les colonnes générées. Les différences avec un serveur PostgreSQL sont signalées au fil du texte et rassemblées en 33.17.
Les exemples s'exécutent dans le schéma ventes :
CREATE SCHEMA ventes;
SET search_path TO ventes;33.1 Sommaire des familles de types#
| Famille | Types acceptés | Annoncé au client |
|---|---|---|
| Entiers | smallint (int2), integer (int, int4), bigint (int8) | int2, int4, int8 |
| Identité | smallserial, serial, bigserial, GENERATED … AS IDENTITY | l'entier correspondant |
| Nombres exacts | numeric(p,s), decimal(p,s), numeric | numeric |
| Flottants | real (float4), double precision (float8, float) | float4, float8 |
| Booléens | boolean (bool) | bool |
| Chaînes | char(n) (character(n)), varchar(n) (character varying(n)), text, varchar, name | bpchar, varchar, text |
| Octets | bytea | bytea |
| Chaînes de bits | bit(n), bit, bit varying(n) (varbit(n)), bit varying | bit, varbit |
| Dates et heures | date, time[(p)], timestamp[(p)], timestamptz[(p)] (timestamp with time zone) | date, time, timestamp, timestamptz |
| Documents | json, jsonb | json, jsonb |
| Identifiants | uuid | uuid |
| Vecteurs | vector(n) | vector |
| Tableaux (valeurs d'expression) | ARRAY[…], '{…}'::type[] | int4[], text[], numeric[]… |
Les mots-clés sont reconnus quelle que soit leur casse. Les noms de colonnes et de tables sont, eux, insensibles à la casse même entre guillemets : "Client" et client désignent la même table.
Le type d'une colonne se relit par format_type, par information_schema.columns ou par pg_typeof :
CREATE TABLE prix (
id integer PRIMARY KEY,
montant numeric(10,2) NOT NULL,
taux real,
poids double precision,
libre numeric
);
SELECT a.attname, format_type(a.atttypid, a.atttypmod) AS type
FROM pg_attribute a
WHERE a.attrelid = 'prix'::regclass AND a.attnum > 0
ORDER BY a.attnum; attname | type
---------+------------------
id | integer
montant | numeric(10,2)
taux | real
poids | double precision
libre | numericUne colonne de table reprise telle quelle par une requête est annoncée au client comme PostgreSQL le fait : son type déclaré, son modificateur (varchar(20), numeric(10,2), timestamp(3)), l'OID de sa table et son numéro de colonne. C'est le cas en lecture directe, après DISTINCT, après GROUP BY (clé de groupement citée par son nom, un alias ou une position, colonne que la clé primaire groupée détermine, *), au travers d'une table dérivée ou d'une expression de table ; au travers d'une vue, la relation annoncée est la vue. UNION, INTERSECT et EXCEPT gardent le type et le modificateur que partagent tous leurs membres, sans relation ; des membres du même type varchar ou char aux longueurs différentes (varchar(20) et varchar(10)), ou un varchar(n) uni à un texte écrit sans type ou à NULL, gardent le type sans modificateur. Une expression ou un agrégat suit le type de son calcul, sans modificateur ni relation. pg_typeof nomme de même le type déclaré après GROUP BY (character varying, et non text).
SELECT montant, count(*) FROM prix GROUP BY montant;
-- colonne montant : numeric(10,2), table prix, colonne 2 ; count : bigint
SELECT montant FROM prix UNION ALL SELECT montant FROM prix;
-- colonne montant : numeric(10,2), sans tableUne colonne de vue est annoncée comme la requête qui définit la vue l'annonce quand on l'exécute directement, la relation annoncée étant la vue : même type et même modificateur à chaque consultation (que MIRAJ serve le résultat qu'il garde de la vue ou rejoue sa requête), dans tout fuseau, et dans pg_attribute comme dans information_schema.columns. Une colonne calculée suit le type de son expression, une conversion écrite garde son type, une vue lue par une autre vue garde le type déclaré de la colonne de base :
CREATE VIEW v_prix AS
SELECT id, montant, 'n° ' || id AS libelle, id::smallint AS court, taux::numeric(6,2) AS arrondi
FROM prix;
SELECT * FROM v_prix;
-- id : integer ; montant : numeric(10,2) ; libelle : text ; court : smallint ; arrondi : numeric
SELECT pg_typeof(libelle), pg_typeof(court) FROM v_prix; -- text | smallintLe modificateur d'une conversion écrite (::numeric(6,2), ::varchar(5), ::timestamptz(0)) n'est pas annoncé au client, dans une requête comme au travers d'une vue (PostgreSQL l'annonce) ; la valeur, elle, est bien arrondie ou tronquée. Celui d'un ::char(n) est annoncé : il donne la longueur à laquelle la valeur est complétée.
Correspondance entre l'écriture et le type gardé :
| Écrit | Type gardé (format_type) |
|---|---|
int2, smallint | smallint |
int, int4, integer | integer |
int8, bigint | bigint |
float4, real, float(1) à float(24) | real |
float8, double precision, float, float(25) à float(53) | double precision |
numeric(p,s), decimal(p,s) | numeric(p,s) |
numeric(p) | numeric(p,0) |
numeric, decimal | numeric |
bool, boolean | boolean |
char(n), character(n) | character(n) |
char, character, "char" | character(1) |
bpchar(n) | character(n) |
bpchar sans longueur | bpchar |
varchar(n), character varying(n) | character varying(n) |
varchar, text | text |
name | character varying(63) |
time, time without time zone, timetz | time without time zone |
time(p) | time(p) without time zone |
timestamp, timestamp without time zone | timestamp without time zone |
timestamp(p) | timestamp(p) without time zone |
timestamptz, timestamp with time zone | timestamp with time zone |
33.2 Entiers#
CREATE TABLE mesure (
id integer PRIMARY KEY,
quantite smallint NOT NULL,
total bigint
);
INSERT INTO mesure VALUES (1, 32767, 9223372036854775807);| Type | Octets | Plage |
|---|---|---|
smallint | 2 | −32 768 à 32 767 |
integer | 4 | −2 147 483 648 à 2 147 483 647 |
bigint | 8 | −9 223 372 036 854 775 808 à 9 223 372 036 854 775 807 |
Une valeur hors plage écrite dans une colonne est refusée (SQLSTATE 22003) :
INSERT INTO mesure VALUES (2, 32768, 0);ERROR 22003: Out of range value for column 'quantite' at row 1Littéraux et calculs. Un entier écrit qui tient sur 32 bits est un integer, sinon un bigint ; un nombre écrit avec un point ou un exposant (1.5, 1e0) est un numeric.
SELECT pg_typeof(1), pg_typeof(3000000000), pg_typeof(1.5), pg_typeof(1e0);
-- integer | bigint | numeric | numeric
SELECT 2147483647 + 1;
-- ERROR 22003: integer out of range
SELECT 2147483647::bigint + 1;
-- 2147483648Un résultat entier rendu au client qui dépasse son type est une erreur 22003, comme dans PostgreSQL ; convertir un opérande en bigint élargit le calcul.
Entre deux entiers, la division / est entière, tronquée vers zéro, et son résultat garde le type entier ; dès qu'un opérande est un numeric, le quotient est décimal :
SELECT 7 / 2 AS quotient, -7 / 2 AS negatif, 7.0 / 2 AS decimal, div(7, 2) AS entier, 7 % 2 AS reste; quotient | negatif | decimal | entier | reste
----------+---------+--------------------+--------+-------
3 | -3 | 3.5000000000000000 | 3 | 1Le quotient d'un numeric a au moins seize chiffres significatifs (1 / 3.0 vaut 0.33333333333333333333), et jamais moins de décimales qu'un de ses opérandes. Une division ou un modulo par zéro est une erreur 22012 (division by zero), pour les entiers, les numeric et les flottants.
Il n'y a pas de type entier sans signe ni d'entier sur un octet dans PostgreSQL ; les types correspondants de MIRAJ restent acceptés (voir 33.11).
33.3 Nombres exacts et flottants#
INSERT INTO prix VALUES (1, 1234.567, 0.1, 0.1, 1.5),
(2, -0.005, 1.5, 1e100, 0.123456789012345);
SELECT * FROM prix ORDER BY id; id | montant | taux | poids | libre
----+---------+------+--------+-------------------
1 | 1234.57 | 0.1 | 0.1 | 1.5
2 | -0.01 | 1.5 | 1e+100 | 0.123456789012345numeric(p,s) et decimal(p,s)#
Nombre décimal exact de p chiffres dont s après la virgule. Une valeur écrite avec plus de décimales est arrondie à l'échelle de la colonne (au plus proche, le demi s'éloignant de zéro : -0.005 devient -0.01, 1.005::numeric(10,2) vaut 1.01) ; une valeur dont la partie entière ne tient pas est refusée :
INSERT INTO prix VALUES (3, 123456789.00, NULL, NULL, NULL);
-- ERROR 22003: Out of range value for column 'montant' at row 1| Paramètre | Limite | Erreur si dépassée |
|---|---|---|
précision p | 65 | 22023 : Too big precision specified for 'n'. Maximum is 65 |
échelle s | 30 | 22023 : Too big scale 31 specified for 'n'. Maximum is 30 |
s ≤ p | obligatoire | 22023 : For float(M,D), double(M,D) or decimal(M,D), M must be >= D (column 'n') |
numeric sans précision garde jusqu'à vingt chiffres avant la virgule et dix-huit après ; la valeur est affichée sans zéros finals inutiles (la colonne libre ci-dessus rend 1.5 et 0.123456789012345). Une dix-neuvième décimale est arrondie ; une partie entière de plus de vingt chiffres est refusée (22003). Il en va de même d'une conversion :
SELECT 1::numeric AS a, '12.5'::numeric AS b, 0.1234567890123456789012::numeric AS c;
-- 1 | 12.5 | 0.123456789012345679
SELECT 123456789012345678901::numeric;
-- ERROR 22003: numeric field overflow: A field with precision 38, scale 18 must round to an absolute value less than 10^20.Donner une précision et une échelle explicites (numeric(40,0)) pour un nombre plus grand.
Les calculs entre numeric sont exacts ; sum d'une colonne numeric est un numeric, avg un numeric à seize décimales au moins :
SELECT 1.10 + 2.205, 2.5 * 4, round(2.5), round(-2.5), round(2.345, 2);
-- 3.305 | 10.0 | 3 | -3 | 2.35'NaN' et les infinis ne sont pas des valeurs numeric.
real et double precision#
Flottants binaires sur 4 et 8 octets (IEEE 754), donc approchés : 0.1::float8 + 0.2::float8 vaut 0.30000000000000004. Les valeurs sont affichées au plus court (0.1, 1e+100).
| Type | Synonymes | Grandeur maximale |
|---|---|---|
real | float4, float(n) pour n ≤ 24 | environ 3,4 × 10³⁸ |
double precision | float8, float, float(n) pour n ≥ 25 | environ 1,8 × 10³⁰⁸ |
Une valeur trop grande pour une colonne real est refusée (22003). 'NaN', 'Infinity' et '-Infinity' se convertissent en float8 dans une expression, mais ne s'écrivent pas dans une colonne :
SELECT 'NaN'::float8, 'Infinity'::float8, '-Infinity'::float8;
-- NaN | Infinity | -Infinity
INSERT INTO prix (id, montant, poids) VALUES (9, 0, 'NaN');
-- ERROR 22007: Incorrect double value: 'NaN' for column "ventes"."prix"."poids" at row 1Une somme de deux real est calculée et annoncée en double precision.
33.4 Booléens#
CREATE TABLE option_client (
client_id integer PRIMARY KEY,
actif boolean NOT NULL DEFAULT true,
newsletter boolean
);
INSERT INTO option_client VALUES (1, true, 'yes'), (2, false, 'off'), (3, 't', NULL), (4, '0', 'n');
SELECT * FROM option_client ORDER BY client_id; client_id | actif | newsletter
-----------+-------+------------
1 | t | t
2 | f | f
3 | t | NULL
4 | f | fUne colonne boolean a trois états : vrai, faux et NULL. Elle est rendue t ou f (type bool), que les pilotes convertissent dans le booléen de leur langage.
| Écriture | Valeur |
|---|---|
TRUE, 't', 'true', 'yes', 'on', '1', 1 | vrai |
FALSE, 'f', 'false', 'no', 'off', '0', 0 | faux |
Les textes sont lus sans égard à la casse et leurs préfixes sont admis ('tr', 'n'). Un autre texte est refusé :
INSERT INTO option_client VALUES (5, 'maybe', NULL);
-- ERROR 22P02: invalid input syntax for type boolean: "maybe"Une colonne booléenne s'emploie directement comme condition, et une condition placée dans la liste d'un SELECT est un booléen :
SELECT client_id FROM option_client WHERE actif AND newsletter IS NOT TRUE; -- 3
SELECT client_id FROM option_client WHERE NOT actif; -- 2, 4
SELECT 1 = 1, 1 < 0; -- t | fUn booléen converti en texte s'écrit true ou false (true::text, CAST(actif AS varchar), 'x' || true vaut xtrue) ; concat et concat_ws l'écrivent t ou f (concat('x', true) vaut xt), comme PostgreSQL. Un texte se convertit en booléen par ::boolean (t, true, yes, on, 1, leurs contraires et leurs préfixes ; 22P02 pour un autre texte), que le texte soit écrit ou calculé ((doc ->> 'actif')::boolean). Un entier aussi (1::boolean) ; un numeric, un flottant, une date ou une heure ne se convertissent pas (32.5::boolean : 42846, cannot cast type numeric to boolean). Une valeur par défaut écrite en chaîne est lue de même : boolean DEFAULT 'yes' vaut DEFAULT true.
Écarts avec PostgreSQL :
- un booléen s'additionne comme un entier :
(1 = 1) + 1vaut2(PostgreSQL le refuse) ; - un entier s'écrit dans une colonne booléenne sans conversion (
INSERT … VALUES (1, 0)).
33.5 Chaînes de caractères#
CREATE TABLE client (
id integer PRIMARY KEY,
code char(6) NOT NULL,
nom varchar(80) NOT NULL,
notes text
);
INSERT INTO client VALUES (1, 'C001', 'Dupont', 'Client historique'),
(2, 'C002', 'Élodie Martin', NULL);| Type | Contenu |
|---|---|
char(n), character(n) | au plus n caractères, complétés par des espaces à l'affichage ; char seul vaut char(1) |
varchar(n), character varying(n) | au plus n caractères |
text, varchar sans longueur | longueur libre |
bpchar sans longueur | longueur libre ; espaces finaux sans signification |
name | varchar(63) |
Les textes sont en UTF-8 ; les longueurs se comptent en caractères : length('日本語') vaut 3, octet_length('日本語') vaut 9, length('😀') vaut 1.
Un texte trop long pour sa colonne est refusé ; seuls des espaces en trop à la fin sont coupés (avec un avis) :
INSERT INTO client VALUES (3, 'C0000003', 'Trop long', NULL);
-- ERROR 22001: Data too long for column 'code' at row 1Une conversion explicite, elle, tronque : 'abcdef'::varchar(3) vaut abc (avec un avis Truncated incorrect CHAR(3) value).
char(n) est complété par des espaces à l'affichage, comme dans PostgreSQL : la valeur est envoyée au client sur n caractères. Ces espaces de remplissage ne comptent ni pour length, ni dans une concaténation, ni dans une conversion en text ou en varchar, ni dans les comparaisons (code = 'C001' et code = 'C001 ' trouvent la même ligne, par l'index de la colonne) ; octet_length et LIKE les lisent :
SELECT code, length(code) AS n, octet_length(code) AS octets, code || '|' AS colle,
code LIKE 'C001' AS exact, code LIKE 'C001%' AS debut
FROM client ORDER BY id; code | n | octets | colle | exact | debut
--------+---+--------+-------+-------+-------
C001 | 4 | 6 | C001| | f | t
C002 | 4 | 6 | C002| | f | fUne conversion écrite ::char(n) (CAST(… AS character(n)), ::bpchar(n)) suit les mêmes règles : 'ab'::char(5) s'affiche ab, length('ab'::char(5)) vaut 2 et octet_length('ab'::char(5)) vaut 5. Écarts qui restent avec PostgreSQL :
- la valeur est rangée sans ses espaces finaux et n'est complétée qu'à l'envoi au client (protocole PostgreSQL,
COPY … TO STDOUTcompris) : l'interface embarquée la rend telle quelle, comme elle rend un booléen en1/0; - seule une valeur annoncée
character(n)est complétée : colonne lue directement, aprèsGROUP BYouDISTINCT, au travers d'une vue, d'une table dérivée ou d'une CTE, conversion::char(n). Une valeur calculée qui reste uncharacterdans PostgreSQL (max(code),coalesce(code, 'x'),CASE) est annoncéetextet envoyée sans complément ; concat(code, '|'),format('%s', code),to_json(code),array_agg(code)et les expressions rationnelles (code ~ 'C$') lisent la valeur sans ses espaces de remplissage.
bpchar sans longueur déclare une colonne de longueur libre qui garde le nom de son type : bpchar dans pg_attribute (format_type), character dans information_schema.columns et pour pg_typeof, type bpchar (1042) sans modificateur pour le client, bpchar dans SHOW CREATE TABLE. Ses espaces finaux sont sans signification, comme ceux d'un char(n) : ils sont retirés à l'écriture ('x ' est rangé x, length vaut 1, col || '|' vaut x|). PostgreSQL les garde à l'affichage de la colonne seule. Sans longueur déclarée, la valeur n'est jamais complétée.
Comparaisons : collation « C »#
Les textes sont comparés et triés octet par octet, comme dans un serveur PostgreSQL dont la collation est C (SHOW lc_collate rend C) : la casse et les accents comptent, les majuscules se trient avant les minuscules, et les lettres accentuées après les lettres simples. Seuls les espaces de fin sont ignorés par =.
SELECT 'a' = 'A', 'e' = 'é', 'a ' = 'a';
-- f | f | t
SELECT nom FROM client WHERE nom = 'DUPONT'; -- aucune ligne
SELECT nom FROM client WHERE nom = 'Dupont'; -- Dupont
SELECT x FROM (VALUES ('b'), ('B'), ('a'), ('É'), ('e')) t(x) ORDER BY x;
-- B, a, b, e, É'a' et 'A' sont donc deux valeurs distinctes pour une clé primaire, une contrainte UNIQUE, un GROUP BY ou un DISTINCT. LIKE et ~ respectent la casse et les accents ; ILIKE ignore la casse et les accents (nom ILIKE 'elodie%' trouve Élodie Martin), ~* ignore la casse.
COLLATE "C", COLLATE "POSIX" et COLLATE ucs_basic (qualifiés ou non de pg_catalog.) imposent la comparaison octet par octet dans une expression ou un tri (ORDER BY nom COLLATE "C"). Pour une comparaison qui ignore la casse et les accents, appliquer une collation de MIRAJ, dans l'expression ou sur la colonne :
SELECT nom FROM client WHERE nom = 'DUPONT' COLLATE utf8mb4_general_ci; -- Dupont
CREATE TABLE code_souple (code varchar(10) COLLATE utf8mb4_general_ci UNIQUE);
INSERT INTO code_souple VALUES ('a'), ('A');
-- ERROR 23505: Duplicate entry 'A' for key 'code'Les collations de MIRAJ sont décrites au chapitre 13.
Littéraux de chaînes#
| Écriture | Sens |
|---|---|
'l''été' | chaîne standard : '' écrit une apostrophe, \ est un caractère ordinaire |
E'a\tb' | chaîne à échappements : \b \f \n \r \t, \\, \', octal \101, hexadécimal \x41, Unicode \u00e9 et \U0001F600 |
$$l'été$$, $q$un $$ dedans$q$ | chaîne sans aucun échappement |
SELECT 'l''été' AS a, $$l'été$$ AS c, $q$un $$ dedans$q$ AS d, length('a\nb') AS n;
-- l'été | l'été | un $$ dedans | 4"texte" entre guillemets doubles est un identifiant, jamais une chaîne. Deux chaînes écrites côte à côte sont concaténées ('a' 'b' vaut ab), même sur une seule ligne. Une chaîne vide n'est pas NULL.
33.6 Octets : bytea#
CREATE TABLE piece_jointe (id integer PRIMARY KEY, contenu bytea);
INSERT INTO piece_jointe VALUES (1, '\x48656c6c6f'), (2, 'abc'), (3, decode('AP8Q', 'base64'));
SELECT id, contenu, length(contenu) AS octets, encode(contenu, 'base64') AS b64
FROM piece_jointe ORDER BY id; id | contenu | octets | b64
----+--------------+--------+----------
1 | \x48656c6c6f | 5 | SGVsbG8=
2 | \x616263 | 3 | YWJj
3 | \x00ff10 | 3 | AP8QUne colonne bytea garde une suite d'octets de longueur libre. Elle est toujours rendue au format hexadécimal \x…. À l'écriture, un texte est décodé comme le fait PostgreSQL :
| Texte écrit | Octets gardés |
|---|---|
'\x48656c6c6f' | format hexadécimal (majuscules ou minuscules) ; '\x' est la suite vide |
'abc' | les octets du texte |
E'a\\\\b\\001' | format d'échappement : \\ pour une barre oblique, \001 pour un octet en octal |
Un texte hexadécimal mal formé est refusé : '\xZZ' donne 22P02 (invalid input syntax for type bytea: "\xZZ"). length et octet_length comptent les octets ; encode(b, 'hex' | 'base64' | 'escape') et decode(texte, format) convertissent (voir 37. Fonctions SQL). La conversion '…'::bytea et le littéral typé bytea '\x00' suivent les mêmes règles.
Chaînes de bits : bit(n) et bit varying(n)#
CREATE TABLE drapeaux (id integer PRIMARY KEY, b bit(3), masque bit(8), v bit varying(8));
INSERT INTO drapeaux VALUES (1, B'101', X'0F', B'0101'), (2, '011', 'xF0', '01');
SELECT b, masque, v, b & B'110' AS et, ~masque AS inverse, b || v AS suite, b::int AS n
FROM drapeaux ORDER BY id; b | masque | v | et | inverse | suite | n
-----+----------+------+-----+----------+---------+---
101 | 00001111 | 0101 | 100 | 11110000 | 1010101 | 5
011 | 11110000 | 01 | 010 | 00001111 | 01101 | 3Une chaîne de bits s'écrit B'101' (chiffres binaires) ou X'1F' (chiffres hexadécimaux, quatre positions chacun : 00011111) ; elle est rendue en chiffres binaires, poids fort d'abord, sur toutes ses positions. bit(n) en a exactement n (64 au plus ; bit seul est un bit(1)), bit varying(n) au plus n, bit varying sans limite. Une colonne reçoit une chaîne de bits ou un texte lu de même ('101', 'x1F') :
| Écriture | Réponse |
|---|---|
autre longueur que n dans un bit(n) | 22026 : bit string length 4 does not match type bit(3) |
plus de n positions dans un bit varying(n) | 22001 : bit string too long for type bit varying(8) |
autre chiffre que 0 ou 1 | 22P02 : invalid input syntax for type bit: "102" |
chaîne de bits dans une colonne d'un autre type (bytea, text, integer…) | 42804 : column "contenu" is of type bytea but expression is of type bit ; la convertir (b::text, b::integer) |
entier dans une colonne bit(n) | 42804 : column "b" is of type bit but expression is of type integer ; le convertir (5::bit(3)) |
comparaison d'une chaîne de bits à un entier (b = 5) | 42883 : operator does not exist: bit = integer |
X'DEADBEEF' est donc une chaîne de 32 bits, et non une suite d'octets : pour un bytea, écrire '\xDEADBEEF' ou decode('DEADBEEF', 'hex').
Les opérateurs &, |, #, ~, <<, >> et ||, les fonctions length, get_bit, set_bit, substring… sont décrits en 37. Fonctions SQL. Une chaîne de bits se compare position par position, la plus courte d'abord (B'10' < B'101') ; face à une colonne bit, un texte écrit est lu en chaîne de bits (WHERE b = '101'). max, min, coalesce, nullif, CASE, greatest, une union, une vue et une table dérivée gardent le type ; bit_and, bit_or et bit_xor d'une colonne bit rendent une chaîne de bits, et une colonne bit varying reçoit une colonne bit(n) sans conversion.
| Conversion | Résultat |
|---|---|
x::bit(n) d'une chaîne de bits ou d'un texte | coupée ou complétée de zéros à droite : B'1010'::bit(3) vaut 101, B'10'::bit(4) vaut 1000 |
x::bit varying(n) | coupée à n positions ; sans longueur, inchangée |
entier::bit(n) | les n positions de droite de l'entier : 5::bit(8) vaut 00000101, (-1)::bit(4) vaut 1111 |
bits::integer, bits::bigint | l'entier : B'101'::int vaut 5 ; plus de 32 (64) positions : 22003 |
bits::text | ses chiffres |
Écarts : un bit(n) est limité à 64 positions (0A000 au-delà) ; une chaîne de bits calculée gardée par CREATE TABLE … AS SELECT ou par une vue fait une colonne bit varying (sa longueur n'étant pas connue d'avance).
33.7 Dates et heures#
CREATE TABLE evenement (
id integer PRIMARY KEY,
jour date,
heure time,
saisi_le timestamp,
recu_le timestamptz,
arrondi timestamp(0)
);
SET TIME ZONE 'Europe/Paris';
INSERT INTO evenement VALUES (1, '2026-02-28', '23:59:59.5', '2026-02-28 23:59:59.123456',
'2026-02-28 23:59:59+02', '2026-02-28 23:59:59.7');
SELECT * FROM evenement; id | jour | heure | saisi_le | recu_le | arrondi
----+------------+------------+----------------------------+------------------------+---------------------
1 | 2026-02-28 | 23:59:59.5 | 2026-02-28 23:59:59.123456 | 2026-02-28 22:59:59+01 | 2026-02-28 23:59:59| Type | Contenu | Plage, précision |
|---|---|---|
date | jour du calendrier | 0001-01-01 à 9999-12-31 |
time[(p)] | heure ou durée, sans fuseau | -838:59:59 à 838:59:59 ; p de 0 à 6, 6 par défaut |
timestamp[(p)] | date et heure civiles, sans fuseau | p de 0 à 6, 6 par défaut (microseconde) |
timestamptz[(p)] | instant | rangé en UTC, lu et écrit dans le fuseau de la session |
time without time zone, timestamp without time zone et timestamp with time zone sont les formes longues. Une précision au-delà de 6 est refusée (22023, Too big precision specified for 'a'. Maximum is 6). Avec une précision p, les décimales en trop sont coupées (arrondi ci-dessus garde 23:59:59, '10:00:00.123456'::time(3) vaut 10:00:00.123).
Formats acceptés#
Les dates s'écrivent au format ISO, '2026-02-28' ou '20260228' ; une date-heure admet l'espace ou le T ('2026-02-28T10:00:00'). Les autres styles de PostgreSQL ('28/02/2026', 'February 28, 2026') et les valeurs 'infinity' / '-infinity' ne sont pas reconnus.
| Valeur écrite dans une colonne | Réponse |
|---|---|
'2026-02-30' (jour hors du calendrier), '10000-01-01' | 22008 : Incorrect date value: '2026-02-30' for column … |
'28/02/2026', 'infinity' | 22007 : Incorrect date value: '28/02/2026' for column … |
'839:00:00' dans une colonne time | 22008 : Incorrect time value: '839:00:00' for column … |
Une colonne time est aussi une durée : '25:00:00' et '-01:00:00' y sont admis, alors que PostgreSQL s'arrête à 24:00:00.
timestamp et timestamptz#
Un timestamp garde l'heure telle qu'elle est écrite ; un décalage écrit est retiré sans conversion. Un timestamptz désigne un instant : le décalage ou le nom de fuseau écrit le fixe, et sans décalage l'heure est lue dans le fuseau de la session.
SELECT '2026-10-11 10:15:30+02'::timestamp AS sans_fuseau,
'2026-10-11 10:15:30Z'::timestamptz AS utc,
'2026-10-11 10:15:30 Europe/Paris'::timestamptz AS paris; sans_fuseau | utc | paris
---------------------+------------------------+------------------------
2026-10-11 10:15:30 | 2026-10-11 12:15:30+02 | 2026-10-11 10:15:30+02Une colonne timestamptz est affichée dans le fuseau de la session, avec son décalage ; changer de fuseau ne change pas l'instant gardé, et ne touche pas un timestamp :
SET TIME ZONE 'UTC';
SELECT saisi_le, recu_le FROM evenement; saisi_le | recu_le
----------------------------+------------------------
2026-02-28 23:59:59.123456 | 2026-02-28 21:59:59+00Le fuseau de la session est, à la connexion, celui du serveur (SHOW TIME ZONE) ; SET TIME ZONE le change (voir 38. Transactions et session). Sont reconnus les décalages (+02, -05:30, Z) et les noms de fuseaux précédés d'une espace (… Europe/Paris), pas les abréviations (CEST, PST).
now(), current_timestamp, transaction_timestamp(), statement_timestamp() et clock_timestamp() rendent un timestamptz ; current_date une date, current_time un time (sans fuseau), localtimestamp un timestamp. Dans un bloc de transaction, tous rendent l'heure du début de la transaction, sauf statement_timestamp() (début de l'instruction) et clock_timestamp() (horloge), voir 38.9.
Le type timetz (time with time zone) est accepté et rangé comme un time : le fuseau n'est pas gardé. '10:00:00+02'::timetz vaut 10:00:00, et une colonne time ou timetz reçoit de même un texte portant un décalage, qu'elle retire (INSERT INTO t VALUES ('10:00:00+02') range 10:00:00).
Littéraux typés et calculs#
SELECT DATE '2026-10-11', TIME '10:15:30', TIMESTAMP '2026-10-11 10:15:30',
TIMESTAMPTZ '2026-10-11 10:15:30+02';
SELECT DATE '2026-03-01' - DATE '2026-02-01' AS jours,
DATE '2026-02-28' + 1 AS lendemain,
DATE '2026-02-28' + INTERVAL '1 month' AS plus_un_mois,
TIMESTAMP '2026-02-28 10:00:00' - TIMESTAMP '2026-02-26 08:00:00' AS duree; jours | lendemain | plus_un_mois | duree
-------+------------+---------------------+-----------------
28 | 2026-03-01 | 2026-03-28 00:00:00 | 2 days 02:00:00| Calcul | Résultat |
|---|---|
date - date | nombre de jours (integer) |
date + n, date - n | date |
date + time | timestamp |
date ± INTERVAL '…', timestamp ± INTERVAL '…' | timestamp |
timestamp - timestamp | interval (2 days 02:00:00) |
Intervalles#
Un interval garde à part ses mois, ses jours et sa durée, comme PostgreSQL, et s'écrit comme lui ; il peut être le type d'une colonne (rangée en texte réécrit, '25 hours' devenant 25:00:00 ; un texte illisible : 22007). Le format ISO 8601 est lu aussi ('P1D', 'P1Y2M3DT4H5M6S', 'P0001-02-03T04:05:06').
SELECT INTERVAL '1 day', INTERVAL '2 hours 30 minutes', '45 days'::interval,
INTERVAL '1 year 2 months', INTERVAL '1 day 02:00:00', INTERVAL '1 month 1 day';
-- 1 day | 02:30:00 | 45 days | 1 year 2 mons | 1 day 02:00:00 | 1 mon 1 day
SELECT DATE '2026-01-01' + INTERVAL '3' DAY,
TIMESTAMP '2026-01-31 10:00:00' + INTERVAL '1 month';
-- 2026-01-04 00:00:00 | 2026-02-28 10:00:00Les intervalles s'additionnent, se multiplient ou se divisent par un nombre, se comparent et se trient par durée (voir le chapitre 37, Intervalles). En langage MIRAJ, une colonne interval garde son nom (SHOW CREATE TABLE, DESCRIBE, vidage relu dans l'un ou l'autre langage) ; elle y reste un texte, que seul le langage PostgreSQL contrôle et calcule en intervalle. Une clé primaire ou UNIQUE sur une colonne interval porte sur la durée, dans les deux langages : '24:00:00' y est refusé (23505) si '1 day' existe.
Les fonctions de dates (extract, date_trunc, to_char, age, AT TIME ZONE…) sont décrites au chapitre 37. Fonctions SQL.
33.8 json et jsonb#
CREATE TABLE document (id integer PRIMARY KEY, brut json, doc jsonb);
INSERT INTO document VALUES
(1, '{"a": 1, "b": [1, 2]}', '{"client": {"nom": "Dupont"}, "tags": ["or", "vip"]}');
SELECT doc -> 'client' ->> 'nom' AS nom,
doc #>> '{tags,0}' AS tag,
doc @> '{"tags": ["vip"]}' AS vip,
pg_typeof(doc) AS type_doc, pg_typeof(brut) AS type_brut
FROM document; nom | tag | vip | type_doc | type_brut
--------+-----+-----+----------+-----------
Dupont | or | t | jsonb | jsonLes deux types gardent un document JSON : objet, tableau, ou valeur seule ('"texte"', '12.50', 'null'). Un texte qui n'est pas du JSON est refusé à l'écriture :
INSERT INTO document VALUES (2, '{pas du json}', NULL);
-- ERROR 22P02: invalid input syntax for type json: "{pas du json}"json et jsonb ont le même stockage et les mêmes fonctions. Une colonne jsonb est annoncée jsonb au client, dans le catalogue et par pg_typeof, et doc -> 'clé' sur elle rend du jsonb ; une colonne json reste json. Comme dans PostgreSQL :
- un
jsonrend le document tel qu'il a été écrit (espaces, ordre des clés, clés en double) ; - un
jsonbest normalisé à l'écriture dans la colonne et par::jsonb: 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é (1e2:100). Deux documents qui ne diffèrent que par l'ordre de leurs clés sont égaux par=; - deux
jsonbse comparent par valeur, dans l'ordre propre au type :null, puis les chaînes, les nombres, les booléens, les tableaux et les objets ; les nombres par leur valeur ('1.0'::jsonb = '1'::jsonbest vrai,'2'::jsonb < '10'::jsonbaussi), les tableaux par leur nombre d'éléments puis élément par élément, les objets par leur nombre de membres puis clé et valeur de chaque membre ; un tableau vide au premier niveau précède tout.ORDER BY,DISTINCT,GROUP BY,UNION/INTERSECT/EXCEPT,IN,BETWEEN,count(DISTINCT …)et une clé de jointure suivent cette comparaison. Un document écrit en chaîne face à unjsonb(doc = '1',doc IN ('{"a": 1}')) est lu enjsonb.
SELECT '{"b": 1, "a": 2, "a": 3}'::json AS brut, '{"b": 1, "a": 2, "a": 3}'::jsonb AS doc,
'{"a": 1, "b": 2}'::jsonb = '{"b": 2, "a": 1}'::jsonb AS egaux; brut | doc | egaux
----------------------------+------------------+-------
{"b": 1, "a": 2, "a": 3} | {"a": 3, "b": 1} | tUne valeur par défaut constante d'une colonne jsonb est lue en jsonb (DEFAULT '{"b": 1, "a": 2}' range {"a": 2, "b": 1}) ; elle se relit '…'::jsonb, comme dans PostgreSQL. Dans l'expression d'une colonne générée ou d'une contrainte CHECK, une colonne jsonb garde son type (GENERATED ALWAYS AS (doc || '{"k": 1}') STORED fusionne les documents).
Écarts avec PostgreSQL : un document rangé dans une colonne jsonb par une version antérieure du serveur, ou écrit en langage MIRAJ, garde son texte (ordre et doublons de ses clés) jusqu'à sa prochaine écriture en langage PostgreSQL (UPDATE t SET doc = doc::jsonb le normalise) ; il se compare et se trie déjà par valeur. Une clé primaire et une contrainte UNIQUE sur une colonne jsonb portent sur sa valeur : 1.0 et 1, ou deux objets aux membres dans un autre ordre, y sont des doublons (23505), y compris pour un document rangé par une version antérieure (l'index est reconstruit à l'ouverture) ; la recherche par un index donne le même résultat qu'un filtre doc = …, évalué par valeur. PARTITION BY et ORDER BY dans une fonction de fenêtrage, de greatest, least, nullif et de doc > ALL (SELECT …). min et max d'un jsonb, absents de PostgreSQL, suivent l'ordre du type.
Opérateurs : -> (élément, en document), ->> (élément, en texte ; NULL pour un null JSON), #> et #>> (chemin écrit '{a,0}'), @> et <@ (inclusion), || (fusion), - 'clé' (retrait), ? (la clé ou l'élément existe), ?| (l'une des clés existe) et ?& (toutes existent), ces trois derniers sur un jsonb seulement :
SELECT doc ? 'tags' AS a, doc ?| '{x,tags}' AS b, doc ?& '{x,tags}' AS c, doc -> 'tags' ? 'vip' AS d
FROM document; a | b | c | d
---+---+---+---
t | t | f | tLa liste de clés de ?| et ?& s'écrit en texte de tableau ('{x,tags}') ou en ARRAY['x', 'tags']. Les fonctions (jsonb_build_object, jsonb_set, jsonb_typeof, jsonb_agg…) sont décrites au chapitre 37. Fonctions SQL.
Un index peut porter sur une valeur extraite d'un document : voir 34.6.
33.9 uuid#
CREATE TABLE jeton (id uuid PRIMARY KEY DEFAULT uuid(), libelle text);
INSERT INTO jeton (libelle) VALUES ('auto');
INSERT INTO jeton VALUES ('A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11', 'écrit');
SELECT id, libelle FROM jeton WHERE libelle = 'écrit'; id | libelle
--------------------------------------+---------
a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 | écritUne colonne uuid garde la forme canonique en minuscules, quelle que soit l'écriture reçue : majuscules, accolades ('{a0ee…}') ou trente-deux chiffres sans tirets. Un texte qui n'est pas un UUID est refusé :
INSERT INTO jeton VALUES ('pas-un-uuid', 'x');
-- ERROR 22P02: invalid input syntax for type uuid: "pas-un-uuid"La colonne est annoncée uuid au client. gen_random_uuid() (comme uuid_generate_v4() et uuid()) rend un UUID aléatoire, dans une requête comme en valeur par défaut :
CREATE TABLE jeton2 (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), libelle text);
INSERT INTO jeton2 (libelle) VALUES ('a'), ('b'); -- deux UUID distincts33.10 vector(n)#
CREATE TABLE article_vecteur (article_id integer PRIMARY KEY, e vector(3));
INSERT INTO article_vecteur VALUES (1, '[1,2,3]'), (2, '[0.5, 0, -1]');
SELECT article_id, e, e <-> '[1,2,3]' AS distance FROM article_vecteur ORDER BY distance; article_id | e | distance
------------+------------+----------
1 | [1,2,3] | 0
2 | [0.5,0,-1] | 4.5vector(n) garde un vecteur de n nombres flottants, écrit '[x,y,…]' ; il est annoncé sous le type vector de l'extension pgvector. Un vecteur d'une autre dimension est refusé (22000, expected 3 dimensions, not 2). Les opérateurs de distance sont <->, <#> et <+> ; les index vectoriels sont une extension de MIRAJ (voir 34.6).
33.11 Types de MIRAJ acceptés#
Les types propres au langage MIRAJ restent utilisables dans une définition de colonne. Ils sont annoncés au client sous le type PostgreSQL le plus proche :
| Type MIRAJ | Annoncé | Remarque |
|---|---|---|
tinyint | smallint | −128 à 127 |
mediumint | integer | |
int unsigned | bigint | 0 à 4 294 967 295 ; une valeur négative : 22003 |
double | double precision | |
datetime | timestamp(0) without time zone | à la seconde sans précision écrite |
tinytext, mediumtext, longtext | text | |
blob, varbinary(n) | bytea | |
year | smallint | |
enum('a', 'b', …) | type <table>_<colonne>_enum | type énuméré de pg_type (genre e), un par colonne |
set('a', 'b', …) | text | |
point, geometry et les autres types spatiaux | bytea |
CREATE TABLE commande_etat (
id integer PRIMARY KEY,
etat enum('nouvelle', 'expediee', 'annulee') NOT NULL DEFAULT 'nouvelle'
);
INSERT INTO commande_etat (id) VALUES (1);
INSERT INTO commande_etat VALUES (2, 'perdue');
-- ERROR 22P02: invalid input value for enum commande_etat_etat_enum: "perdue"Une colonne enum de MIRAJ tient lieu de type énuméré : CREATE TYPE … AS ENUM n'existe pas (voir 33.17). Elle est annoncée au client sous l'OID de son type énuméré, celui que donnent pg_attribute.atttypid et pg_type ; ce type suit la valeur au travers d'une vue, d'une table dérivée, de CASE, de coalesce et de max / min, et pg_typeof le nomme (commande_etat_etat_enum). Ces types sont décrits dans le chapitre 6.
33.12 Conversions#
Trois écritures convertissent une valeur :
SELECT '42'::integer AS a, CAST('3.14' AS numeric(5,2)) AS b, 2.5::integer AS c, 42::text AS d,
'2026-10-11'::date AS e, 'yes'::boolean AS f, integer '7' AS g, date '2026-10-11' AS h; a | b | c | d | e | f | g | h
----+------+---+----+------------+---+---+------------
42 | 3.14 | 3 | 42 | 2026-10-11 | t | 7 | 2026-10-11| Écriture | Exemple |
|---|---|
expression::type | '42'::integer, total::numeric(12,2) |
CAST(expression AS type) | CAST('3.14' AS numeric(5,2)) |
littéral typé type 'texte' | integer '7', date '2026-10-11', timestamptz '…', bytea '\x00', boolean 't', bit '0101' |
:: lie plus fort que le signe moins : -1.5::integer se lit -(1.5::integer) et vaut -2 ; écrire (-1.5)::integer pour convertir le nombre négatif.
Types de conversion#
| Type | Résultat |
|---|---|
int2, smallint, int4, int, integer, int8, bigint | entier ; hors plage : 22003 (smallint out of range, integer out of range) |
numeric(p,s), decimal(p,s), numeric | décimal ; numeric seul : dix-huit décimales au plus, sans zéros finals |
float4, real, float8, double precision | flottant |
bool, boolean | booléen ; textes de 33.4 |
text, varchar[(n)], char[(n)], name | texte ; avec une longueur, tronqué avec un avis |
date, time[(p)], timetz, timestamp[(p)], timestamptz | date et heure |
bytea | octets, texte décodé comme en 33.6 |
bit(n), bit varying(n) (varbit(n)) | chaîne de bits (voir 33.6) |
json, jsonb | document |
uuid | identifiant |
interval | intervalle |
oid, regclass, regtype, regnamespace, regrole | numéros et noms du catalogue (voir 41. Catalogue et outils) |
vector(n) | vecteur |
Les noms de conversion de MIRAJ restent acceptés (CAST(x AS SIGNED), CAST(x AS DATETIME)), et le préfixe pg_catalog. est ignoré.
Texte vers nombre#
La conversion est stricte, comme dans PostgreSQL : le texte entier doit être un nombre, espaces autour admis.
SELECT ' 42 '::int, '0x1F'::int, '0o17'::int, '0b101'::int, '1_000'::int;
-- 42 | 31 | 15 | 5 | 1000
-- Les mêmes écritures sans guillemets sont des entiers ; le souligné sépare aussi les chiffres
-- d'un décimal ou d'un exposant
SELECT 0x1F, 0o17, 0b101, 1_000, 0X_ff, 1_000.5, 1e1_0;
-- 31 | 15 | 5 | 1000 | 255 | 1000.5 | 10000000000
SELECT 1__0; -- ERROR 42601 (souligné doublé ; de même 1_, 0x, 0b2)
SELECT 'abc'::int; -- ERROR 22P02: invalid input syntax for type integer: "abc"
SELECT '1.5'::int; -- ERROR 22P02: invalid input syntax for type integer: "1.5"
SELECT 'abc'::numeric; -- ERROR 22P02: invalid input syntax for type numeric: "abc"
SELECT 99999::int2; -- ERROR 22003: smallint out of rangeLes autres types contrôlent de même le texte converti : '2026-02-30'::date (22008, date/time field value out of range), 'abc'::date et CAST('abc' AS timestamp) (22007, invalid input syntax for type …), '{bad'::jsonb et 'zz'::uuid (22P02).
Nombre vers entier#
Un décimal ou un flottant converti en entier est arrondi au plus proche, le demi s'éloignant de zéro : 32.5::int vaut 3, 3.5::int vaut 4, (-2.5)::int vaut −3. PostgreSQL arrondit de même un numeric, mais arrondit un double precision au pair le plus proche (32.5::float8::int y vaut 2 ; ici 3).
Conversions implicites#
Elles sont plus larges que dans PostgreSQL : un texte se compare à un nombre et s'additionne, un nombre se concatène.
SELECT 1 + '1' AS a, '1' || 1 AS b, 1 = '1' AS c, 1 + 1.5 AS d,
pg_typeof(1 + 1.5) AS td, pg_typeof(1 + 1.5::float8) AS te; a | b | c | d | td | te
---+----+---+-----+---------+------------------
2 | 11 | t | 2.5 | numeric | double precisionUn entier et un numeric donnent un numeric ; dès qu'un opérande est un flottant, le résultat est un double precision. 1 + '1' est calculé en double precision.
33.13 NULL#
NULL représente l'absence de valeur, dans tous les types. Une comparaison avec NULL rend NULL ; on le teste par IS NULL, IS NOT NULL ou IS [NOT] DISTINCT FROM.
SELECT NULL = NULL AS a, NULL IS NULL AS b, 1 IS DISTINCT FROM NULL AS c,
coalesce(NULL, 'x') AS d, nullif(1, 1) AS e, NULL || 'a' AS f, '' IS NULL AS g; a | b | c | d | e | f | g
------+---+---+---+------+------+---
NULL | t | t | x | NULL | NULL | f||rend NULL dès qu'un opérande est NULL.- Les agrégats ignorent les NULL :
count(colonne)ne les compte pas,sumd'aucune valeur rend NULL. - Une contrainte
UNIQUEadmet plusieurs NULL ; une contrainteCHECKdont la condition est NULL est satisfaite. - Tri : sans précision, les NULL viennent en dernier en ordre croissant et en premier en ordre décroissant, comme dans PostgreSQL ;
NULLS FIRSTetNULLS LASTle changent (voir 36. Requêtes SELECT).
Écrire NULL dans une colonne NOT NULL, ou l'omettre sans valeur par défaut, est une erreur 23502 :
ERROR 23502: Column 'a' cannot be null
ERROR 23502: Field 'a' doesn't have a default value33.14 DEFAULT#
La valeur par défaut d'une colonne s'applique quand un INSERT ne la cite pas ou écrit DEFAULT à sa place. Sans clause DEFAULT, c'est NULL.
CREATE TABLE article (
id serial PRIMARY KEY,
ref varchar(20) NOT NULL UNIQUE,
libelle text NOT NULL,
prix_ht numeric(10,2) NOT NULL DEFAULT 0,
tva numeric(4,2) NOT NULL DEFAULT 20.00,
prix_ttc numeric(12,2) GENERATED ALWAYS AS (prix_ht * (1 + tva / 100)) STORED,
actif boolean NOT NULL DEFAULT true,
cree_le timestamptz NOT NULL DEFAULT now(),
CHECK (prix_ht >= 0)
);
INSERT INTO article (ref, libelle, prix_ht) VALUES ('STY-01', 'Stylo', 1.50), ('CAH-01', 'Cahier', 3.20);
INSERT INTO article (ref, libelle, prix_ht, tva) VALUES ('LIV-01', 'Livre', 12.00, DEFAULT);
SELECT id, ref, prix_ht, tva, prix_ttc, actif FROM article ORDER BY id; id | ref | prix_ht | tva | prix_ttc | actif
----+--------+---------+-------+----------+-------
1 | STY-01 | 1.50 | 20.00 | 1.80 | t
2 | CAH-01 | 3.20 | 20.00 | 3.84 | t
3 | LIV-01 | 12.00 | 20.00 | 14.40 | tFormes vérifiées :
| Forme | Exemple |
|---|---|
| constante | DEFAULT 7, DEFAULT 'x', DEFAULT true, DEFAULT NULL, DEFAULT '{}' (colonne jsonb), DEFAULT 'yes' (colonne boolean) |
| littéral typé ou converti | DEFAULT DATE '2026-01-01', DEFAULT 'x'::character varying |
| date et heure courantes | DEFAULT now(), DEFAULT CURRENT_TIMESTAMP, DEFAULT CURRENT_DATE |
| expression | DEFAULT 'a' || 'b', DEFAULT 1.5 * 2, DEFAULT upper('x'), DEFAULT length('abc') |
| identifiant | DEFAULT gen_random_uuid(), DEFAULT uuid() |
| fonction volatile | DEFAULT random() |
| séquence | DEFAULT nextval('nom') (voir 34.8) |
Une expression peut citer une autre colonne de la ligne (b integer DEFAULT a + 1 : une ligne insérée avec a = 5 reçoit b = 6), ce que PostgreSQL n'admet pas ; pour une valeur qui doit suivre les modifications de la ligne, employer une colonne générée (33.16). Une fonction volatile est calculée pour chaque ligne : DEFAULT random(), DEFAULT gen_random_uuid().
Une chaîne seule est lue dans le type de la colonne, comme PostgreSQL lit une constante sans type : boolean DEFAULT 'yes' vaut DEFAULT true (22P02 pour un texte qui n'est pas un booléen), la constante d'une colonne jsonb est normalisée (elle se relit '…'::jsonb) et celle d'une colonne interval est réécrite (DEFAULT '25 hours' range 25:00:00), celle d'une colonne uuid est mise en minuscules, celle d'une colonne bytea décodée ('\x0aff'). Un décalage horaire écrit est ignoré pour une colonne timestamp, time ou timetz (timetz DEFAULT '10:15:00+02' range 10:15:00, une colonne timetz étant rangée en time). Une chaîne convertie dans le type de la colonne ('…'::uuid, ('…'::timestamptz), '…'::date, '…'::text, entre parenthèses ou non) est lue de même et reste une constante. Il en va de même pour ALTER TABLE … ALTER COLUMN … SET DEFAULT.
La constante d'une colonne timestamptz désigne un instant, fixé à la création comme dans PostgreSQL : celui du fuseau écrit ('2026-10-08 10:15:00+02', '… Europe/Paris', '…Z'), ou, sans fuseau écrit, celui du TimeZone de la session qui crée la colonne (CREATE TABLE, ADD COLUMN, SET DEFAULT). Une ligne écrite plus tard reçoit le même instant, quel que soit le fuseau de la session qui l'écrit ; pg_get_expr, column_default et SHOW CREATE TABLE l'affichent à l'heure de la session qui lit ('2026-10-08 10:15:00+02'::timestamp with time zone à Paris, '2026-10-08 08:15:00+00'::… à UTC). Les décimales au-delà de la précision de la colonne sont coupées, comme pour une valeur écrite (PostgreSQL arrondit). En langage MIRAJ, ce défaut se lit DEFAULT '2026-10-08 08:15:00+00' (l'instant en UTC). '…'::timestamptz dans une expression (DEFAULT ('…+02'::timestamptz + interval '1 day'), CHECK) garde son fuseau, et s'évalue à chaque ligne.
SET TimeZone = 'Europe/Paris';
CREATE TABLE rdv (id int PRIMARY KEY, debut timestamptz DEFAULT '2026-10-08 10:15:00');
SET TimeZone = 'UTC';
INSERT INTO rdv (id) VALUES (1);
SELECT debut, pg_get_expr(adbin, adrelid) FROM rdv, pg_attrdef WHERE adrelid = 'rdv'::regclass; debut | pg_get_expr
------------------------+---------------------------------------------------
2026-10-08 08:15:00+00 | '2026-10-08 08:15:00+00'::timestamp with time zoneLes valeurs par défaut se relisent dans information_schema.columns.column_default, par pg_get_expr (\d) et par SHOW CREATE TABLE, qui les rendent en langage PostgreSQL : constante convertie au type de la colonne ('x'::text, '2026-10-08'::date, '1 day'::interval, '{"a": 2}'::jsonb, 7, true). INSERT INTO t DEFAULT VALUES pose toutes les valeurs par défaut ; il échoue (23502) si une colonne NOT NULL n'en a pas.
33.15 Colonnes d'identité : serial et GENERATED … AS IDENTITY#
| Écriture | Colonne |
|---|---|
smallserial (serial2) | smallint, numérotée automatiquement, NOT NULL |
serial (serial4) | integer, numérotée automatiquement, NOT NULL |
bigserial (serial8) | bigint, numérotée automatiquement, NOT NULL |
type GENERATED BY DEFAULT AS IDENTITY [(START [WITH] n)] | idem, sur le type entier écrit |
type GENERATED ALWAYS AS IDENTITY [(START [WITH] n)] | idem ; une valeur écrite y est refusée (428C9) |
La colonne reçoit 1, 2, 3… (ou n, n + 1… avec START WITH n) quand l'INSERT ne la cite pas ou écrit DEFAULT. La valeur attribuée se récupère par INSERT … RETURNING id (voir 35. Langage DML).
INSERT INTO article (id, ref, libelle) VALUES (10, 'GOM-01', 'Gomme');
INSERT INTO article (ref, libelle) VALUES ('REG-01', 'Règle');
SELECT id, ref FROM article ORDER BY id; id | ref
----+--------
1 | STY-01
2 | CAH-01
3 | LIV-01
4 | REG-01
10 | GOM-01À connaître :
- Une valeur écrite n'avance pas le compteur, comme une séquence de PostgreSQL : après l'
INSERTde l'identifiant 10, la ligne suivante reçoit 4. Les numéros générés finissent par rencontrer la valeur écrite, ce qui rend alors23505; comme avecnextval, le numéro est consommé par cet échec et l'insertion suivante reçoit le numéro d'après. - Catalogue : pour une colonne
serial,information_schema.columnsrendcolumn_default=nextval('article_id_seq'::regclass),is_identity=NO; pour une colonneGENERATED … AS IDENTITY,column_defaultest NULL,is_identity=YESetidentity_generationvautBY DEFAULTouALWAYS.pg_attribute.attidentityvautd(serial,BY DEFAULT) oua(ALWAYS). SHOW CREATE TABLEécrit le numéro suivant du compteur :integer GENERATED BY DEFAULT AS IDENTITY (START WITH 5) NOT NULLpour une table dont la prochaine ligne recevra 5 ; un vidage rechargé reprend donc la numérotation.GENERATED ALWAYSrefuse une valeur écrite, comme dans PostgreSQL :INSERTetUPDATEn'y admettent queDEFAULT(428C9), saufINSERT … OVERRIDING SYSTEM VALUEetCOPY(voir 35.1) :ERROR 428C9: cannot insert a non-DEFAULT value into column "id" DÉTAIL : Column "id" is an identity column defined as GENERATED ALWAYS. ASTUCE : Use OVERRIDING SYSTEM VALUE to override.Une colonne
serialouBY DEFAULTaccepte une valeur écrite.- Options :
START WITH nfixe le premier numéro, et la numérotation avance de 1 en 1 (GENERATED BY DEFAULT AS IDENTITY (START WITH 100 INCREMENT BY 10)numérote 100, 101, 102…). - Une seule colonne d'identité par table, et elle doit porter une clé : si elle n'est pas à elle seule la clé primaire, elle reçoit d'office une contrainte d'unicité. Deux colonnes
serialdans une table sont refusées (42000,Incorrect table definition; there can be only one auto column and it must be defined as a key). TRUNCATEgarde le compteur ;TRUNCATE … RESTART IDENTITYle remet à son départ (voir 34.5).
Pour une numérotation réglable (départ, pas, cycle) ou partagée entre tables, créer une séquence (voir 34.8).
33.16 Colonnes générées#
colonne type GENERATED ALWAYS AS (expression) STOREDLa valeur est calculée à chaque écriture de la ligne à partir des autres colonnes, et gardée avec elle. Dans la table article ci-dessus, prix_ttc suit prix_ht et tva :
UPDATE article SET prix_ht = 2.00 WHERE ref = 'STY-01';
SELECT ref, prix_ht, prix_ttc FROM article WHERE ref = 'STY-01';
-- STY-01 | 2.00 | 2.40- L'expression cite les colonnes de la ligne, des constantes et des fonctions déterministes ; elle ne peut citer ni la colonne d'identité (
Generated column 'x' cannot refer to auto-increment column.), ninow()ou une autre fonction dont le résultat change (Expression of generated column 'x' contains a disallowed function.). Ces deux refus portent le SQLSTATEXX000. - Une colonne générée peut être indexée et porter une contrainte
UNIQUEouCHECK. - Une valeur écrite dans une colonne générée est refusée, comme dans PostgreSQL ; seul
DEFAULTy est admis :INSERT INTO article (ref, libelle, prix_ttc) VALUES ('Y', 'Forcé', 99); -- ERROR 428C9: cannot insert a non-DEFAULT value into column "prix_ttc" -- DÉTAIL : Column "prix_ttc" is a generated column. UPDATE article SET prix_ttc = 1; -- ERROR 428C9: column "prix_ttc" can only be updated to DEFAULT - Les formes de MIRAJ
GENERATED ALWAYS AS (expr) VIRTUALetGENERATED ALWAYS AS (expr)(colonne calculée à la lecture, non gardée) sont aussi acceptées. - L'expression garde les règles du langage PostgreSQL (fonctions,
||, comptage en caractères) même si l'instance est plus tard rouverte en langage MIRAJ.
pg_attribute.attgenerated vaut s pour une colonne STORED, et reste vide pour une colonne calculée à la lecture.
33.17 Non pris en charge et écarts#
Types absents#
| Forme | Réponse |
|---|---|
colonne de type tableau : integer[], text ARRAY | 0A000 : an array type is not supported yet in the PostgreSQL language |
CREATE TYPE … AS ENUM, CREATE DOMAIN | 42601 : syntax error at or near "TYPE" / "DOMAIN" ; employer une colonne enum(…), une contrainte CHECK |
money, inet, cidr, macaddr, xml, tsvector, int4range, hstore, colonne oid | 42601 : syntax error at or near "money"… |
chaîne Unicode U&'…' | 42601 ou 42703 |
littéral typé TIMESTAMP WITH TIME ZONE '…' | 42601 : syntax error at or near "WITH" ; écrire TIMESTAMPTZ '…' (la forme longue est admise pour une colonne) |
Les valeurs de tableau d'une expression sont décrites en 33.18.
Écarts de comportement#
| Sujet | MIRAJ | PostgreSQL |
|---|---|---|
numeric sans précision | vingt chiffres entiers et dix-huit décimales au plus | chiffres écrits gardés |
numeric : précision, échelle | 65 et 30 au plus | 1000 |
'NaN', 'Infinity' dans une colonne flottante | refusés (22007) | acceptés |
char(n) | complété à n à l'envoi au client ; max, coalesce, concat, ~ le lisent sans complément | complété à n partout |
time | de -838:59:59 à 838:59:59 | de 00:00:00 à 24:00:00 |
timetz | rangé comme time, sans fuseau | fuseau gardé |
'infinity' (dates), abréviations de fuseaux | non reconnus | reconnus |
clé primaire, UNIQUE et index sur un jsonb | par valeur (1.0 et 1 en doublon) | par valeur |
bpchar sans longueur | espaces finaux retirés à l'écriture | gardés à l'affichage, sans effet ailleurs |
bit(n) | 64 positions au plus | 83 886 080 |
X'1F' | chaîne de bits 00011111, refusée par une colonne bytea (42804) ; en langage MIRAJ : une suite d'octets | de même |
| flottant converti en entier | demi éloigné de zéro | demi au pair |
| conversions implicites | 1 + '1', '1' || 1, 1 = '1' admis | en partie refusées |
serial, identité | compteur propre à la table ; START WITH seul retenu | séquence t_c_seq |
identifiants "Nom" | insensibles à la casse | sensibles |
Points à surveiller#
Les comportements suivants ont été constatés ; ils s'écartent de PostgreSQL sans rendre d'erreur.
- Valeur hors d'une énumération : SQLSTATE
22P02(invalid input value for enum …), comme PostgreSQL ; la ligne n'est pas écrite. Le numéro d'erreur de MIRAJ reste 1265. - Ordre d'une énumération :
max(etat),min(etat)et les comparaisons</>suivent le texte de la valeur, et non le rang de sa déclaration. Le tri (ORDER BY etat) suit, lui, le rang, pour la colonne lue directement comme au travers d'une vue ou d'une table dérivée. - Union de chaînes de types différents : un
varchar(n)uni à unchar(n)est annoncétext(PostgreSQL annoncecharacter varyingoucharacter). Les valeurs d'une union dechar(n)de longueurs différentes (annoncéecharactersans modificateur) ne sont pas complétées d'espaces.CREATE TABLE … ASsur une union devarchar(n)sans modificateur commun crée une colonnetext(PostgreSQL :character varyingsans longueur).
33.18 Tableaux#
Un tableau se construit dans une expression par ARRAY[…] ou par un texte de tableau converti ('{…}'::type[]). Il est rendu au client sous la forme texte de PostgreSQL ({1,2,3}, éléments cités au besoin, NULL pour un élément absent) et annoncé sous le type tableau de ses éléments (int4[], text[], numeric[]…). Le type des éléments est choisi comme dans PostgreSQL : le plus large des nombres, un texte sans type lu dans le type des autres éléments.
SELECT ARRAY[1,2,3] AS a, '{1,2}'::int[] AS b, ARRAY['a', 'b c'] AS c, (ARRAY[10,20,30])[2] AS d,
pg_typeof(ARRAY[1,2]) AS ta, pg_typeof(ARRAY[1.5, 2]) AS tb; a | b | c | d | ta | tb
---------+-------+-----------+----+-----------+-----------
{1,2,3} | {1,2} | {a,"b c"} | 20 | integer[] | numeric[]| Écriture | Résultat |
|---|---|
ARRAY[1, NULL], ARRAY[true, false], ARRAY[]::int[] | {1,NULL}, {t,f}, {} |
tableau[n] | l'élément n (à partir de 1) ; NULL hors des bornes |
x = ANY (tableau), x <> ALL (tableau) | appartenance, comme IN / NOT IN |
unnest(tableau) dans un FROM ou une liste de SELECT | une ligne par élément |
array_agg(x [ORDER BY …]) | tableau des valeurs d'un groupe |
ARRAY[1, 'x'] | 22P02 : invalid input syntax for type integer: "x" |
SELECT 2 = ANY (ARRAY[1,2,3]) AS a, 5 <> ALL ('{1,2}'::int[]) AS b;
-- t | t
SELECT x FROM unnest(ARRAY[1,2]) AS x;
-- 1, 2Les fonctions de tableaux (array_length, cardinality, array_to_string…) sont décrites au chapitre 37. Fonctions SQL.
Voir aussi#
- 34. Langage DDL : tables, contraintes, index, séquences.
- 35. Langage DML : écriture des valeurs,
RETURNING,COPY. - 37. Fonctions SQL : fonctions de chaînes, de nombres, de dates et de JSON.
- 38. Transactions et session : fuseau de la session.
- 41. Catalogue et outils :
pg_type,pg_attribute, types annoncés aux pilotes. - Chapitre 30.