🔝 Retour au Sommaire
NULL est probablement le concept le plus mal compris et le plus problématique en SQL. Il est source d'innombrables bugs, de comportements inattendus et de frustrations pour les développeurs débutants comme expérimentés.
Pourquoi ? Parce que NULL ne se comporte pas comme les autres valeurs. Il ne représente ni zéro, ni une chaîne vide, ni faux. NULL représente l'absence d'information ou une valeur inconnue.
Dans ce chapitre, nous allons :
- Comprendre ce qu'est réellement NULL
- Découvrir la logique ternaire (trois états au lieu de deux)
- Explorer comment NULL affecte les opérations
- Maîtriser les fonctions pour gérer NULL (COALESCE, NULLIF)
- Identifier les pièges courants et comment les éviter
NULL signifie "valeur inconnue" ou "absence de valeur". C'est un marqueur spécial qui indique qu'une information n'est pas disponible.
Exemples de situations où NULL a du sens :
-- Un employé sans manager (CEO, par exemple)
INSERT INTO employes (nom, manager_id) VALUES ('Alice', NULL);
-- Un produit dont le prix n'est pas encore fixé
INSERT INTO produits (nom, prix) VALUES ('Nouveau produit', NULL);
-- Un client qui n'a pas fourni son numéro de téléphone
INSERT INTO clients (nom, telephone) VALUES ('Bob', NULL);
-- Une commande pas encore livrée (date de livraison inconnue)
INSERT INTO commandes (numero, date_livraison) VALUES ('CMD123', NULL);NULL ≠ 0 (zéro)
-- Ces trois employés ont des situations DIFFÉRENTES
INSERT INTO employes (nom, salaire) VALUES
('Alice', 50000), -- Alice gagne 50000
('Bob', 0), -- Bob gagne 0 (bénévole ?)
('Charlie', NULL); -- Le salaire de Charlie est inconnu
-- Requête pour comprendre
SELECT nom, salaire,
CASE
WHEN salaire IS NULL THEN 'Salaire inconnu'
WHEN salaire = 0 THEN 'Salaire nul'
ELSE 'Salaire: ' || salaire
END as statut
FROM employes;Résultat :
nom | salaire | statut
---------|---------|-------------------
Alice | 50000 | Salaire: 50000
Bob | 0 | Salaire nul
Charlie | NULL | Salaire inconnu
NULL ≠ '' (chaîne vide)
-- Ces trois clients ont des situations DIFFÉRENTES
INSERT INTO clients (nom, email) VALUES
('Alice', 'alice@example.com'), -- Alice a fourni un email
('Bob', ''), -- Bob a fourni une chaîne vide
('Charlie', NULL); -- Charlie n'a pas fourni d'email
-- Vérification
SELECT nom, email,
CASE
WHEN email IS NULL THEN 'Email non fourni'
WHEN email = '' THEN 'Email vide'
ELSE email
END as statut_email
FROM clients;NULL ≠ FALSE (faux)
-- NULL n'est pas un booléen
-- NULL signifie "on ne sait pas si c'est vrai ou faux"
SELECT
TRUE as vrai,
FALSE as faux,
NULL as inconnu;En logique booléenne classique, vous avez deux états : TRUE (vrai) ou FALSE (faux).
En SQL, avec NULL, vous avez trois états :
- TRUE : vrai
- FALSE : faux
- NULL : inconnu
C'est ce qu'on appelle la logique ternaire (ou logique à trois valeurs).
Toute comparaison impliquant NULL retourne NULL (pas TRUE, pas FALSE) :
SELECT
NULL = NULL as test1, -- NULL (pas TRUE !)
NULL != NULL as test2, -- NULL
NULL > 5 as test3, -- NULL
NULL < 5 as test4, -- NULL
5 = NULL as test5, -- NULL
NULL + 10 as test6, -- NULL
NULL * 2 as test7, -- NULL
NULL || 'texte' as test8; -- NULL (concaténation)Résultat : Toutes ces expressions retournent NULL.
Pourquoi ?
Pensez-y logiquement :
- Si vous ne connaissez pas une valeur (NULL), comment pouvez-vous dire si elle est égale à une autre ?
- Si vous ne connaissez pas un nombre (NULL), comment pouvez-vous savoir s'il est supérieur à 5 ?
- Si vous ne connaissez pas une valeur (NULL), comment pouvez-vous lui ajouter 10 ?
La réponse est : vous ne pouvez pas. Le résultat est donc inconnu (NULL).
C'est pourquoi SQL fournit des opérateurs spéciaux pour tester NULL :
-- ❌ NE FONCTIONNE JAMAIS
WHERE colonne = NULL -- Retourne toujours NULL (évalué comme FALSE en WHERE)
WHERE colonne != NULL -- Retourne toujours NULL
-- ✅ SYNTAXE CORRECTE
WHERE colonne IS NULL -- Teste si la valeur est NULL
WHERE colonne IS NOT NULL -- Teste si la valeur n'est pas NULL Exemple concret :
-- Table d'employés
CREATE TABLE employes (
id SERIAL PRIMARY KEY,
nom VARCHAR(100),
manager_id INTEGER
);
INSERT INTO employes (nom, manager_id) VALUES
('Alice', NULL), -- PDG, pas de manager
('Bob', 1), -- Manager: Alice
('Charlie', 1), -- Manager: Alice
('David', 2); -- Manager: Bob
-- ❌ Cette requête ne retourne AUCUNE ligne
SELECT nom FROM employes WHERE manager_id = NULL;
-- ✅ Cette requête retourne Alice
SELECT nom FROM employes WHERE manager_id IS NULL;
-- ✅ Cette requête retourne Bob, Charlie, David
SELECT nom FROM employes WHERE manager_id IS NOT NULL;| A | B | A AND B |
|---|---|---|
| TRUE | TRUE | TRUE |
| TRUE | FALSE | FALSE |
| TRUE | NULL | NULL |
| FALSE | TRUE | FALSE |
| FALSE | FALSE | FALSE |
| FALSE | NULL | FALSE |
| NULL | TRUE | NULL |
| NULL | FALSE | FALSE |
| NULL | NULL | NULL |
Règle mnémotechnique :
FALSE AND n'importe quoi= FALSE (car une seule condition fausse suffit)TRUE AND NULL= NULL (on ne sait pas)NULL AND NULL= NULL (on ne sait pas)
Exemple :
SELECT nom, salaire, age
FROM employes
WHERE salaire > 50000 AND age > 30;
-- Si un employé a salaire=60000 mais age=NULL :
-- 60000 > 50000 = TRUE
-- NULL > 30 = NULL
-- TRUE AND NULL = NULL
-- → Cet employé NE sera PAS retourné (NULL est évalué comme FALSE en WHERE)| A | B | A OR B |
|---|---|---|
| TRUE | TRUE | TRUE |
| TRUE | FALSE | TRUE |
| TRUE | NULL | TRUE |
| FALSE | TRUE | TRUE |
| FALSE | FALSE | FALSE |
| FALSE | NULL | NULL |
| NULL | TRUE | TRUE |
| NULL | FALSE | NULL |
| NULL | NULL | NULL |
Règle mnémotechnique :
TRUE OR n'importe quoi= TRUE (car une seule condition vraie suffit)FALSE OR NULL= NULL (on ne sait pas)NULL OR NULL= NULL (on ne sait pas)
Exemple :
SELECT nom, statut, date_fin_contrat
FROM employes
WHERE statut = 'actif' OR date_fin_contrat > CURRENT_DATE;
-- Si un employé a statut='inactif' et date_fin_contrat=NULL :
-- 'inactif' = 'actif' = FALSE
-- NULL > CURRENT_DATE = NULL
-- FALSE OR NULL = NULL
-- → Cet employé NE sera PAS retourné| A | NOT A |
|---|---|
| TRUE | FALSE |
| FALSE | TRUE |
| NULL | NULL |
Exemple :
SELECT nom FROM employes WHERE NOT (age > 30);
-- Si un employé a age=NULL :
-- age > 30 = NULL
-- NOT NULL = NULL
-- → Cet employé NE sera PAS retournéLa clause WHERE ne retient que les lignes où la condition évalue à TRUE.
Si une condition évalue à NULL ou FALSE, la ligne est éliminée.
-- Table exemple
CREATE TABLE produits (
nom VARCHAR(100),
prix NUMERIC,
remise NUMERIC
);
INSERT INTO produits VALUES
('Produit A', 100, 10),
('Produit B', 50, NULL),
('Produit C', 200, 20);
-- Requête
SELECT nom, prix, remise
FROM produits
WHERE prix - remise > 80;
-- Résultats :
-- Produit A : 100 - 10 = 90 > 80 → TRUE → retourné
-- Produit B : 50 - NULL = NULL > 80 → NULL → PAS retourné
-- Produit C : 200 - 20 = 180 > 80 → TRUE → retournéRésultat final :
nom | prix | remise
-----------|------|-------
Produit A | 100 | 10
Produit C | 200 | 20
Le Produit B est éliminé car NULL > 80 = NULL.
Les fonctions d'agrégation ignorent les valeurs NULL (sauf COUNT(*)).
CREATE TABLE employes (
nom VARCHAR(100),
telephone VARCHAR(20)
);
INSERT INTO employes VALUES
('Alice', '0601020304'),
('Bob', NULL),
('Charlie', '0605060708'),
('David', NULL);
-- COUNT(*) : compte toutes les lignes
SELECT COUNT(*) FROM employes; -- Résultat : 4
-- COUNT(colonne) : compte les valeurs NON-NULL
SELECT COUNT(telephone) FROM employes; -- Résultat : 2
-- COUNT(DISTINCT colonne) : compte les valeurs uniques NON-NULL
SELECT COUNT(DISTINCT telephone) FROM employes; -- Résultat : 2Ces fonctions ignorent les NULL :
CREATE TABLE ventes (
produit VARCHAR(50),
montant NUMERIC
);
INSERT INTO ventes VALUES
('Produit A', 100),
('Produit B', NULL), -- Vente annulée, montant inconnu
('Produit C', 200),
('Produit D', NULL);
-- SUM ignore les NULL
SELECT SUM(montant) FROM ventes; -- Résultat : 300 (pas 300 + NULL + NULL)
-- AVG ignore les NULL
SELECT AVG(montant) FROM ventes; -- Résultat : 150 (moyenne de 100 et 200)
-- MIN et MAX ignorent les NULL
SELECT MIN(montant) FROM ventes; -- Résultat : 100
SELECT MAX(montant) FROM ventes; -- Résultat : 200
-- COUNT(*) compte tout
SELECT COUNT(*) FROM ventes; -- Résultat : 4
-- COUNT(montant) ignore les NULL
SELECT COUNT(montant) FROM ventes; -- Résultat : 2-- Moyenne calculée par PostgreSQL (ignore NULL)
SELECT AVG(montant) FROM ventes; -- 150
-- Si vous calculez manuellement avec SUM/COUNT :
SELECT SUM(montant) / COUNT(*) FROM ventes; -- 300 / 4 = 75 ❌ FAUX
-- Calcul correct
SELECT SUM(montant) / COUNT(montant) FROM ventes; -- 300 / 2 = 150 ✓PostgreSQL fournit plusieurs fonctions pour gérer les NULL de manière élégante.
COALESCE retourne la première valeur non-NULL de sa liste d'arguments.
Syntaxe :
COALESCE(valeur1, valeur2, valeur3, ..., valeur_par_defaut)Fonctionnement :
- Si
valeur1n'est pas NULL → retournevaleur1 - Sinon, si
valeur2n'est pas NULL → retournevaleur2 - Sinon, si
valeur3n'est pas NULL → retournevaleur3 - ... et ainsi de suite
- Si toutes les valeurs sont NULL → retourne
valeur_par_defaut
Exemples simples :
SELECT COALESCE(NULL, 'Valeur par défaut'); -- Résultat : 'Valeur par défaut'
SELECT COALESCE('Valeur', 'Défaut'); -- Résultat : 'Valeur'
SELECT COALESCE(NULL, NULL, 'Troisième', 'Défaut'); -- Résultat : 'Troisième'
SELECT COALESCE(NULL, NULL, NULL); -- Résultat : NULL (aucune valeur non-NULL)Cas d'usage pratiques :
SELECT
nom,
COALESCE(telephone, 'Non renseigné') as telephone,
COALESCE(email, 'Non renseigné') as email
FROM clients;
-- Si Alice n'a pas de téléphone :
-- nom: Alice, telephone: Non renseigné-- Calculer un prix final avec remise optionnelle
SELECT
nom_produit,
prix,
remise,
prix - COALESCE(remise, 0) as prix_final
FROM produits;
-- Si remise est NULL, on utilise 0 pour le calcul
-- Sinon, on utilise la vraie valeur de remise-- Utiliser le téléphone mobile, sinon fixe, sinon un message
SELECT
nom,
COALESCE(tel_mobile, tel_fixe, 'Aucun téléphone') as contact
FROM clients;-- Salaire moyen ou message si aucune donnée
SELECT
departement,
COALESCE(AVG(salaire)::TEXT, 'Aucune donnée') as salaire_moyen
FROM employes
GROUP BY departement; -- Afficher le nom du manager ou 'PDG' si NULL
SELECT
e.nom as employe,
COALESCE(m.nom, 'PDG') as manager
FROM employes e
LEFT JOIN employes m ON e.manager_id = m.id; -- Insérer des valeurs avec défauts si non fournies
INSERT INTO preferences (user_id, theme, langue, notifications)
VALUES (
123,
COALESCE(:theme, 'clair'), -- 'clair' par défaut
COALESCE(:langue, 'fr'), -- 'fr' par défaut
COALESCE(:notifications, true) -- true par défaut
);NULLIF retourne NULL si deux valeurs sont égales, sinon retourne la première valeur.
Syntaxe :
NULLIF(valeur1, valeur2)Fonctionnement :
- Si
valeur1 = valeur2→ retourne NULL - Sinon → retourne
valeur1
Pourquoi est-ce utile ?
Parfois, certaines valeurs "spéciales" doivent être traitées comme NULL (par exemple, 0, -1, '', 'N/A', etc.).
Exemples simples :
SELECT NULLIF(5, 5); -- Résultat : NULL (5 = 5)
SELECT NULLIF(5, 10); -- Résultat : 5 (5 ≠ 10)
SELECT NULLIF('', ''); -- Résultat : NULL ('' = '')
SELECT NULLIF('abc', ''); -- Résultat : 'abc' ('abc' ≠ '') Cas d'usage pratiques :
-- ❌ Division par zéro → ERREUR
SELECT 10 / 0; -- ERROR: division by zero
-- ✅ Avec NULLIF
SELECT 10 / NULLIF(0, 0); -- Résultat : NULL (pas d'erreur)
-- Cas réel : calculer un pourcentage
SELECT
nom_produit,
ventes,
stock,
(ventes * 100.0 / NULLIF(stock, 0))::NUMERIC(10,2) as pourcentage_vendu
FROM produits;
-- Si stock = 0, le résultat sera NULL au lieu d'une erreur-- Certaines applications insèrent '' au lieu de NULL
-- Normaliser ces données :
UPDATE clients
SET email = NULLIF(email, '')
WHERE email = '';
-- Ou en requête de sélection
SELECT
nom,
NULLIF(email, '') as email_propre
FROM clients;-- Anciennes bases de données utilisent parfois -1 ou 0 pour "non défini"
SELECT
nom_produit,
NULLIF(stock, -1) as stock_reel,
NULLIF(delai_livraison, 0) as delai_reel
FROM produits;
-- stock=-1 devient NULL
-- delai_livraison=0 devient NULL-- Fichiers CSV avec valeurs spéciales
SELECT
nom,
NULLIF(telephone, 'N/A') as telephone,
NULLIF(ville, 'Unknown') as ville,
NULLIF(age, -1) as age
FROM donnees_importees;Ces deux fonctions se complètent souvent :
-- Convertir les chaînes vides en NULL, puis fournir un défaut
SELECT
nom,
COALESCE(NULLIF(email, ''), 'aucun@email.com') as email
FROM clients;
-- Diviser avec protection contre zéro, puis fournir un défaut
SELECT
produit,
COALESCE(ventes / NULLIF(stock, 0), 0) as ratio
FROM statistiques;
-- Étapes :
-- 1. NULLIF(stock, 0) : si stock=0, retourne NULL
-- 2. ventes / NULL = NULL
-- 3. COALESCE(NULL, 0) = 0Ces fonctions ignorent NULL par défaut dans PostgreSQL :
-- GREATEST retourne la plus grande valeur (ignore NULL)
SELECT GREATEST(10, NULL, 5, 20); -- Résultat : 20
SELECT GREATEST(NULL, NULL); -- Résultat : NULL (tous NULL)
-- LEAST retourne la plus petite valeur (ignore NULL)
SELECT LEAST(10, NULL, 5, 20); -- Résultat : 5
-- Cas d'usage : prendre le prix le plus bas entre plusieurs options
SELECT
nom_produit,
LEAST(prix_normal, prix_promo, prix_membre) as meilleur_prix
FROM produits;CASE permet une gestion très flexible de NULL :
SELECT
nom,
salaire,
CASE
WHEN salaire IS NULL THEN 'Salaire non défini'
WHEN salaire = 0 THEN 'Bénévole'
WHEN salaire < 30000 THEN 'Junior'
WHEN salaire < 60000 THEN 'Confirmé'
ELSE 'Senior'
END as categorie
FROM employes;Opérateur spécial qui traite NULL comme une valeur comparable :
-- Comparaison classique
SELECT NULL = NULL; -- Résultat : NULL
SELECT NULL != NULL; -- Résultat : NULL
-- Avec IS DISTINCT FROM
SELECT NULL IS DISTINCT FROM NULL; -- Résultat : FALSE (ils sont "égaux")
SELECT 5 IS DISTINCT FROM NULL; -- Résultat : TRUE (ils sont différents)
SELECT 5 IS DISTINCT FROM 5; -- Résultat : FALSE
-- Cas d'usage : détecter des changements (même si NULL)
SELECT * FROM employes_nouveau
WHERE (salaire, departement) IS DISTINCT FROM
(SELECT salaire, departement FROM employes_ancien WHERE id = employes_nouveau.id);C'est l'un des pièges les plus vicieux de SQL.
-- Table de départements autorisés
CREATE TABLE dept_autorises (dept VARCHAR(50));
INSERT INTO dept_autorises VALUES ('IT'), ('RH'), (NULL);
-- Trouver les employés n'étant PAS dans les départements autorisés
SELECT nom, departement
FROM employes
WHERE departement NOT IN (SELECT dept FROM dept_autorises); Résultat attendu : Les employés des départements non listés (Ventes, Finance, etc.)
Résultat réel : AUCUNE LIGNE ! 😱
Pourquoi ?
NOT IN est équivalent à une série de comparaisons avec != et AND :
WHERE departement NOT IN ('IT', 'RH', NULL)
-- Est équivalent à :
WHERE departement != 'IT'
AND departement != 'RH'
AND departement != NULL
-- Or, departement != NULL retourne toujours NULL
-- TRUE AND TRUE AND NULL = NULL
-- Donc aucune ligne n'est retenue✅ Solution 1 : Exclure les NULL de la sous-requête
WHERE departement NOT IN (SELECT dept FROM dept_autorises WHERE dept IS NOT NULL)✅ Solution 2 : Utiliser NOT EXISTS
WHERE NOT EXISTS (
SELECT 1 FROM dept_autorises
WHERE dept = employes.departement
)CREATE TABLE commandes (
client_id INTEGER,
montant NUMERIC
);
INSERT INTO commandes VALUES
(1, 100),
(1, 200),
(2, 150),
(NULL, 50), -- Commande sans client
(NULL, 75);
SELECT client_id, SUM(montant)
FROM commandes
GROUP BY client_id; Résultat :
client_id | sum
----------|-----
1 | 300
2 | 150
NULL | 125 -- Les NULL sont groupés ensemble !
Les valeurs NULL sont traitées comme égales lors du regroupement.
Dans PostgreSQL, plusieurs NULL sont autorisés dans une colonne UNIQUE :
CREATE TABLE utilisateurs (
id SERIAL PRIMARY KEY,
email VARCHAR(100) UNIQUE
);
-- Ces trois insertions fonctionnent !
INSERT INTO utilisateurs (email) VALUES (NULL);
INSERT INTO utilisateurs (email) VALUES (NULL);
INSERT INTO utilisateurs (email) VALUES (NULL);
-- Mais celle-ci échoue (doublon non-NULL)
INSERT INTO utilisateurs (email) VALUES ('test@example.com');
INSERT INTO utilisateurs (email) VALUES ('test@example.com'); -- ❌ ERREUR Pourquoi ? Selon le standard SQL, NULL != NULL, donc plusieurs NULL ne violent pas la contrainte UNIQUE.
Si vous voulez interdire les NULL multiples :
-- Ajoutez une contrainte NOT NULL
email VARCHAR(100) UNIQUE NOT NULL
-- Ou utilisez un index unique avec filtrage (PostgreSQL)
CREATE UNIQUE INDEX idx_users_email ON utilisateurs(email) WHERE email IS NOT NULL;
-- Permet un seul NULL, mais pas de doublons non-NULLPar défaut, PostgreSQL place les NULL en dernier lors du tri ascendant :
SELECT nom, salaire
FROM employes
ORDER BY salaire ASC; Résultat :
nom | salaire
---------|--------
Alice | 30000
Bob | 50000
Charlie | 70000
David | NULL -- NULL en dernier
Eve | NULL
Contrôler le placement des NULL :
-- NULL en premier
ORDER BY salaire ASC NULLS FIRST
-- NULL en dernier (par défaut pour ASC)
ORDER BY salaire ASC NULLS LAST
-- NULL en premier (par défaut pour DESC)
ORDER BY salaire DESC NULLS FIRST
-- NULL en dernier
ORDER BY salaire DESC NULLS LASTSELECT 'Bonjour ' || NULL || ' monde'; -- Résultat : NULL
-- Toute la chaîne devient NULL !
-- ✅ Solution avec COALESCE
SELECT 'Bonjour ' || COALESCE(prenom, '') || ' ' || nom
FROM clients; -- Un seul NULL rend tout le calcul NULL
SELECT prix * quantite * (1 - COALESCE(remise_pct, 0) / 100) as total
FROM lignes_commande;
-- Si quantite est NULL, total sera NULL
-- Si remise_pct est NULL, on utilise 0Utiliser des valeurs par défaut :
CREATE TABLE employes (
nom VARCHAR(100) NOT NULL,
telephone VARCHAR(20) DEFAULT '', -- Chaîne vide au lieu de NULL
actif BOOLEAN DEFAULT TRUE, -- TRUE par défaut
date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);NULL= "information non fournie"''= "information fournie, mais vide"
CREATE TABLE produits (
id SERIAL PRIMARY KEY,
nom VARCHAR(100) NOT NULL, -- Un produit DOIT avoir un nom
prix NUMERIC(10,2) NOT NULL, -- Un produit DOIT avoir un prix
description TEXT, -- Optionnel (peut être NULL)
image_url VARCHAR(500) -- Optionnel
);-- À la sélection
SELECT
nom,
COALESCE(telephone, 'Non renseigné'),
COALESCE(email, 'Non renseigné'),
COALESCE(adresse, 'Non renseignée')
FROM clients;
-- À l'insertion
INSERT INTO produits (nom, prix, remise)
VALUES ('Nouveau produit', 99.99, COALESCE(:remise_input, 0)); -- Transformer les chaînes vides en NULL
INSERT INTO clients (nom, email, telephone)
VALUES (
'Alice',
NULLIF(TRIM(:email), ''), -- Si email vide, mettre NULL
NULLIF(TRIM(:telephone), '') -- Si telephone vide, mettre NULL
);CREATE TABLE employes (
id SERIAL PRIMARY KEY,
nom VARCHAR(100) NOT NULL,
-- NULL signifie "pas de manager" (cas du PDG)
manager_id INTEGER REFERENCES employes(id),
-- NULL signifie "salaire non négocié" ou "données confidentielles"
salaire NUMERIC(10,2),
-- NULL signifie "date de fin de contrat non définie" (CDI)
date_fin_contrat DATE
);Certains systèmes utilisent des valeurs sentinelles au lieu de NULL :
-1pour "non défini"0pour "inconnu"''pour "pas de texte"'1900-01-01'pour "pas de date"
Avantages de NULL :
- Sémantiquement correct
- Pas de confusion avec des valeurs réelles
- Fonctions d'agrégation les ignorent automatiquement
- Standard SQL
Inconvénients de NULL :
- Logique ternaire complexe
- Pièges avec NOT IN, comparaisons, etc.
- Nécessite IS NULL / IS NOT NULL
Recommandation : Utilisez NULL pour représenter l'absence de données, mais soyez conscient des pièges.
-- Générer un rapport de ventes avec gestion complète de NULL
SELECT
-- Nom du vendeur avec défaut
COALESCE(v.nom, 'Vendeur inconnu') as vendeur,
-- Département avec défaut
COALESCE(v.departement, 'Non assigné') as departement,
-- Nombre de ventes (COUNT(*) ne compte pas NULL)
COUNT(*) as nb_ventes,
-- Montant total (SUM ignore NULL)
COALESCE(SUM(c.montant), 0) as total_ventes,
-- Montant moyen (AVG ignore NULL)
COALESCE(AVG(c.montant)::NUMERIC(10,2), 0) as vente_moyenne,
-- Commission avec défaut si NULL
COALESCE(v.taux_commission, 0) as taux_commission,
-- Calcul sécurisé de la commission
COALESCE(SUM(c.montant), 0) * COALESCE(v.taux_commission, 0) / 100 as commission_totale,
-- Dernière vente avec message si aucune
COALESCE(MAX(c.date_vente)::TEXT, 'Aucune vente') as derniere_vente
FROM vendeurs v
LEFT JOIN commandes c ON v.id = c.vendeur_id
GROUP BY v.id, v.nom, v.departement, v.taux_commission
ORDER BY total_ventes DESC NULLS LAST; -
NULL ≠ 0, NULL ≠ '', NULL ≠ FALSE
- NULL signifie "valeur inconnue" ou "absence de valeur"
-
Logique ternaire : TRUE, FALSE, NULL
- Toute opération avec NULL retourne généralement NULL
-
WHERE élimine les NULL
- Seules les lignes où la condition = TRUE sont retenues
- NULL et FALSE sont éliminés
-
Utilisez IS NULL et IS NOT NULL
- Jamais
= NULLou!= NULL
- Jamais
-
Les agrégations ignorent NULL
- Sauf
COUNT(*)qui compte toutes les lignes
- Sauf
-
COALESCE pour les valeurs par défaut
COALESCE(valeur, defaut)retourne la première valeur non-NULL
-
NULLIF pour convertir en NULL
NULLIF(valeur, sentinelle)retourne NULL si égales
-
Attention à NOT IN avec NULL
- Peut ne retourner aucune ligne de manière inattendue
-
NULL dans UNIQUE permet des doublons
- Plusieurs NULL sont autorisés
-
Documentez la signification de NULL
- Dans votre schéma, précisez ce que NULL représente
NULL est un concept fondamental mais déroutant en SQL. Sa logique ternaire (TRUE/FALSE/NULL) crée des comportements contre-intuitifs qui peuvent générer des bugs subtils.
Les clés pour maîtriser NULL :
- Comprendre que NULL signifie "inconnu", pas "zéro" ou "vide"
- Utiliser
IS NULLetIS NOT NULLpour les tests - Anticiper le comportement de NULL dans les opérations et les agrégations
- Gérer NULL explicitement avec
COALESCE,NULLIFetCASE - Éviter NULL quand une valeur est obligatoire (contrainte
NOT NULL) - Documenter la signification de NULL dans votre modèle de données
Avec ces connaissances, vous êtes maintenant équipé pour éviter les pièges courants et écrire des requêtes SQL robustes qui gèrent correctement l'absence de données.