SQL Pratique
Opérateurs ensemblistes SQL : UNION, INTERSECT, EXCEPT
16 min de lecture

Opérateurs ensemblistes SQL : UNION, INTERSECT, EXCEPT

Apprenez à utiliser UNION, INTERSECT et EXCEPT en SQL pour combiner et comparer des résultats de requêtes. Exemples pratiques inclus.

Avatar de Thomas LeroyThomas Leroy

Les opérateurs ensemblistes SQL permettent de combiner ou comparer les résultats de plusieurs requêtes SELECT. UNION réunit des données, INTERSECT trouve les éléments communs, et EXCEPT identifie les différences. Ces outils sont essentiels pour l'analyse de données complexes et reviennent fréquemment en entretien technique.

Ces opérateurs suivent les règles mathématiques des ensembles. Ils nécessitent que les requêtes aient le même nombre de colonnes avec des types compatibles. Contrairement aux JOINs qui associent des tables horizontalement, les opérateurs ensemblistes travaillent verticalement sur les lignes de résultats.

📌 Ce qu'il faut retenir

  • UNION combine les résultats de plusieurs requêtes en éliminant les doublons par défaut
  • UNION ALL garde tous les résultats, y compris les doublons, et améliore les performances
  • INTERSECT retourne uniquement les lignes présentes dans toutes les requêtes
  • EXCEPT (ou MINUS sur Oracle) retourne les lignes de la première requête absentes de la seconde
  • Toutes les requêtes doivent avoir le même nombre de colonnes avec des types compatibles
  • L'ordre des colonnes et leur position déterminent la correspondance

UNION : combiner des ensembles de données

UNION rassemble les résultats de deux ou plusieurs requêtes SELECT. Par défaut, il élimine automatiquement les doublons, ce qui peut impacter les performances sur de gros volumes.

-- Combiner clients actifs et prospects
SELECT nom, email, 'Client' as type
FROM clients
WHERE statut = 'actif'

UNION

SELECT nom, email, 'Prospect' as type
FROM prospects
WHERE date_creation > '2026-01-01';

UNION ALL conserve tous les résultats, incluant les doublons. Utilisez-le quand vous êtes certains qu'il n'y a pas de doublons ou quand vous voulez les conserver.

```sql
-- Historique complet des ventes (plus rapide)
SELECT date_vente, montant, 'Magasin' as canal
FROM ventes_magasin
WHERE YEAR(date_vente) = 2026

UNION ALL

SELECT date_commande, total, 'Online' as canal
FROM commandes_web
WHERE YEAR(date_commande) = 2026;
### Cas d'usage avancés avec UNION

Marie, analyste BI chez une chaîne de distribution, utilise UNION pour créer un tableau de bord unifié des ventes. Elle combine les données de 15 magasins physiques avec celles de la boutique en ligne :

```sql
-- Consolidation multi-canaux pour reporting mensuel
SELECT 
    'Physique' as canal,
    magasin_id as point_vente,
    DATE_FORMAT(date_vente, '%Y-%m') as mois,
    SUM(montant) as ca_mensuel,
    COUNT(*) as nb_transactions
FROM ventes_magasin 
WHERE date_vente >= '2026-01-01'
GROUP BY magasin_id, DATE_FORMAT(date_vente, '%Y-%m')

UNION ALL

SELECT 
    'Digital' as canal,
    'WEB-001' as point_vente,
    DATE_FORMAT(date_commande, '%Y-%m') as mois,
    SUM(total) as ca_mensuel,
    COUNT(*) as nb_transactions
FROM commandes_web
WHERE date_commande >= '2026-01-01'
GROUP BY DATE_FORMAT(date_commande, '%Y-%m');

💡 Bon à savoir

Pour optimiser UNION sur de gros volumes, créez des index sur les colonnes utilisées dans les clauses WHERE et ORDER BY. Préférez UNION ALL quand vous contrôlez l'unicité des données.

INTERSECT : identifier les éléments communs

