PostgreSQL offre un support puissant pour stocker et interroger des données JSON grâce à ses types de données json et jsonb. Le type jsonb est particulièrement utile car il stocke les données dans un format binaire, ce qui permet une indexation et des requêtes plus efficaces que le type json, basé sur du texte.
Dans ce tutoriel, on va explorer les opérations JSON les plus courantes dans PostgreSQL avec des exemples pratiques que tu pourras utiliser dans tes applications.
Créer des données JSON
Commençons par créer un tableau avec une colonne jsonb et y insérer quelques données d’exemple :
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
info JSONB
);
-- Insert a customer with basic information
INSERT INTO customers (info) VALUES (
'{"name": "Alice Smith", "email": "alice@example.com", "age": 32, "addresses": [
{"type": "home", "city": "San Francisco", "state": "CA"},
{"type": "work", "city": "Oakland", "state": "CA"}
]}'
);
-- Insert another customer
INSERT INTO customers (info) VALUES (
'{"name": "Bob Johnson", "email": "bob@example.com", "age": 45, "addresses": [
{"type": "home", "city": "Seattle", "state": "WA"}
], "orders": [101, 102, 103]}'
);
Opérations JSON de base
Récupérer le document JSON complet
SELECT info FROM customers WHERE id = 1;
Résultat :
{
"age": 32,
"name": "Alice Smith",
"email": "alice@example.com",
"addresses": [
{"city": "San Francisco", "type": "home", "state": "CA"},
{"city": "Oakland", "type": "work", "state": "CA"}
]
}
Extraire des valeurs du JSON
PostgreSQL propose plusieurs opérateurs pour extraire des valeurs du JSON :
Extraire un champ précis avec l’opérateur -> (renvoie du JSON)
-- Get the name field as JSON
SELECT info -> 'name' FROM customers;
Résultat :
"Alice Smith"
"Bob Johnson"
Extraire un champ précis avec l’opérateur -» (renvoie du texte)
-- Get the name field as text
SELECT info ->> 'name' AS customer_name FROM customers;
Résultat :
customer_name
----------------
Alice Smith
Bob Johnson
Accéder aux objets imbriqués
-- Getting the first address (array element 0) city
SELECT info -> 'addresses' -> 0 ->> 'city' AS home_city FROM customers;
Résultat :
home_city
------------
San Francisco
Seattle
Utiliser l’opérateur #> pour accéder à un chemin
-- Using the path syntax to get the same value
SELECT info #> '{addresses,0,city}' FROM customers;
Résultat :
"San Francisco"
"Seattle"
Utiliser l’opérateur #» pour accéder à un chemin en texte
-- Using the path syntax to get the same value as text
SELECT info #>> '{addresses,0,city}' AS home_city FROM customers;
Résultat :
home_city
------------
San Francisco
Seattle
Modifier des données JSON
PostgreSQL fournit plusieurs fonctions pour modifier des données JSON :
Ajouter un champ avec jsonb_set
-- Add a new field 'premium_customer' to a customer
UPDATE customers
SET info = jsonb_set(info, '{premium_customer}', 'true')
WHERE id = 1;
Supprimer un champ avec l’opérateur -
-- Remove the premium_customer field
UPDATE customers
SET info = info - 'premium_customer'
WHERE id = 1;
Fusionner des objets JSON avec ||
-- Add multiple fields by merging objects
UPDATE customers
SET info = info || '{"status": "active", "last_login": "2025-03-23"}'::jsonb
WHERE id = 1;
Mettre à jour un champ imbriqué
-- Update a nested field (home address state)
UPDATE customers
SET info = jsonb_set(
info,
'{addresses,0,state}',
'"NY"'::jsonb
)
WHERE id = 1;
Interroger des tableaux JSON
Travailler avec des tableaux JSON est une opération courante :
Obtenir la longueur d’un tableau
-- Get the number of addresses for each customer
SELECT
info ->> 'name' AS customer_name,
jsonb_array_length(info -> 'addresses') AS address_count
FROM customers;
Résultat :
customer_name | address_count
---------------+--------------
Alice Smith | 2
Bob Johnson | 1
Extraire des éléments précis d’un tableau
-- Get the second address (index 1) for Alice
SELECT info -> 'addresses' -> 1 FROM customers WHERE id = 1;
Résultat :
{"city": "Oakland", "type": "work", "state": "CA"}
Désimbriquer des tableaux pour interroger chaque élément
-- Unnest the addresses array to query individual addresses
SELECT
c.id,
c.info ->> 'name' AS customer_name,
jsonb_array_elements(c.info -> 'addresses') AS address
FROM customers c;
Résultat :
id | customer_name | address
---+---------------+------------------------------------------------------
1 | Alice Smith | {"city": "San Francisco", "type": "home", "state": "NY"}
1 | Alice Smith | {"city": "Oakland", "type": "work", "state": "CA"}
2 | Bob Johnson | {"city": "Seattle", "type": "home", "state": "WA"}
Filtrer avec des tableaux désimbriqués
-- Find customers with addresses in CA
SELECT DISTINCT
c.id,
c.info ->> 'name' AS customer_name
FROM customers c,
jsonb_array_elements(c.info -> 'addresses') AS addr
WHERE addr ->> 'state' = 'CA';
Résultat :
id | customer_name
---+--------------
1 | Alice Smith
Vérifier des clés et des valeurs
Vérifier si une clé existe
-- Find customers who have the 'orders' key
SELECT id, info ->> 'name' AS customer_name
FROM customers
WHERE info ? 'orders';
Résultat :
id | customer_name
---+--------------
2 | Bob Johnson
Rechercher des valeurs dans des tableaux
-- Find customers with a specific order number
SELECT id, info ->> 'name' AS customer_name
FROM customers
WHERE info -> 'orders' ? '101';
Résultat :
id | customer_name
---+--------------
2 | Bob Johnson
Vérifier si un élément du tableau correspond
-- Find customers who have an address in Oakland (using path operators)
SELECT id, info ->> 'name' AS customer_name
FROM customers
WHERE info @> '{"addresses": [{"city": "Oakland"}]}';
Résultat :
id | customer_name
---+--------------
1 | Alice Smith
Indexer des données JSON
Pour les bases de données de production avec de gros volumes de JSON, une bonne indexation est indispensable :
Créer un index GIN pour les requêtes de contenance
-- Create a GIN index for faster containment operations
CREATE INDEX idx_customers_info ON customers USING GIN (info);
Créer un index dédié à un champ JSON
-- Create an index on the 'email' field for fast lookups
CREATE INDEX idx_customers_email ON customers ((info ->> 'email'));
Utiliser des champs indexés dans les requêtes
-- This query can use the index we created above
SELECT id, info ->> 'name' AS customer_name
FROM customers
WHERE info ->> 'email' = 'alice@example.com';
Convertir entre JSON et tables
Convertir du JSON en lignes (jsonb_to_record)
-- Extract JSON fields into a tabular structure
SELECT
id,
rec.name,
rec.email,
rec.age
FROM
customers,
jsonb_to_record(info) AS rec(
name text,
email text,
age int
);
Résultat :
id | name | email | age
---+--------------+-------------------+-----
1 | Alice Smith | alice@example.com | 32
2 | Bob Johnson | bob@example.com | 45
Convertir des tableaux imbriqués en tables (jsonb_to_recordset)
-- Extract addresses from all customers into a table
SELECT
c.id,
c.info ->> 'name' AS customer_name,
addr.type AS address_type,
addr.city,
addr.state
FROM
customers c,
jsonb_to_recordset(c.info -> 'addresses') AS addr(
type text,
city text,
state text
);
Résultat :
id | customer_name | address_type | city | state
---+---------------+--------------+---------------+------
1 | Alice Smith | home | San Francisco | NY
1 | Alice Smith | work | Oakland | CA
2 | Bob Johnson | home | Seattle | WA
Convertir des résultats de requête en JSON
-- Aggregate customer orders into a JSON array
SELECT
c.id,
c.info ->> 'name' AS customer_name,
json_agg(o.order_id) AS orders
FROM
customers c
CROSS JOIN LATERAL (
SELECT jsonb_array_elements_text(c.info -> 'orders') AS order_id
) o
GROUP BY c.id, c.info ->> 'name';
Résultat :
id | customer_name | orders
---+---------------+----------
2 | Bob Johnson | ["101", "102", "103"]
Considérations de performance
Quand tu travailles avec du JSON dans PostgreSQL, garde ces points de performance en tête :
-
Utilise
jsonbplutôt quejson: le typejsonboffre de meilleures performances pour la plupart des opérations. -
Indexe de façon stratégique : utilise des index GIN pour les requêtes de contenance, mais crée des index dédiés pour les champs souvent interrogés.
-
Réfléchis bien avant de dénormaliser : même si le JSON permet de dénormaliser ton modèle de données, une imbrication excessive peut nuire aux performances des requêtes.
-
Surveille tes schémas de requêtes : les expressions de chemin JSON complexes ou la désimbrication fréquente de gros tableaux peuvent coûter cher.
-
Limite la taille des tableaux : les très gros tableaux JSON peuvent poser des problèmes de performance une fois désimbriqués.
-
Pense à la projection : extrais uniquement les champs dont tu as besoin plutôt que de renvoyer tout le document JSON.
Conclusion
Les capacités JSON de PostgreSQL en font un excellent choix pour les applications qui doivent stocker et interroger des données semi-structurées. Avec les opérateurs et fonctions vus dans ce tutoriel, tu peux travailler efficacement avec du JSON tout en profitant de la puissance et de la fiabilité d’une base de données relationnelle mature.
Pour les modèles de données complexes, envisage une approche hybride : utilise des tables structurées pour les schémas fixes et les données souvent interrogées, et du JSON pour les données flexibles sans schéma ou les structures imbriquées difficiles à modéliser de façon relationnelle.
Envie d’essayer ces exemples toi-même ? Ouvre Beekeeper Studio et commence dès aujourd’hui à explorer les capacités JSON de PostgreSQL.
Beekeeper Studio est une interface de base de données gratuite & open source
Le meilleur outil de requêtes & éditeur SQL que j’aie jamais utilisé. Il m’offre tout ce dont j’ai besoin pour gérer ma base de données. - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studio est rapide, intuitif et facile à utiliser. Beekeeper prend en charge plein de bases de données et fonctionne parfaitement sous Windows, Mac et Linux.
Ce que les utilisateurs disent de Beekeeper Studio
« Beekeeper Studio a complètement remplacé mon ancien workflow SQL. C’est rapide, intuitif, et ça rend le travail sur les bases de données à nouveau agréable. »
« J’ai essayé plein d’interfaces de base de données, mais Beekeeper trouve l’équilibre parfait entre fonctionnalités et simplicité. Ça marche, tout simplement. »