PostgreSQL : types, JSONB et écritures avancées

Première leçon du cours PostgreSQL en pratique (index, performances), en accès libre : environ 18 minutes, 4 exercices et 3 questions de quiz. C'est la leçon de base du catalogue ; dans l'espace étudiant, elle est réécrite pour ton profil.

Objectifs

  • Choisir les types PostgreSQL adaptés (identity, numeric, text, timestamptz, uuid, jsonb)
  • Interroger une colonne JSONB avec ->, ->> et @>
  • Écrire un upsert avec INSERT ... ON CONFLICT et récupérer les lignes avec RETURNING
  • Se repérer dans psql

Types PostgreSQLJSONBINSERT ... ON CONFLICTRETURNING

PostgreSQL est le SGBD relationnel open source le plus utilisé pour les nouvelles applications web et les plateformes data. Il respecte bien le standard SQL, offre des types riches et un optimiseur performant. Ce cours se base sur PostgreSQL 18 (sorti fin 2025) ; tout ce qui est présenté fonctionne aussi sur les versions récentes antérieures, sauf mention contraire.

Des types précis

CREATE TABLE commandes (
    id          bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    reference   uuid NOT NULL DEFAULT gen_random_uuid() UNIQUE,
    client_id   bigint NOT NULL REFERENCES clients(id),
    montant     numeric(10,2) NOT NULL CHECK (montant >= 0),
    statut      text NOT NULL DEFAULT 'en_attente',
    options     jsonb NOT NULL DEFAULT '{}',
    cree_le     timestamptz NOT NULL DEFAULT now()
);
  • GENERATED ALWAYS AS IDENTITY est la forme standard d’auto-incrément ; serial reste accepté mais n’est plus recommandé.
  • uuid donne des identifiants non devinables, utiles dans les URL d’une API. gen_random_uuid() produit des UUID v4 ; PostgreSQL 18 ajoute uuidv7(), dont les valeurs croissent avec le temps et s’indexent mieux.
  • numeric est exact : indispensable pour l’argent.
  • text n’a pas de pénalité de performance par rapport à varchar(n) ; on ajoute un CHECK si une longueur maximale est une vraie règle métier.
  • timestamptz enregistre un instant (converti en UTC) et l’affiche dans le fuseau de la session (SET TIME ZONE 'Europe/Paris'). Le type timestamp sans fuseau est ambigu dès que plusieurs fuseaux sont en jeu.

JSONB : de la souplesse dans un modèle relationnel

jsonb stocke du JSON sous une forme binaire, indexable. Il est idéal pour des attributs variables (caractéristiques d’un produit, préférences d’un utilisateur), pas pour remplacer des colonnes interrogées en permanence.

SELECT nom,
       attributs -> 'taille'   AS taille_json,   -- résultat jsonb : "M"
       attributs ->> 'couleur' AS couleur        -- résultat text  : noir
FROM produits
WHERE attributs @> '{"couleur": "noir"}';        -- contient cette paire

->> renvoie du texte, à utiliser pour comparer ou afficher ; @> teste l’inclusion et peut utiliser un index GIN (leçon 4). SQLite propose aussi -> et ->> (depuis la version 3.38), ce qui permet de s’entraîner dans le navigateur.

Upsert et RETURNING

Insérer une ligne, ou la mettre à jour si elle existe déjà, se fait en une seule instruction atomique :

INSERT INTO stocks (produit_id, quantite)
VALUES (42, 10)
ON CONFLICT (produit_id)                       -- exige une contrainte UNIQUE ou PK
DO UPDATE SET quantite = stocks.quantite + EXCLUDED.quantite
RETURNING produit_id, quantite;

EXCLUDED désigne la ligne qu’on tentait d’insérer. ON CONFLICT DO NOTHING ignore simplement les doublons. RETURNING renvoie les lignes écrites (avec leurs valeurs générées : id, cree_le), ce qui évite un SELECT supplémentaire après chaque INSERT. Pour des synchronisations plus complexes, PostgreSQL dispose aussi de MERGE (depuis la version 15).

Premiers pas dans psql

psql est le client en ligne de commande officiel :

psql -h localhost -U app -d boutique   -- connexion
\dt                -- lister les tables
\d commandes       -- structure, index et contraintes d'une table
\x                 -- affichage étendu (une colonne par ligne)
\timing            -- afficher la durée de chaque requête
\q                 -- quitter