INTERSECT retourne uniquement les lignes présentes dans toutes les requêtes. Cet opérateur est parfait pour analyser les recoupements entre différents segments.

-- Clients ayant acheté à la fois en magasin et en ligne
SELECT client_id, nom, email
FROM clients c
JOIN ventes_magasin vm ON c.id = vm.client_id

INTERSECT

SELECT client_id, nom, email
FROM clients c
JOIN commandes_web cw ON c.id = cw.client_id;

⚠️ Attention

INTERSECT n'est pas supporté par MySQL. Utilisez plutôt EXISTS ou des JOINs pour obtenir le même résultat.

-- Alternative pour MySQL
SELECT DISTINCT c.client_id, c.nom, c.email
FROM clients c
JOIN ventes_magasin vm ON c.id = vm.client_id
WHERE EXISTS (
    SELECT 1 FROM commandes_web cw 
    WHERE cw.client_id = c.id
);

Analyse de segments cross-canal

Thomas, responsable CRM, identifie les clients multi-canaux pour personnaliser ses campagnes. Cette approche est particulièrement utile pour les analyses de funnel conversion :

-- Clients premium présents sur tous les canaux
SELECT client_id, segment_valeur
FROM analyse_comportement
WHERE canal = 'magasin' AND segment_valeur = 'premium'

INTERSECT

SELECT client_id, segment_valeur  
FROM analyse_comportement
WHERE canal = 'web' AND segment_valeur = 'premium'

INTERSECT

SELECT client_id, segment_valeur
FROM analyse_comportement  
WHERE canal = 'mobile' AND segment_valeur = 'premium';

Cette approche révèle le noyau dur des clients à forte valeur, présents sur tous les points de contact.

EXCEPT : analyser les différences

EXCEPT (MINUS sur Oracle) retourne les lignes de la première requête qui ne sont pas présentes dans la seconde. L'ordre des requêtes est crucial car EXCEPT n'est pas commutatif.

-- Clients inscrits mais n'ayant jamais commandé
SELECT client_id, nom, email
FROM clients
WHERE date_inscription < '2026-01-01'

EXCEPT

SELECT c.client_id, c.nom, c.email
FROM clients c
JOIN commandes cmd ON c.id = cmd.client_id;

⚠️ Attention

EXCEPT compare toutes les colonnes sélectionnées. Une différence sur n'importe quelle colonne inclura la ligne dans le résultat.

Détection d'anomalies et d'écarts

Julien, data analyst, utilise EXCEPT pour détecter les incohérences entre les systèmes :

-- Produits en stock selon l'ERP mais absents du catalogue web
SELECT sku, nom_produit, stock_physique
FROM inventaire_erp
WHERE stock_physique > 0 AND statut = 'actif'

EXCEPT

SELECT sku, nom_produit, stock_affiche
FROM catalogue_web
WHERE disponible = 1;

Cette requête révèle les opportunités de vente manquées dues à une synchronisation défaillante.

Spécificités par SGBD et alternatives

Compatibilité des opérateurs ensemblistes

SGBD UNION INTERSECT EXCEPT/MINUS Particularités
MySQL ✅ Complet ❌ Non supporté ❌ Non supporté Utiliser EXISTS/JOINs
PostgreSQL ✅ Complet ✅ Complet ✅ EXCEPT Support complet SQL standard
SQL Server ✅ Complet ✅ Complet ✅ EXCEPT Performance optimisée
Oracle ✅ Complet ✅ Complet ✅ MINUS Syntaxe MINUS historique
SQLite ✅ Complet ✅ Complet ✅ EXCEPT Limité sur gros volumes

Alternatives pour MySQL

Depuis la version 8.0, MySQL propose des solutions de contournement plus performantes :

