Mirajv1.0
FR

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#

FamilleTypes acceptésAnnoncé au client
Entierssmallint (int2), integer (int, int4), bigint (int8)int2, int4, int8
Identitésmallserial, serial, bigserial, GENERATED … AS IDENTITYl'entier correspondant
Nombres exactsnumeric(p,s), decimal(p,s), numericnumeric
Flottantsreal (float4), double precision (float8, float)float4, float8
Booléensboolean (bool)bool
Chaîneschar(n) (character(n)), varchar(n) (character varying(n)), text, varchar, namebpchar, varchar, text
Octetsbyteabytea
Chaînes de bitsbit(n), bit, bit varying(n) (varbit(n)), bit varyingbit, varbit
Dates et heuresdate, time[(p)], timestamp[(p)], timestamptz[(p)] (timestamp with time zone)date, time, timestamp, timestamptz
Documentsjson, jsonbjson, jsonb
Identifiantsuuiduuid
Vecteursvector(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   | numeric

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

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

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

ÉcritType gardé (format_type)
int2, smallintsmallint
int, int4, integerinteger
int8, bigintbigint
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, decimalnumeric
bool, booleanboolean
char(n), character(n)character(n)
char, character, "char"character(1)
bpchar(n)character(n)
bpchar sans longueurbpchar
varchar(n), character varying(n)character varying(n)
varchar, texttext
namecharacter varying(63)
time, time without time zone, timetztime without time zone
time(p)time(p) without time zone
timestamp, timestamp without time zonetimestamp without time zone
timestamp(p)timestamp(p) without time zone
timestamptz, timestamp with time zonetimestamp 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);
TypeOctetsPlage
smallint2−32 768 à 32 767
integer4−2 147 483 648 à 2 147 483 647
bigint8−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 1

Litté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;
-- 2147483648

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

Le 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.123456789012345

numeric(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ètreLimiteErreur si dépassée
précision p6522023 : Too big precision specified for 'n'. Maximum is 65
échelle s3022023 : Too big scale 31 specified for 'n'. Maximum is 30
s ≤ pobligatoire22023 : 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).

TypeSynonymesGrandeur maximale
realfloat4, float(n) pour n ≤ 24environ 3,4 × 10³⁸
double precisionfloat8, float, float(n) pour n ≥ 25environ 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 1

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

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

ÉcritureValeur
TRUE, 't', 'true', 'yes', 'on', '1', 1vrai
FALSE, 'f', 'false', 'no', 'off', '0', 0faux

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

Un 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) + 1 vaut 2 (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);
TypeContenu
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 longueurlongueur libre
bpchar sans longueurlongueur libre ; espaces finaux sans signification
namevarchar(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 1

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

Une 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 STDOUT compris) : l'interface embarquée la rend telle quelle, comme elle rend un booléen en 1 / 0 ;
  • seule une valeur annoncée character(n) est complétée : colonne lue directement, après GROUP BY ou DISTINCT, au travers d'une vue, d'une table dérivée ou d'une CTE, conversion ::char(n). Une valeur calculée qui reste un character dans PostgreSQL (max(code), coalesce(code, 'x'), CASE) est annoncée text et 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#

ÉcritureSens
'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      | AP8Q

Une 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 écritOctets 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   | 3

Une 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') :

ÉcritureRé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 122P02 : 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.

ConversionRésultat
x::bit(n) d'une chaîne de bits ou d'un textecoupé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::bigintl'entier : B'101'::int vaut 5 ; plus de 32 (64) positions : 22003
bits::textses 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
TypeContenuPlage, précision
datejour du calendrier0001-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 fuseaup de 0 à 6, 6 par défaut (microseconde)
timestamptz[(p)]instantrangé 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 colonneRé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 time22008 : 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+02

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

Le 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
CalculRésultat
date - datenombre de jours (integer)
date + n, date - ndate
date + timetimestamp
date ± INTERVAL '…', timestamp ± INTERVAL '…'timestamp
timestamp - timestampinterval (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:00

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

Les 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 json rend le document tel qu'il a été écrit (espaces, ordre des clés, clés en double) ;
  • un jsonb est 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 jsonb se 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'::jsonb est vrai, '2'::jsonb < '10'::jsonb aussi), 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 à un jsonb (doc = '1', doc IN ('{"a": 1}')) est lu en jsonb.
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} | t

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

La 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 | écrit

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

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