\d et \timing vous serviront dans toute la suite du cours pour vérifier vos index et mesurer vos optimisations.

Exercices

Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.

  1. CodeDifficulté 1SQL (SQLite) dans le navigateur1 indice

    Filtrer sur un attribut JSON

    Table disponible : produits(id, nom, attributs) où attributs contient un objet JSON.

    Affichez le nom des produits dont l’attribut couleur vaut noir, en utilisant l’opérateur ->>.

    Cet exercice s’exécute en SQLite, dont la syntaxe est ici identique à PostgreSQL.

    Données de l'exercice (SQL)
    -- En PostgreSQL, attributs serait de type jsonb ; SQLite stocke le JSON en texte
    CREATE TABLE produits (
      id INTEGER PRIMARY KEY,
      nom TEXT NOT NULL,
      attributs TEXT NOT NULL
    );
    INSERT INTO produits VALUES
     (1, 'T-shirt', '{"couleur": "noir", "taille": "M"}'),
     (2, 'Sweat', '{"couleur": "gris", "taille": "L"}'),
     (3, 'Casquette', '{"couleur": "noir"}'),
     (4, 'Chaussettes', '{"taille": "S", "lot": 3}');
    SELECT nom
    FROM produits
    WHERE attributs ->> ... = ...;
  2. CodeDifficulté 2SQL (SQLite) dans le navigateur2 indices

    Répartition par couleur

    Table disponible : produits(id, nom, attributs).

    Affichez chaque couleur (alias couleur, avec 'non renseignée' quand l’attribut est absent) et le nombre de produits (alias nb).

    Cet exercice s’exécute en SQLite, dont la syntaxe est ici identique à PostgreSQL.

    Données de l'exercice (SQL)
    -- En PostgreSQL, attributs serait de type jsonb ; SQLite stocke le JSON en texte
    CREATE TABLE produits (
      id INTEGER PRIMARY KEY,
      nom TEXT NOT NULL,
      attributs TEXT NOT NULL
    );
    INSERT INTO produits VALUES
     (1, 'T-shirt', '{"couleur": "noir", "taille": "M"}'),
     (2, 'Sweat', '{"couleur": "gris", "taille": "L"}'),
     (3, 'Casquette', '{"couleur": "noir"}'),
     (4, 'Chaussettes', '{"taille": "S", "lot": 3}');
    SELECT COALESCE(..., 'non renseignée') AS couleur, COUNT(*) AS nb
    FROM produits
    GROUP BY ...;
  3. QCMDifficulté 11 indice

    Horodater des événements

    Une application enregistre l’heure de connexion d’utilisateurs situés en France et au Québec. Quel type choisir ?

    • timestamp
    • timestamptz
    • text au format ISO
  4. TâcheDifficulté 21 indice

    Upsert de stock dans psql

    Dans une base PostgreSQL de test, créez une table stocks(produit_id bigint PRIMARY KEY, quantite integer NOT NULL CHECK (quantite >= 0)). Écrivez une instruction unique qui ajoute 10 unités au produit 42, en créant la ligne si elle n’existe pas, et qui renvoie la quantité finale. Exécutez-la deux fois.

    • La table est créée avec une clé primaire sur produit_id
    • L'instruction utilise INSERT ... ON CONFLICT (produit_id) DO UPDATE
    • La mise à jour utilise EXCLUDED.quantite pour additionner
    • RETURNING renvoie produit_id et quantite
    • La première exécution renvoie 10, la seconde 20

Quiz de fin de leçon

  1. Que renvoie attributs ->> ‘taille’ sur une colonne jsonb ?

    • Une valeur jsonb
    • Une valeur text
    • Un booléen
  2. Quelle condition est nécessaire pour INSERT … ON CONFLICT (email) ?

    • Un index GIN sur email
    • Une contrainte UNIQUE (ou clé primaire) sur email
    • Une colonne de type uuid
  3. À quoi sert RETURNING ?

    • À annuler une écriture
    • À renvoyer les lignes écrites, avec leurs valeurs générées
    • À relancer la requête en cas d'erreur

La suite du cours

  1. Lire un plan d'exécution avec EXPLAIN ANALYZE20 min
  2. Les index B-tree : créer, composer, rendre utilisables20 min
  3. Index spécialisés : partiels, expressions, GIN, BRIN18 min
  4. Optimiser les requêtes et entretenir la base20 min

PostgreSQL en pratique (index, performances) · Avancé · 3 h 36