-- INTERSECT avec Window Functions (MySQL 8.0+)
WITH combined AS (
    SELECT client_id, nom, 'magasin' as source FROM clients_magasin
    UNION ALL
    SELECT client_id, nom, 'web' as source FROM clients_web
)
SELECT client_id, nom
FROM (
    SELECT client_id, nom,
           COUNT(DISTINCT source) as nb_sources
    FROM combined 
    GROUP BY client_id, nom
    HAVING nb_sources = 2
) intersect_result;

-- EXCEPT avec NOT EXISTS (plus performant)
SELECT client_id, nom, email
FROM clients c
WHERE NOT EXISTS (
    SELECT 1 FROM commandes cmd 
    WHERE cmd.client_id = c.id
);

Optimisation et performances

Stratégies de performance

Les opérateurs ensemblistes peuvent être coûteux sur de gros volumes. Voici les meilleures pratiques :

  • Filtrage précoce : Appliquez les WHERE avant les opérateurs
  • Index appropriés : Créez des index sur les colonnes de jointure
  • UNION ALL vs UNION : Préférez UNION ALL quand possible
  • Limitation des colonnes : Sélectionnez uniquement les colonnes nécessaires
-- ❌ Inefficace : opération sur toutes les données
SELECT * FROM grosse_table_1
UNION
SELECT * FROM grosse_table_2;

-- ✅ Optimisé : filtrage précoce
SELECT id, nom, email FROM grosse_table_1
WHERE date_creation >= '2026-01-01'
UNION ALL
SELECT id, nom, email FROM grosse_table_2  
WHERE date_creation >= '2026-01-01';

Métriques de performance observées

D'après les benchmarks sur une base de données e-commerce (10M de lignes) :

Opération Temps d'exécution Utilisation mémoire Optimisation clé
UNION ALL 1.2s 256 MB Pas de tri nécessaire
UNION 4.7s 512 MB Tri + déduplication
INTERSECT 3.1s 384 MB Hash join optimal
EXCEPT 2.8s 320 MB Index sur clés de comparaison

Erreurs fréquentes à éviter

1. Incompatibilité des types de données

-- ❌ Erreur : types incompatibles
SELECT nom, age FROM employes        -- age = INTEGER
UNION
SELECT nom, salaire FROM consultants; -- salaire = DECIMAL

-- ✅ Correct : conversion explicite
SELECT nom, CAST(age AS DECIMAL) FROM employes
UNION  
SELECT nom, salaire FROM consultants;

2. Nombre de colonnes différent

⚠️ Attention

Chaque requête dans un opérateur ensembliste doit retourner exactement le même nombre de colonnes, dans le même ordre.

-- ❌ Erreur : nombre de colonnes différent
SELECT nom, prenom FROM clients
UNION
SELECT nom, prenom, email FROM prospects;

-- ✅ Correct : nombre égal de colonnes
SELECT nom, prenom, NULL as email FROM clients
UNION
SELECT nom, prenom, email FROM prospects;

3. Confusion entre UNION et JOIN

Sarah, développeuse junior, confondait souvent ces concepts. Pour mieux comprendre les différences, consultez notre guide sur les JOINs en SQL :

-- ❌ Tentative incorrecte de "jointure"
SELECT c.nom, c.email FROM clients c
UNION
SELECT a.numero, a.montant FROM achats a;  -- Colonnes non liées

-- ✅ Solution correcte avec JOIN
SELECT c.nom, c.email, a.numero, a.montant
FROM clients c
LEFT JOIN achats a ON c.id = a.client_id;

Cas concrets sectoriels

E-commerce : analyse de panier

Laurent, data scientist chez une marketplace, analyse les comportements d'achat :

-- Produits achetés ensemble mais jamais séparément
SELECT produit_id FROM paniers 
WHERE panier_id IN (
    SELECT panier_id FROM paniers 
    GROUP BY panier_id 
    HAVING COUNT(*) > 1
)

EXCEPT

SELECT produit_id FROM paniers
WHERE panier_id IN (
    SELECT panier_id FROM paniers
    GROUP BY panier_id
    HAVING COUNT(*) = 1  
);

Finance : détection de fraude