vector(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 MIRAJAnnoncéRemarque
tinyintsmallint−128 à 127
mediumintinteger
int unsignedbigint0 à 4 294 967 295 ; une valeur négative : 22003
doubledouble precision
datetimetimestamp(0) without time zoneà la seconde sans précision écrite
tinytext, mediumtext, longtexttext
blob, varbinary(n)bytea
yearsmallint
enum('a', 'b', …)type <table>_<colonne>_enumtype énuméré de pg_type (genre e), un par colonne
set('a', 'b', …)text
point, geometry et les autres types spatiauxbytea
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
ÉcritureExemple
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#

TypeRésultat
int2, smallint, int4, int, integer, int8, bigintentier ; hors plage : 22003 (smallint out of range, integer out of range)
numeric(p,s), decimal(p,s), numericdécimal ; numeric seul : dix-huit décimales au plus, sans zéros finals
float4, real, float8, double precisionflottant
bool, booleanbooléen ; textes de 33.4
text, varchar[(n)], char[(n)], nametexte ; avec une longueur, tronqué avec un avis
date, time[(p)], timetz, timestamp[(p)], timestamptzdate et heure
byteaoctets, texte décodé comme en 33.6
bit(n), bit varying(n) (varbit(n))chaîne de bits (voir 33.6)
json, jsonbdocument
uuididentifiant
intervalintervalle
oid, regclass, regtype, regnamespace, regrolenumé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 range

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

Un 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, sum d'aucune valeur rend NULL.
  • Une contrainte UNIQUE admet plusieurs NULL ; une contrainte CHECK dont 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 FIRST et NULLS LAST le 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 value

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

Formes vérifiées :

FormeExemple
constanteDEFAULT 7, DEFAULT 'x', DEFAULT true, DEFAULT NULL, DEFAULT '{}' (colonne jsonb), DEFAULT 'yes' (colonne boolean)
littéral typé ou convertiDEFAULT DATE '2026-01-01', DEFAULT 'x'::character varying
date et heure courantesDEFAULT now(), DEFAULT CURRENT_TIMESTAMP, DEFAULT CURRENT_DATE
expressionDEFAULT 'a' || 'b', DEFAULT 1.5 * 2, DEFAULT upper('x'), DEFAULT length('abc')
identifiantDEFAULT gen_random_uuid(), DEFAULT uuid()
fonction volatileDEFAULT random()
séquenceDEFAULT 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 zone

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

ÉcritureColonne
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'INSERT de 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 alors 23505 ; comme avec nextval, 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.columns rend column_default = nextval('article_id_seq'::regclass), is_identity = NO ; pour une colonne GENERATED … AS IDENTITY, column_default est NULL, is_identity = YES et identity_generation vaut BY DEFAULT ou ALWAYS. pg_attribute.attidentity vaut d (serial, BY DEFAULT) ou a (ALWAYS).
  • SHOW CREATE TABLE écrit le numéro suivant du compteur : integer GENERATED BY DEFAULT AS IDENTITY (START WITH 5) NOT NULL pour une table dont la prochaine ligne recevra 5 ; un vidage rechargé reprend donc la numérotation.
  • GENERATED ALWAYS refuse une valeur écrite, comme dans PostgreSQL : INSERT et UPDATE n'y admettent que DEFAULT (428C9), sauf INSERT … OVERRIDING SYSTEM VALUE et COPY (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 serial ou BY DEFAULT accepte une valeur écrite.

  • Options : START WITH n fixe 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 serial dans une table sont refusées (42000, Incorrect table definition; there can be only one auto column and it must be defined as a key).
  • TRUNCATE garde le compteur ; TRUNCATE … RESTART IDENTITY le 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) STORED

La 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.), ni now() ou une autre fonction dont le résultat change (Expression of generated column 'x' contains a disallowed function.). Ces deux refus portent le SQLSTATE XX000.
  • Une colonne générée peut être indexée et porter une contrainte UNIQUE ou CHECK.
  • Une valeur écrite dans une colonne générée est refusée, comme dans PostgreSQL ; seul DEFAULT y 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) VIRTUAL et GENERATED 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#

FormeRéponse
colonne de type tableau : integer[], text ARRAY0A000 : an array type is not supported yet in the PostgreSQL language
CREATE TYPE … AS ENUM, CREATE DOMAIN42601 : syntax error at or near "TYPE" / "DOMAIN" ; employer une colonne enum(…), une contrainte CHECK
money, inet, cidr, macaddr, xml, tsvector, int4range, hstore, colonne oid42601 : 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#

SujetMIRAJPostgreSQL
numeric sans précisionvingt chiffres entiers et dix-huit décimales au pluschiffres écrits gardés
numeric : précision, échelle65 et 30 au plus1000
'NaN', 'Infinity' dans une colonne flottanterefusés (22007)acceptés
char(n)complété à n à l'envoi au client ; max, coalesce, concat, ~ le lisent sans complémentcomplété à n partout
timede -838:59:59 à 838:59:59de 00:00:00 à 24:00:00
timetzrangé comme time, sans fuseaufuseau gardé
'infinity' (dates), abréviations de fuseauxnon reconnusreconnus
clé primaire, UNIQUE et index sur un jsonbpar valeur (1.0 et 1 en doublon)par valeur
bpchar sans longueurespaces finaux retirés à l'écrituregardés à l'affichage, sans effet ailleurs
bit(n)64 positions au plus83 886 080
X'1F'chaîne de bits 00011111, refusée par une colonne bytea (42804) ; en langage MIRAJ : une suite d'octetsde même
flottant converti en entierdemi éloigné de zérodemi au pair
conversions implicites1 + '1', '1' || 1, 1 = '1' admisen partie refusées
serial, identitécompteur propre à la table ; START WITH seul retenuséquence t_c_seq
identifiants "Nom"insensibles à la cassesensibles

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 à un char(n) est annoncé text (PostgreSQL annonce character varying ou character). Les valeurs d'une union de char(n) de longueurs différentes (annoncée character sans modificateur) ne sont pas complétées d'espaces. CREATE TABLE … AS sur une union de varchar(n) sans modificateur commun crée une colonne text (PostgreSQL : character varying sans 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[]
ÉcritureRé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 SELECTune 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, 2

Les fonctions de tableaux (array_length, cardinality, array_to_string…) sont décrites au chapitre 37. Fonctions SQL.

Voir aussi#