SQL Pratique
CASE WHEN en SQL : syntaxe, exemples et cas pratiques
21 min de lecture

CASE WHEN en SQL : syntaxe, exemples et cas pratiques

Maîtrisez CASE WHEN en SQL : syntaxe simple et recherchée, combinaison avec GROUP BY, pivots et cas pratiques d'entretien technique.

Avatar de Thomas LeroyThomas Leroy

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;
nomsalaireniveau
Alice75000Senior
Bob45000Junior
Charlie95000Senior
Diana55000Confirmé
Eve120000Senior+

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 :

vendeurtrimestremontant
AliceQ115000
AliceQ218000
AliceQ312000
BobQ122000
BobQ219000
BobQ325000
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;
vendeurq1q2q3total
Alice15000180001200045000
Bob22000190002500066000

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éthodeAvantagesInconvénientsCas d'usage
CASE WHENSimple, auto-contenuPerformance avec beaucoup de conditionsLogique simple, < 10 conditions
Table mapping + JOINPerformance, maintenanceComplexité architectureLogique complexe, évolutive
Colonne dénormaliséePerformance maximaleSynchronisation, espace disqueRequêtes très fréquentes
Vue matérialiséePerformance + flexibilitéComplexité maintenanceCalculs 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 conditionsPerformance relativeRecommandation
1-3100%CASE WHEN optimal
4-895%CASE WHEN recommandé
9-1585%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.

Prêt à vous entraîner ?

50 exercices SQL interactifs avec éditeur en ligne, chronomètre et feedback IA.

Voir les exercices