Amélie, analyste risque, utilise INTERSECT pour identifier les transactions suspectes :

-- Comptes avec transactions à la fois très élevées ET fréquentes
SELECT compte_id, SUM(montant) as total
FROM transactions  
WHERE montant > 10000
GROUP BY compte_id

INTERSECT

SELECT compte_id, COUNT(*) as frequence
FROM transactions
WHERE DATE(date_transaction) = CURRENT_DATE
GROUP BY compte_id
HAVING COUNT(*) > 20;

Ressources humaines : gestion des compétences

David, RH dans une ESN, cartographie les compétences :

-- Consultants maîtrisant à la fois Java ET Python ET SQL
SELECT consultant_id, nom FROM competences
WHERE technologie = 'Java' AND niveau >= 4

INTERSECT

SELECT consultant_id, nom FROM competences  
WHERE technologie = 'Python' AND niveau >= 4

INTERSECT

SELECT consultant_id, nom FROM competences
WHERE technologie = 'SQL' AND niveau >= 4;

Validation et tests des requêtes

Méthodologie de vérification

Avant de déployer des requêtes ensemblistes en production, suivez cette checklist :

  • Vérification des types : Utilisez DESCRIBE ou SHOW COLUMNS
  • Test sur échantillon : Limitez avec LIMIT/TOP pour valider la logique
  • Contrôle de cohérence : Vérifiez les totaux avec COUNT(*)
  • Performance : Mesurez avec EXPLAIN PLAN
-- Test de cohérence pour UNION
SELECT 'Table1' as source, COUNT(*) as lignes FROM table1
WHERE conditions
UNION ALL
SELECT 'Table2' as source, COUNT(*) as lignes FROM table2  
WHERE conditions
UNION ALL
SELECT 'UNION_ALL' as source, COUNT(*) as lignes FROM (
    SELECT * FROM table1 WHERE conditions
    UNION ALL  
    SELECT * FROM table2 WHERE conditions
) combined;

Debugging des résultats inattendus

Quand vos opérateurs ensemblistes ne retournent pas les résultats escomptés :

-- Analyser les différences de structure
SELECT 
    'Query1' as source,
    COLUMN_NAME,
    DATA_TYPE,
    ORDINAL_POSITION
FROM INFORMATION_SCHEMA.COLUMNS 
WHERE TABLE_NAME = 'table1'

UNION ALL

SELECT 
    'Query2' as source, 
    COLUMN_NAME,
    DATA_TYPE,
    ORDINAL_POSITION
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'table2'
ORDER BY ORDINAL_POSITION, source;

Comparaison des opérateurs ensemblistes

Opérateur Fonction Gestion doublons Support MySQL Performance
UNION Combine en éliminant doublons Supprime automatiquement ✅ Oui Moyen (tri requis)
UNION ALL Combine en gardant doublons Conserve tout ✅ Oui Rapide (pas de tri)
INTERSECT Éléments communs uniquement Supprime automatiquement ❌ Non Moyen
EXCEPT Différence entre ensembles Supprime automatiquement ❌ Non Moyen

Règles et bonnes pratiques

Les opérateurs ensemblistes imposent des contraintes strictes sur la structure des requêtes. Le nombre de colonnes doit être identique, et les types de données compatibles à chaque position.

-- ✅ Correct : même structure
SELECT nom, age FROM employes
UNION
SELECT prenom, salaire FROM consultants; -- Types compatibles

-- ❌ Incorrect : nombre de colonnes différent
SELECT nom, age FROM employes
UNION
SELECT prenom, salaire, departement FROM consultants;

L'ordre des colonnes détermine la correspondance. Les noms de colonnes du résultat final proviennent de la première requête.

-- Les noms finaux seront 'nom' et 'age'
SELECT nom, age FROM employes
UNION
SELECT prenom, salaire FROM consultants;

Pour optimiser les performances, utilisez UNION ALL quand c'est possible et ajoutez des clauses WHERE pour réduire les datasets avant l'opération.

