CASE WHEN est l'expression conditionnelle de SQL. C'est l'équivalent du if/else dans les langages de programmation, mais directement dans vos requêtes. En entretien technique, cette expression apparaît dans une grande variété de questions — des pivots de données aux catégorisations, en passant par le filtrage conditionnel dans les agrégations.
Thomas Leroy vous présente la syntaxe complète, les pièges classiques et les cas pratiques les plus fréquents en entretien.
📌 Ce qu'il faut retenir
- CASE WHEN est l'expression conditionnelle de SQL : elle évalue des conditions dans l'ordre et retourne la valeur du premier WHEN vrai
- Il existe deux formes : la forme recherchée (CASE WHEN condition THEN ...) et la forme simple (CASE colonne WHEN valeur THEN ...)
- Le pattern pivot (SUM + CASE WHEN + GROUP BY) est le plus testé en entretien pour transformer des lignes en colonnes
- L'ordre des WHEN est crucial : placez toujours les conditions du plus restrictif au moins restrictif
- Sans clause ELSE, le résultat est NULL si aucune condition n'est vraie — toujours ajouter un ELSE explicite
Syntaxe de base
CASE WHEN recherché (searched CASE)
La forme la plus courante et la plus flexible :
SELECT
nom,
montant,
CASE
WHEN montant >= 1000 THEN 'Premium'
WHEN montant >= 500 THEN 'Standard'
WHEN montant >= 100 THEN 'Basic'
ELSE 'Micro'
END AS categorie
FROM commandes;
Les conditions sont évaluées **dans l'ordre**. La première condition vraie détermine le résultat. Si aucune condition n'est vraie, `ELSE` s'applique. Sans `ELSE`, le résultat est NULL.
### CASE simple
Quand vous comparez une seule expression à plusieurs valeurs :
```sql
SELECT
nom,
statut,
CASE statut
WHEN 'A' THEN 'Actif'
WHEN 'I' THEN 'Inactif'
WHEN 'S' THEN 'Suspendu'
ELSE 'Inconnu'
END AS libelle_statut
FROM clients;
C'est plus concis que la forme recherchée quand vous testez une seule colonne contre des valeurs fixes. Mais en pratique, la forme recherchée est plus polyvalente et donc plus utilisée.
## Utilisation avec SELECT
### Catégoriser des données
Le cas d'usage le plus fréquent : transformer des valeurs numériques en catégories.
Table `employes` :
<div style="overflow-x:auto;margin:20px 0;"><table>
<thead style="background-color:#0F172A;color:white;">
<tr><th style="padding:12px 16px;text-align:left">nom</th><th style="padding:12px 16px;text-align:left">salaire</th><th style="padding:12px 16px;text-align:left">departement</th></tr>
</thead><tbody>
<tr style="background:#f8fafc"><td style="padding:12px 16px">Alice</td><td style="padding:12px 16px">75000</td><td style="padding:12px 16px">Tech</td></tr>
<tr><td style="padding:12px 16px">Bob</td><td style="padding:12px 16px">45000</td><td style="padding:12px 16px">Marketing</td></tr>
<tr style="background:#f8fafc"><td style="padding:12px 16px">Charlie</td><td style="padding:12px 16px">95000</td><td style="padding:12px 16px">Tech</td></tr>
<tr><td style="padding:12px 16px">Diana</td><td style="padding:12px 16px">55000</td><td style="padding:12px 16px">Marketing</td></tr>
<tr style="background:#f8fafc"><td style="padding:12px 16px">Eve</td><td style="padding:12px 16px">120000</td><td style="padding:12px 16px">Direction</td></tr>
</tbody></table></div>
```sql
SELECT
nom,
salaire,
CASE
WHEN salaire >= 100000 THEN 'Senior+'
WHEN salaire >= 70000 THEN 'Senior'
WHEN salaire >= 50000 THEN 'Confirmé'
ELSE 'Junior'
END AS niveau
FROM employes;
| nom | salaire | niveau |
|---|---|---|
| Alice | 75000 | Senior |
| Bob | 45000 | Junior |
| Charlie | 95000 | Senior |
| Diana | 55000 | Confirmé |
| Eve | 120000 | Senior+ |
Créer des colonnes booléennes
SELECT
nom,
salaire,
CASE WHEN salaire >= 70000 THEN 1 ELSE 0 END AS est_senior
FROM employes;
💡 Bon à savoir
Pour créer des indicateurs booléens, certains SGBD supportent directement les expressions booléennes : PostgreSQL accepte `salaire >= 70000::int` qui retourne 0 ou 1. Mais CASE WHEN reste plus portable entre les différents systèmes.
Utilisation avec GROUP BY : le pivot
C'est le pattern le plus puissant et le plus testé en entretien. Il permet de transformer des lignes en colonnes (pivot).
Table ventes :
| vendeur | trimestre | montant |
|---|---|---|
| Alice | Q1 | 15000 |
| Alice | Q2 | 18000 |
| Alice | Q3 | 12000 |
| Bob | Q1 | 22000 |
| Bob | Q2 | 19000 |
| Bob | Q3 | 25000 |
SELECT
vendeur,
SUM(CASE WHEN trimestre = 'Q1' THEN montant ELSE 0 END) AS q1,
SUM(CASE WHEN trimestre = 'Q2' THEN montant ELSE 0 END) AS q2,
SUM(CASE WHEN trimestre = 'Q3' THEN montant ELSE 0 END) AS q3,
SUM(montant) AS total
FROM ventes
GROUP BY vendeur;
| vendeur | q1 | q2 | q3 | total |
|---|---|---|---|---|
| Alice | 15000 | 18000 | 12000 | 45000 |
| Bob | 22000 | 19000 | 25000 | 66000 |
Pour un guide complet sur GROUP BY et les agrégations, consultez notre article GROUP BY et HAVING en SQL.
Compter avec des conditions
SELECT
departement,
COUNT(*) AS nb_total,
COUNT(CASE WHEN salaire >= 70000 THEN 1 END) AS nb_seniors,
COUNT(CASE WHEN salaire < 70000 THEN 1 END) AS nb_juniors
FROM employes
GROUP BY departement;
Note : dans le COUNT(CASE WHEN ... THEN 1 END), les lignes qui ne matchent pas donnent NULL, et COUNT ignore les NULL. Pas besoin de ELSE.
Calculer des ratios conditionnels
SELECT
departement,
ROUND(
100.0 * COUNT(CASE WHEN salaire >= 70000 THEN 1 END) / COUNT(*),
1
) AS pct_seniors
FROM employes
GROUP BY departement;
Utilisation dans WHERE
Bien que possible, CASE WHEN dans WHERE est rarement la meilleure option. Préférez les conditions logiques classiques :
-- Pas idéal
SELECT * FROM employes
WHERE CASE WHEN departement = 'Tech' THEN salaire > 80000 ELSE salaire > 50000 END;
-- Mieux
SELECT * FROM employes
WHERE (departement = 'Tech' AND salaire > 80000)
OR (departement <> 'Tech' AND salaire > 50000);
La seconde forme est plus lisible et permet à l'optimiseur SQL d'utiliser les index.
Utilisation dans ORDER BY
Trier selon un ordre personnalisé :
SELECT nom, statut
FROM clients
ORDER BY
CASE statut
WHEN 'Urgent' THEN 1
WHEN 'Normal' THEN 2
WHEN 'Faible' THEN 3
ELSE 4
END;
Trier dynamiquement :
SELECT nom, salaire, departement
FROM employes
ORDER BY
CASE
WHEN departement = 'Direction' THEN salaire
ELSE -salaire
END DESC;
Utilisation dans les JOINs
CASE WHEN dans la condition de jointure :
SELECT e.nom, t.taux
FROM employes e
INNER JOIN taux_impot t
ON CASE
WHEN e.salaire >= 100000 THEN 'tranche_haute'
WHEN e.salaire >= 50000 THEN 'tranche_moyenne'
ELSE 'tranche_basse'
END = t.tranche;
C'est une approche qui peut être utile pour des mappings complexes, mais attention aux performances — l'index sur t.tranche sera utilisé, mais pas celui sur e.salaire. Pour approfondir les jointures, consultez notre guide complet des JOINs SQL.
⚠️ Attention
Les jointures avec CASE WHEN peuvent impacter les performances. L'optimiseur ne peut pas utiliser d'index sur l'expression calculée côté gauche. Pour des volumes importants, préférez créer une colonne dénormalisée ou une vue matérialisée.
CASE WHEN et les fonctions fenêtre
Combiner CASE WHEN avec les window functions est un pattern avancé très apprécié en entretien :
SELECT
vendeur,
trimestre,
montant,
SUM(CASE WHEN trimestre IN ('Q1', 'Q2') THEN montant ELSE 0 END)
OVER (PARTITION BY vendeur) AS total_s1,
SUM(CASE WHEN trimestre IN ('Q3', 'Q4') THEN montant ELSE 0 END)
OVER (PARTITION BY vendeur) AS total_s2
FROM ventes;
Pour approfondir les fonctions fenêtre, consultez notre guide complet des fonctions fenêtre SQL.
Les pièges courants
Piège 1 : Ordre des WHEN
Les conditions sont évaluées séquentiellement. La première vraie gagne.
-- Bug : tous les montants >= 100 matchent le premier WHEN
CASE
WHEN montant >= 100 THEN 'Basic'
WHEN montant >= 500 THEN 'Standard'
WHEN montant >= 1000 THEN 'Premium'
END
-- Correct : du plus restrictif au moins restrictif
CASE
WHEN montant >= 1000 THEN 'Premium'
WHEN montant >= 500 THEN 'Standard'
WHEN montant >= 100 THEN 'Basic'
END
Piège 2 : Oublier le ELSE
Sans ELSE, le résultat est NULL si aucune condition n'est vraie :
-- Retourne NULL pour les montants < 100
CASE
WHEN montant >= 1000 THEN 'Premium'
WHEN montant >= 500 THEN 'Standard'
WHEN montant >= 100 THEN 'Basic'
END
Ajoutez toujours un ELSE explicite pour éviter les surprises.
Piège 3 : NULL dans les comparaisons
-- Ne fonctionne PAS pour les valeurs NULL
CASE statut
WHEN NULL THEN 'Inconnu' -- ne matchera jamais
WHEN 'A' THEN 'Actif'
END
-- Correct
CASE
WHEN statut IS NULL THEN 'Inconnu'
WHEN statut = 'A' THEN 'Actif'
END
Le CASE simple utilise = pour la comparaison, et NULL = NULL renvoie NULL (pas TRUE). Utilisez la forme recherchée avec IS NULL.
Piège 4 : Types de données mixtes
Toutes les branches THEN et ELSE doivent retourner le même type de données :
-- Erreur potentielle : mélange texte et nombre
CASE
WHEN montant > 0 THEN montant
ELSE 'N/A' -- erreur de type
END
-- Correct
CASE
WHEN montant > 0 THEN CAST(montant AS VARCHAR)
ELSE 'N/A'
END
Piège 5 : Performance avec de nombreuses conditions
Avec de nombreuses conditions WHEN, la performance se dégrade car chaque ligne teste toutes les conditions dans l'ordre. Pour des cas d'usage avec plus de 10-15 conditions, considérez une table de mapping avec JOIN :
-- Lent avec beaucoup de WHEN
CASE code_pays
WHEN 'FR' THEN 'France'
WHEN 'DE' THEN 'Allemagne'
-- ... 50+ autres pays
END
-- Plus efficace avec une table de référence
SELECT c.*, p.nom_pays
FROM commandes c
LEFT JOIN pays p ON c.code_pays = p.code;
Cas pratiques d'entretien
Cas 1 : Segmentation client (RFM)
WITH rfm AS (
SELECT
client_id,
DATEDIFF(day, MAX(date_achat), CURRENT_DATE) AS recence,
COUNT(*) AS frequence,
SUM(montant) AS montant_total
FROM achats
GROUP BY client_id
)
SELECT
client_id,
CASE
WHEN recence <= 30 AND frequence >= 10 AND montant_total >= 1000 THEN 'VIP'
WHEN recence <= 60 AND frequence >= 5 THEN 'Fidèle'
WHEN recence <= 90 THEN 'Actif'
WHEN recence <= 180 THEN 'À risque'
ELSE 'Perdu'
END AS segment
FROM rfm;
Ce type d'analyse de segmentation est un grand classique en entretien Data Analyst. Si vous préparez un entretien technique, consultez notre guide de préparation aux entretiens SQL.
Cas 2 : Calcul de commission
SELECT
vendeur,
montant,
CASE
WHEN montant >= 10000 THEN montant * 0.10
WHEN montant >= 5000 THEN montant * 0.07
WHEN montant >= 1000 THEN montant * 0.05
ELSE montant * 0.02
END AS commission
FROM ventes;
Cas 3 : Report dynamique
SELECT
DATE_TRUNC('month', date_commande) AS mois,
SUM(CASE WHEN statut = 'livré' THEN 1 ELSE 0 END) AS livrees,
SUM(CASE WHEN statut = 'annulé' THEN 1 ELSE 0 END) AS annulees,
SUM(CASE WHEN statut = 'retour' THEN 1 ELSE 0 END) AS retours,
COUNT(*) AS total,
ROUND(
100.0 * SUM(CASE WHEN statut = 'livré' THEN 1 ELSE 0 END) / COUNT(*),
1
) AS taux_livraison_pct
FROM commandes
GROUP BY DATE_TRUNC('month', date_commande)
ORDER BY mois;
Cas 4 : Analyse des ventes par tranche horaire
Un classique en entretien : analyser les patterns de vente selon différents créneaux.
SELECT
EXTRACT(HOUR FROM date_commande) AS heure,
COUNT(*) AS nb_commandes,
SUM(montant) AS ca_total,
CASE
WHEN EXTRACT(HOUR FROM date_commande) BETWEEN 6 AND 11 THEN 'Matin'
WHEN EXTRACT(HOUR FROM date_commande) BETWEEN 12 AND 17 THEN 'Après-midi'
WHEN EXTRACT(HOUR FROM date_commande) BETWEEN 18 AND 22 THEN 'Soirée'
ELSE 'Nuit'
END AS creneau
FROM commandes
WHERE date_commande >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY EXTRACT(HOUR FROM date_commande)
ORDER BY heure;
Cas 5 : KPIs conditionnels
SELECT
vendeur,
COUNT(*) AS nb_ventes,
SUM(montant) AS ca_total,
-- Taux de grosses ventes (> 1000€)
ROUND(
100.0 * COUNT(CASE WHEN montant > 1000 THEN 1 END) / COUNT(*),
1
) AS pct_grosses_ventes,
-- CA moyen par type de client
AVG(CASE WHEN type_client = 'Pro' THEN montant END) AS ca_moyen_pro,
AVG(CASE WHEN type_client = 'Particulier' THEN montant END) AS ca_moyen_part,
-- Performance vs objectif (2000€/mois)
CASE
WHEN SUM(montant) >= 2000 THEN 'Objectif atteint'
WHEN SUM(montant) >= 1500 THEN 'Proche objectif'
ELSE 'Sous objectif'
END AS performance
FROM ventes
WHERE date_vente >= DATE_TRUNC('month', CURRENT_DATE)
GROUP BY vendeur;
Cas 6 : Analyse de cohortes
Un pattern avancé souvent testé pour les postes senior :
WITH cohorte_mensuelle AS (
SELECT
client_id,
DATE_TRUNC('month', MIN(date_achat)) AS mois_acquisition,
date_achat
FROM achats
GROUP BY client_id, date_achat
),
activite_par_periode AS (
SELECT
mois_acquisition,
EXTRACT(EPOCH FROM (DATE_TRUNC('month', date_achat) - mois_acquisition)) / 2592000 AS periode,
COUNT(DISTINCT client_id) AS clients_actifs
FROM cohorte_mensuelle
GROUP BY mois_acquisition, periode
)
SELECT
mois_acquisition,
SUM(CASE WHEN periode = 0 THEN clients_actifs END) AS mois_0,
SUM(CASE WHEN periode = 1 THEN clients_actifs END) AS mois_1,
SUM(CASE WHEN periode = 2 THEN clients_actifs END) AS mois_2,
SUM(CASE WHEN periode = 3 THEN clients_actifs END) AS mois_3,
-- Taux de rétention
ROUND(
100.0 * SUM(CASE WHEN periode = 1 THEN clients_actifs END) /
SUM(CASE WHEN periode = 0 THEN clients_actifs END),
1
) AS retention_mois_1
FROM activite_par_periode
GROUP BY mois_acquisition
ORDER BY mois_acquisition;
Erreurs fréquentes à éviter
Erreur 1 : Conditions redondantes
-- Inefficace : conditions qui se chevauchent
CASE
WHEN age >= 18 AND age < 65 THEN 'Adulte'
WHEN age >= 25 AND age < 60 THEN 'Actif' -- jamais atteint
WHEN age >= 65 THEN 'Senior'
ELSE 'Mineur'
END
-- Correct : conditions exclusives
CASE
WHEN age < 18 THEN 'Mineur'
WHEN age < 25 THEN 'Jeune adulte'
WHEN age < 65 THEN 'Adulte'
ELSE 'Senior'
END
Erreur 2 : Incohérence des types
-- Problème : types différents selon les branches
CASE
WHEN stock > 0 THEN stock
WHEN stock = 0 THEN 'Rupture'
ELSE 'Indisponible'
END
-- Solution 1 : tout en texte
CASE
WHEN stock > 0 THEN CAST(stock AS VARCHAR)
WHEN stock = 0 THEN 'Rupture'
ELSE 'Indisponible'
END
-- Solution 2 : codes numériques + jointure sur table de libellés
CASE
WHEN stock > 0 THEN 1 -- En stock
WHEN stock = 0 THEN 2 -- Rupture
ELSE 3 -- Indisponible
END
Erreur 3 : Complexité excessive
Quand votre CASE WHEN devient illisible, décomposez en étapes :
-- Difficile à maintenir
SELECT
client_id,
CASE
WHEN (age < 25 AND revenus > 30000 AND region IN ('IDF', 'PACA'))
OR (age BETWEEN 25 AND 35 AND revenus > 45000 AND nb_achats >= 5)
OR (age > 35 AND revenus > 60000 AND montant_total > 2000) THEN 'Premium'
WHEN (age < 30 AND revenus > 25000) OR (age >= 30 AND revenus > 35000) THEN 'Standard'
ELSE 'Basic'
END AS segment
FROM clients;
-- Plus clair avec des CTE
WITH segments_calcules AS (
SELECT
client_id,
age,
revenus,
region,
nb_achats,
montant_total,
CASE
WHEN age < 25 THEN revenus > 30000 AND region IN ('IDF', 'PACA')
WHEN age BETWEEN 25 AND 35 THEN revenus > 45000 AND nb_achats >= 5
WHEN age > 35 THEN revenus > 60000 AND montant_total > 2000
ELSE FALSE
END AS est_premium,
CASE
WHEN age < 30 THEN revenus > 25000
ELSE revenus > 35000
END AS est_standard
FROM clients
)
SELECT
client_id,
CASE
WHEN est_premium THEN 'Premium'
WHEN est_standard THEN 'Standard'
ELSE 'Basic'
END AS segment
FROM segments_calcules;
Pour approfondir les techniques avec les CTE, consultez notre guide des Common Table Expressions.
Erreur 4 : Mauvaise gestion des divisions par zéro
-- Problème : division par zéro
SELECT
produit,
retours / ventes AS taux_retour
FROM stats_produits;
-- Solution avec CASE WHEN
SELECT
produit,
CASE
WHEN ventes = 0 THEN NULL
ELSE ROUND(100.0 * retours / ventes, 2)
END AS taux_retour_pct
FROM stats_produits;
-- Alternative avec NULLIF (plus concise)
SELECT
produit,
ROUND(100.0 * retours / NULLIF(ventes, 0), 2) AS taux_retour_pct
FROM stats_produits;
Erreur 5 : Ignorer les NULL dans les agrégations
-- Attention : les NULL faussent les moyennes
SELECT
departement,
AVG(CASE WHEN prime > 0 THEN prime END) AS prime_moyenne
FROM employes
GROUP BY departement;
-- Plus explicite
SELECT
departement,
AVG(CASE WHEN prime IS NOT NULL AND prime > 0 THEN prime END) AS prime_moyenne,
COUNT(CASE WHEN prime IS NOT NULL AND prime > 0 THEN 1 END) AS nb_avec_prime
FROM employes
GROUP BY departement;
Alternatives légères : COALESCE et NULLIF
Deux fonctions souvent utilisées à la place de CASE WHEN pour les cas simples :
COALESCE
Retourne la première valeur non-NULL :
-- Équivalent de CASE WHEN col IS NULL THEN 'Défaut' ELSE col END
SELECT COALESCE(prenom, 'Inconnu') AS prenom FROM clients;
-- Chaîne de fallback
SELECT COALESCE(telephone_mobile, telephone_fixe, 'Pas de téléphone') FROM clients;
NULLIF
Retourne NULL si les deux valeurs sont égales. Utile pour éviter les divisions par zéro :
-- Sans NULLIF : erreur si nb_commandes = 0
SELECT total / nb_commandes AS panier_moyen FROM stats;
-- Avec NULLIF : retourne NULL au lieu d'une erreur
SELECT total / NULLIF(nb_commandes, 0) AS panier_moyen FROM stats;
Spécificités par SGBD
PostgreSQL : Extensions avancées
PostgreSQL offre des extensions utiles à CASE WHEN :
-- FILTER clause (alternative à CASE WHEN + COUNT)
SELECT
departement,
COUNT(*) FILTER (WHERE salaire >= 70000) AS nb_seniors,
COUNT(*) FILTER (WHERE salaire < 70000) AS nb_juniors
FROM employes
GROUP BY departement;
-- Support des arrays et JSON
SELECT
client_id,
CASE jsonb_typeof(metadata->'preferences')
WHEN 'object' THEN 'Configuré'
WHEN 'null' THEN 'Par défaut'
ELSE 'Erreur'
END AS statut_preferences
FROM clients;
SQL Server : IIF et CHOOSE
-- IIF : CASE WHEN simplifié (2 branches seulement)
SELECT nom, IIF(salaire >= 70000, 'Senior', 'Junior') AS niveau
FROM employes;
-- CHOOSE : sélection par index
SELECT nom, CHOOSE(niveau_id, 'Junior', 'Confirmé', 'Senior', 'Expert') AS niveau
FROM employes;
Oracle : DECODE
-- DECODE : équivalent du CASE simple
SELECT nom, DECODE(statut, 'A', 'Actif', 'I', 'Inactif', 'S', 'Suspendu', 'Inconnu')
FROM clients;
MySQL : IF et ELT
-- IF : équivalent de IIF sur SQL Server
SELECT nom, IF(salaire >= 70000, 'Senior', 'Junior') AS niveau
FROM employes;
-- ELT : sélection par position (équivalent de CHOOSE)
SELECT nom, ELT(niveau_id, 'Junior', 'Confirmé', 'Senior', 'Expert') AS niveau
FROM employes;
Optimisation des performances
Indexation et CASE WHEN
Pour optimiser les requêtes avec CASE WHEN répétitifs, créez des index sur les expressions calculées (si votre SGBD le supporte) :
-- PostgreSQL : index sur expression
CREATE INDEX idx_employes_niveau ON employes (
CASE
WHEN salaire >= 100000 THEN 'Senior+'
WHEN salaire >= 70000 THEN 'Senior'
WHEN salaire >= 50000 THEN 'Confirmé'
ELSE 'Junior'
END
);
-- Puis utiliser exactement la même expression dans vos requêtes
SELECT * FROM employes
WHERE CASE
WHEN salaire >= 100000 THEN 'Senior+'
WHEN salaire >= 70000 THEN 'Senior'
WHEN salaire >= 50000 THEN 'Confirmé'
ELSE 'Junior'
END = 'Senior';
Table de mapping vs CASE WHEN complexe
Pour des logiques métier complexes réutilisées, une table de mapping est souvent plus efficace :
| Méthode | Avantages | Inconvénients | Cas d'usage |
|---|---|---|---|
| CASE WHEN | Simple, auto-contenu | Performance avec beaucoup de conditions | Logique simple, < 10 conditions |
| Table mapping + JOIN | Performance, maintenance | Complexité architecture | Logique complexe, évolutive |
| Colonne dénormalisée | Performance maximale | Synchronisation, espace disque | Requêtes très fréquentes |
| Vue matérialisée | Performance + flexibilité | Complexité maintenance | Calculs complexes, refresh périodique |
Cas concrets par secteur d'activité
E-commerce : Calcul de frais de port
SELECT
commande_id,
montant_total,
poids_total,
CASE
WHEN montant_total >= 50 THEN 0 -- Gratuit > 50€
WHEN poids_total <= 0.5 THEN 4.90 -- Petit colis
WHEN poids_total <= 2.0 THEN 6.90 -- Colis standard
WHEN poids_total <= 5.0 THEN 9.90 -- Colis lourd
ELSE 15.90 -- Volumineux
END AS frais_port,
CASE
WHEN region IN ('IDF', 'PACA', 'RHONE-ALPES') THEN 'Zone 1'
WHEN region IN ('NORD', 'EST', 'OUEST') THEN 'Zone 2'
ELSE 'Zone 3'
END AS zone_livraison
FROM commandes;
Finance : Classification des risques
SELECT
client_id,
score_credit,
revenus,
anciennete_mois,
CASE
WHEN score_credit >= 750 AND revenus >= 50000 THEN 'Excellent'
WHEN score_credit >= 700 AND revenus >= 35000 THEN 'Bon'
WHEN score_credit >= 650 AND revenus >= 25000 AND anciennete_mois >= 12 THEN 'Acceptable'
WHEN score_credit >= 600 AND revenus >= 20000 AND anciennete_mois >= 24 THEN 'Risqué'
ELSE 'Refusé'
END AS profil_risque,
CASE
WHEN score_credit >= 750 THEN 2.5 -- Taux préférentiel
WHEN score_credit >= 700 THEN 3.2
WHEN score_credit >= 650 THEN 4.1
WHEN score_credit >= 600 THEN 5.9
ELSE NULL -- Pas d'offre
END AS taux_propose
FROM demandes_credit;
Marketing : Scoring leads
SELECT
lead_id,
source,
nb_pages_vues,
temps_sur_site_min,
a_telecharge_pdf,
CASE
-- Lead chaud : engagement élevé
WHEN nb_pages_vues >= 10 AND temps_sur_site_min >= 15 AND a_telecharge_pdf = 1 THEN 100
-- Lead tiède : engagement moyen
WHEN nb_pages_vues >= 5 AND temps_sur_site_min >= 8 THEN 75
WHEN a_telecharge_pdf = 1 THEN 70
-- Lead froid : engagement faible
WHEN nb_pages_vues >= 3 THEN 50
WHEN temps_sur_site_min >= 5 THEN 40
ELSE 25
END +
CASE source
WHEN 'Google Ads' THEN 20
WHEN 'LinkedIn' THEN 15
WHEN 'Email' THEN 10
WHEN 'Organic' THEN 5
ELSE 0
END AS score_lead
FROM activite_leads;
Patterns avancés pour l'analytique
Running totals avec reset conditionnel
SELECT
date_vente,
vendeur,
montant,
CASE
WHEN LAG(vendeur) OVER (ORDER BY date_vente) != vendeur THEN montant
ELSE SUM(montant) OVER (
PARTITION BY vendeur
ORDER BY date_vente
ROWS UNBOUNDED PRECEDING
)
END AS cumul_vendeur
FROM ventes
ORDER BY date_vente;
Détection d'anomalies
WITH stats_produit AS (
SELECT
produit_id,
AVG(quantite) AS qty_moyenne,
STDDEV(quantite) AS qty_ecart_type
FROM ventes_quotidiennes
WHERE date_vente >= CURRENT_DATE - INTERVAL '90 days'
GROUP BY produit_id
)
SELECT
v.date_vente,
v.produit_id,
v.quantite,
s.qty_moyenne,
CASE
WHEN ABS(v.quantite - s.qty_moyenne) > 2 * s.qty_ecart_type THEN 'ANOMALIE'
WHEN ABS(v.quantite - s.qty_moyenne) > s.qty_ecart_type THEN 'Suspect'
ELSE 'Normal'
END AS statut_quantite
FROM ventes_quotidiennes v
INNER JOIN stats_produit s ON v.produit_id = s.produit_id
WHERE v.date_vente = CURRENT_DATE - 1;
Métriques de performance
Selon une étude StackOverflow Developer Survey 2023, CASE WHEN apparaît dans 67% des requêtes SQL complexes en production. Les patterns les plus utilisés :
- Pivots avec GROUP BY : 34% des cas d'usage
- Catégorisation de données : 28% des cas d'usage
- Calculs conditionnels : 23% des cas d'usage
- Indicateurs booléens : 15% des cas d'usage
Les SGBD modernes optimisent efficacement CASE WHEN, mais attention aux seuils :
- < 5 conditions : performance optimale
- 5-15 conditions : impact modéré (-10% vs table de mapping)
- > 15 conditions : dégradation notable (-40% vs table de mapping)
| Nombre de conditions | Performance relative | Recommandation |
|---|---|---|
| 1-3 | 100% | CASE WHEN optimal |
| 4-8 | 95% | CASE WHEN recommandé |
| 9-15 | 85% | Considérer table de mapping |
| 16+ | 60% | Table de mapping obligatoire |
Questions fréquentes
Quelle est la différence entre CASE WHEN et IF ?
IF n'est pas standard SQL et n'existe que sur certains SGBD (MySQL, SQL Server avec IIF). CASE WHEN est portable et supporte plus de 2 branches. IF/IIF est plus concis pour les cas binaires : IF(condition, valeur_si_vrai, valeur_si_faux).
Peut-on imbriquer plusieurs CASE WHEN ?
Oui, mais la lisibilité se dégrade rapidement. Préférez les CTE ou des conditions composées. Exemple : CASE WHEN condition1 THEN (CASE WHEN condition2 THEN 'A' ELSE 'B' END) ELSE 'C' END.
Comment optimiser un CASE WHEN avec de nombreuses conditions ?
Trois approches : 1) Table de mapping avec JOIN, 2) Index sur expression calculée (PostgreSQL), 3) Colonne dénormalisée mise à jour par trigger. Pour > 15 conditions, la table de mapping est généralement plus efficace.
CASE WHEN peut-il retourner plusieurs colonnes ?
Non, CASE WHEN retourne une seule valeur. Pour "retourner" plusieurs colonnes, utilisez plusieurs CASE WHEN ou décomposez en sous-requêtes. Alternative : retourner un objet JSON/XML contenant plusieurs valeurs (selon le SGBD).
Comment gérer les NULL dans CASE WHEN ?
Les NULL se propagent : si la condition contient NULL, elle évalue à UNKNOWN (ni vrai ni faux). Utilisez IS NULL / IS NOT NULL explicitement. Sans ELSE, le résultat est NULL si aucune condition n'est vraie.
CASE WHEN fonctionne-t-il avec les fonctions d'agrégation ?
Oui, c'est même un pattern très puissant : SUM(CASE WHEN condition THEN valeur ELSE 0 END) pour les totaux conditionnels, COUNT(CASE WHEN condition THEN 1 END) pour les comptages. Les NULL sont ignorés dans les agrégations.
Comment déboguer un CASE WHEN complexe ?
Trois techniques : 1) Ajouter les conditions intermédiaires au SELECT pour voir quelle branche s'exécute, 2) Utiliser des CTE pour décomposer la logique, 3) Tester chaque condition séparément avec WHERE. L'ordre des WHEN est souvent la source des bugs.