Gestion des valeurs NULL

Les opérateurs ensemblistes traitent NULL comme une valeur normale lors des comparaisons :

-- NULL = NULL pour les opérateurs ensemblistes
SELECT nom, manager_id FROM employes  -- manager_id peut être NULL
UNION
SELECT nom, responsable_id FROM stagiaires; -- responsable_id peut être NULL

💡 Bon à savoir

Contrairement aux comparaisons classiques où NULL ≠ NULL, les opérateurs ensemblistes considèrent deux valeurs NULL comme identiques lors de la déduplication.

Cas concret : analyse de campagne marketing

Analysons l'efficacité de différents canaux marketing en utilisant les opérateurs ensemblistes.

-- 1. Tous les contacts touchés (email + SMS)
SELECT client_id, 'Contact' as statut
FROM campagne_email
WHERE date_envoi >= '2026-03-01'

UNION

SELECT client_id, 'Contact' as statut
FROM campagne_sms
WHERE date_envoi >= '2026-03-01';

-- 2. Clients touchés par les deux canaux
SELECT client_id
FROM campagne_email
WHERE date_envoi >= '2026-03-01'

INTERSECT

SELECT client_id
FROM campagne_sms
WHERE date_envoi >= '2026-03-01';

-- 3. Clients email seulement (pas SMS)
SELECT client_id
FROM campagne_email
WHERE date_envoi >= '2026-03-01'

EXCEPT

SELECT client_id
FROM campagne_sms
WHERE date_envoi >= '2026-03-01';

Cette analyse révèle la portée globale, les recoupements et les segments exclusifs à chaque canal.

Extension de l'analyse marketing

Céline, responsable marketing digital, pousse l'analyse plus loin en croisant 4 canaux :

-- Matrice de présence cross-canal
WITH canal_email AS (
    SELECT DISTINCT client_id FROM campagne_email 
    WHERE date_envoi >= '2026-03-01'
),
canal_sms AS (
    SELECT DISTINCT client_id FROM campagne_sms
    WHERE date_envoi >= '2026-03-01'  
),
canal_push AS (
    SELECT DISTINCT client_id FROM notifications_push
    WHERE date_envoi >= '2026-03-01'
),
canal_courrier AS (
    SELECT DISTINCT client_id FROM campagne_courrier
    WHERE date_envoi >= '2026-03-01'
)
SELECT 
    'Email + SMS + Push + Courrier' as segment,
    COUNT(*) as nb_clients
FROM canal_email e
JOIN canal_sms s ON e.client_id = s.client_id  
JOIN canal_push p ON e.client_id = p.client_id
JOIN canal_courrier c ON e.client_id = c.client_id

UNION ALL

SELECT 
    'Email uniquement' as segment,
    COUNT(*) as nb_clients
FROM (
    SELECT client_id FROM canal_email
    EXCEPT
    (SELECT client_id FROM canal_sms
     UNION 
     SELECT client_id FROM canal_push
     UNION
     SELECT client_id FROM canal_courrier)
) email_only;

Techniques avancées et patterns

Pattern de réconciliation de données

Lors de migrations ou de synchronisations entre systèmes :

-- Audit de cohérence entre systèmes source et cible
SELECT 'Source seulement' as statut, source_id, nom
FROM systeme_source
EXCEPT  
SELECT 'Source seulement', target_id, nom  
FROM systeme_cible

UNION ALL

SELECT 'Cible seulement' as statut, target_id, nom
FROM systeme_cible
EXCEPT
SELECT 'Cible seulement', source_id, nom
FROM systeme_source

UNION ALL

SELECT 'Commun' as statut, s.source_id, s.nom
FROM systeme_source s
INTERSECT
SELECT 'Commun', c.target_id, c.nom  
FROM systeme_cible c;

Pattern d'analyse temporelle

Comparer les mêmes métriques sur différentes périodes :

-- Clients actifs : évolution mensuelle
SELECT 'Mars 2026' as periode, client_id, nb_commandes
FROM (
    SELECT client_id, COUNT(*) as nb_commandes
    FROM commandes  
    WHERE DATE_FORMAT(date_commande, '%Y-%m') = '2026-03'
    GROUP BY client_id
    HAVING COUNT(*) >= 2
) clients_mars

EXCEPT

SELECT 'Mars 2026', client_id, nb_commandes  
FROM (
    SELECT client_id, COUNT(*) as nb_commandes
    FROM commandes
    WHERE DATE_FORMAT(date_commande, '%Y-%m') = '2026-02'  
    GROUP BY client_id
    HAVING COUNT(*) >= 2
) clients_fevrier;

Cette requête identifie les nouveaux clients actifs apparus en mars. Pour approfondir votre maîtrise des requêtes complexes, consultez notre article sur les CTEs et Common Table Expressions.

Questions fréquentes

Quelle est la différence entre UNION et JOIN ?

UNION combine verticalement les résultats de plusieurs requêtes avec une structure similaire. JOIN associe horizontalement les données de tables différentes selon des critères de correspondance. UNION travaille sur des ensembles de résultats, JOIN sur des relations entre tables.

Pourquoi UNION est-il plus lent que UNION ALL ?

UNION élimine automatiquement les doublons, ce qui nécessite un tri et une comparaison de toutes les lignes. UNION ALL concatène simplement les résultats sans vérification, d'où sa rapidité supérieure. Utilisez UNION ALL quand vous contrôlez l'absence de doublons.

Comment remplacer INTERSECT sur MySQL ?

Utilisez EXISTS avec une sous-requête ou un INNER JOIN avec DISTINCT. Ces approches reproduisent le comportement d'INTERSECT en identifiant les correspondances entre les ensembles de données.

EXCEPT fonctionne-t-il dans les deux sens ?

Non, EXCEPT n'est pas commutatif. A EXCEPT B retourne les éléments de A absents de B, tandis que B EXCEPT A retourne les éléments de B absents de A. L'ordre des requêtes change complètement le résultat.

Les opérateurs ensemblistes respectent-ils l'ordre des lignes ?

L'ordre n'est pas garanti sauf si vous ajoutez une clause ORDER BY à la requête globale. Les opérateurs ensemblistes peuvent réorganiser les lignes lors du traitement, particulièrement UNION qui effectue un tri pour éliminer les doublons.

Peut-on imbriquer plusieurs opérateurs ensemblistes ?

Oui, mais respectez la priorité des opérations. INTERSECT et EXCEPT ont la même priorité et sont évalués de gauche à droite. UNION a une priorité inférieure.

-- Évaluation : (A INTERSECT B) UNION C
SELECT * FROM table_a
INTERSECT  
SELECT * FROM table_b
UNION
SELECT * FROM table_c;

-- Pour forcer l'ordre : A INTERSECT (B UNION C)  
SELECT * FROM table_a
INTERSECT
(SELECT * FROM table_b
 UNION 
 SELECT * FROM table_c);

Comment gérer les performances avec de très gros volumes ?

  • Partitionnez vos données par date ou critère métier
  • Utilisez des index composites sur les colonnes de sélection
  • Préférez les CTEs aux sous-requêtes pour la lisibilité
  • Considérez la création de vues matérialisées pour les calculs récurrents
-- Optimisation avec partition temporelle
SELECT client_id, montant FROM ventes_2025
WHERE date_vente >= '2025-12-01'
UNION ALL
SELECT client_id, montant FROM ventes_2026
WHERE date_vente >= '2026-01-01';

Ressources complémentaires

Pour maîtriser complètement les opérateurs ensemblistes et progresser en SQL, consultez :

Les opérateurs ensemblistes restent des outils puissants pour manipuler et analyser les données. Pratiquez régulièrement pour maîtriser leurs subtilités et savoir quand privilégier UNION, INTERSECT ou EXCEPT selon votre contexte.

Prêt à vous entraîner ?

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

Voir les exercices