Dans cet article
- La syntaxe COUNT(DISTINCT colonne) permet de compter les valeurs uniques d’une colonne en une seule requête
- La différence entre COUNT(*) et COUNT(DISTINCT) peut représenter un écart de 30 à 90 % sur des tables avec doublons
- Le GROUP BY combiné à COUNT(DISTINCT) est la méthode standard pour segmenter des comptages uniques
- Sur des tables de plus de 10 millions de lignes, COUNT(DISTINCT) nécessite des optimisations d’index spécifiques
- La syntaxe varie légèrement entre MySQL, PostgreSQL, SQL Server et Oracle pour les cas multi-colonnes
- Des alternatives comme les sous-requêtes ou les CTE peuvent remplacer COUNT(DISTINCT) dans certains scénarios complexes
Sommaire
- Syntaxe de base de COUNT(DISTINCT) en SQL
- Différence entre COUNT(*) et COUNT(DISTINCT)
- COUNT(DISTINCT) avec GROUP BY : segmenter les résultats
- COUNT DISTINCT sur plusieurs colonnes
- COUNT DISTINCT selon les SGBD : MySQL, PostgreSQL, Oracle, SQL Server
- Performances et optimisation de COUNT(DISTINCT)
- Cas pratiques et erreurs courantes
- Alternatives à COUNT(DISTINCT)
Quand je forme mes étudiants en BTS SIO aux requêtes SQL, la question revient systématiquement : comment compter uniquement les valeurs différentes d’une colonne ? La réponse tient en deux mots : count distinct sql. Cette combinaison de la fonction d’agrégation COUNT avec le mot-clé DISTINCT est l’un des outils les plus utilisés en analyse de données. Pourtant, je constate régulièrement des confusions sur sa syntaxe, ses limites et ses performances. Dans ce guide, je vous propose de maîtriser COUNT(DISTINCT) de A à Z, avec des exemples concrets que vous pourrez reproduire immédiatement.
Syntaxe de base de COUNT(DISTINCT) en SQL
La syntaxe de COUNT(DISTINCT) est simple et suit une logique que vous connaissez déjà si vous maîtrisez la fonction COUNT classique. Voici la forme générale :
SELECT COUNT(DISTINCT nom_colonne)
FROM nom_table;
Le mot-clé DISTINCT s’insère à l’intérieur des parenthèses de COUNT, juste avant le nom de la colonne. C’est un point crucial : DISTINCT ne se place pas avant COUNT, mais bien à l’intérieur de l’appel de fonction.
Prenons un exemple concret. Imaginons une table commandes qui contient les achats d’un site e-commerce :
CREATE TABLE commandes (
id INT PRIMARY KEY,
client_id INT,
produit VARCHAR(100),
montant DECIMAL(10,2),
date_commande DATE
);
INSERT INTO commandes VALUES
(1, 101, 'Clavier', 49.99, '2026-01-15'),
(2, 102, 'Souris', 29.99, '2026-01-16'),
(3, 101, 'Écran', 299.99, '2026-01-17'),
(4, 103, 'Clavier', 49.99, '2026-01-18'),
(5, 101, 'Souris', 29.99, '2026-01-19'),
(6, 104, 'Clavier', 49.99, '2026-01-20');
Pour connaître le nombre de clients distincts ayant passé commande :
SELECT COUNT(DISTINCT client_id) AS nb_clients_uniques
FROM commandes;
Résultat : 4. La table contient 6 lignes, mais seulement 4 valeurs différentes de client_id (101, 102, 103, 104). Le client 101 apparaît trois fois, mais il n’est compté qu’une seule fois grâce à DISTINCT.
De même, pour compter le nombre de produits différents vendus :
SELECT COUNT(DISTINCT produit) AS nb_produits_differents
FROM commandes;
Résultat : 3 (Clavier, Souris, Écran). Notez que les valeurs NULL sont automatiquement exclues du comptage par COUNT(DISTINCT), comme c’est le cas pour COUNT(colonne) sans DISTINCT.

Différence entre COUNT(*) et COUNT(DISTINCT)
C’est la question que mes étudiants me posent le plus souvent, et la confusion entre ces deux formes provoque régulièrement des bugs subtils en production. Voici ce que chacune fait exactement :
| Expression | Ce qu’elle compte | Inclut les NULL | Inclut les doublons |
|---|---|---|---|
| COUNT(*) | Toutes les lignes de la table | Oui | Oui |
| COUNT(colonne) | Les lignes où colonne n’est pas NULL | Non | Oui |
| COUNT(DISTINCT colonne) | Les valeurs uniques non NULL de colonne | Non | Non |
Illustrons avec notre table commandes :
SELECT
COUNT(*) AS total_lignes,
COUNT(client_id) AS total_non_null,
COUNT(DISTINCT client_id) AS clients_uniques
FROM commandes;
Résultat :
- total_lignes : 6 (toutes les lignes)
- total_non_null : 6 (aucun NULL dans client_id)
- clients_uniques : 4 (valeurs distinctes uniquement)
L’écart entre COUNT(*) et COUNT(DISTINCT) révèle le taux de duplication de vos données. Dans cet exemple, 33 % des lignes sont des doublons du point de vue du client. Sur des tables de production avec des millions de lignes, cet écart peut atteindre 90 % ou plus, ce qui change radicalement l’interprétation de vos résultats. Si vous travaillez avec des fonctions d’agrégation et que vous devez aussi arrondir vos résultats, je vous recommande de consulter mon guide sur la fonction ROUND en SQL pour éviter les pièges classiques.
Un piège fréquent : utiliser COUNT(*) en pensant obtenir des valeurs uniques. J’ai vu des rapports entiers faussés parce qu’un développeur comptait le nombre total de lignes au lieu du nombre de clients distincts. La différence peut transformer un rapport affichant 50 000 commandes en un rapport montrant 8 000 clients actifs, ce qui change complètement la lecture métier.
COUNT(DISTINCT) avec GROUP BY : segmenter les résultats
La véritable puissance de COUNT(DISTINCT) se révèle quand on la combine avec GROUP BY. Cette association permet de compter les valeurs uniques par catégorie, par période ou par tout autre critère de segmentation.
Exemple : compter le nombre de clients distincts par produit acheté :
SELECT
produit,
COUNT(DISTINCT client_id) AS nb_clients
FROM commandes
GROUP BY produit
ORDER BY nb_clients DESC;
Résultat :
| produit | nb_clients |
|---------|------------|
| Clavier | 3 |
| Souris | 2 |
| Écran | 1 |
On voit immédiatement que le Clavier est le produit qui attire le plus de clients différents. Cette information est bien plus pertinente qu’un simple COUNT(*) qui aurait donné le même chiffre (3) pour le Clavier mais pour une raison différente (3 ventes, pas 3 clients).
Vous pouvez aussi combiner plusieurs COUNT(DISTINCT) dans la même requête :
SELECT
DATE_FORMAT(date_commande, '%Y-%m') AS mois,
COUNT(*) AS total_commandes,
COUNT(DISTINCT client_id) AS clients_uniques,
COUNT(DISTINCT produit) AS produits_vendus
FROM commandes
GROUP BY DATE_FORMAT(date_commande, '%Y-%m');
Ce type de requête est fondamental en analyse de données. Elle donne en un coup d’œil le volume d’activité, la diversité de la clientèle et la variété du catalogue vendu, mois par mois. Pour filtrer davantage les résultats d’un GROUP BY, vous pouvez utiliser la clause HAVING :
SELECT
produit,
COUNT(DISTINCT client_id) AS nb_clients
FROM commandes
GROUP BY produit
HAVING COUNT(DISTINCT client_id) >= 2;
Cette requête ne retourne que les produits achetés par au moins 2 clients différents. Notez qu’il faut répéter l’expression COUNT(DISTINCT) dans le HAVING ; on ne peut pas utiliser l’alias nb_clients dans la plupart des SGBD.
COUNT DISTINCT sur plusieurs colonnes

Compter les combinaisons uniques de plusieurs colonnes est un besoin fréquent, mais la syntaxe varie selon les SGBD. C’est un point qui pose souvent problème à mes étudiants.
En SQL standard, COUNT(DISTINCT col1, col2) n’est pas supporté par tous les SGBD. MySQL l’accepte, mais ni PostgreSQL, ni SQL Server, ni Oracle ne le supportent directement.
Méthode MySQL (directe) :
-- Fonctionne uniquement sur MySQL
SELECT COUNT(DISTINCT client_id, produit) AS combinaisons_uniques
FROM commandes;
Méthode universelle avec CONCAT :
SELECT COUNT(DISTINCT CONCAT(client_id, '-', produit)) AS combinaisons_uniques
FROM commandes;
Attention avec CONCAT : si vos valeurs contiennent le séparateur choisi, vous risquez des collisions. Le client_id 1 avec le produit « 2-Clavier » produirait la même chaîne que le client_id 12 avec « Clavier ». Utilisez un séparateur qui n’apparaît jamais dans vos données, ou une approche par sous-requête.
Méthode avec sous-requête (la plus fiable) :
SELECT COUNT(*) AS combinaisons_uniques
FROM (
SELECT DISTINCT client_id, produit
FROM commandes
) AS paires_uniques;
Cette dernière méthode fonctionne sur tous les SGBD sans exception. Elle utilise une sous-requête qui extrait d’abord les paires uniques avec DISTINCT, puis COUNT(*) les dénombre. Si vous avez besoin de combiner cette technique avec des jointures, mon article sur SQL WHERE et LEFT JOIN vous aidera à structurer vos requêtes complexes. Pour mieux comprendre le comptage de valeurs uniques dans un contexte plus large, consultez également mon guide sur COUNT unique en SQL.
COUNT DISTINCT selon les SGBD : MySQL, PostgreSQL, Oracle, SQL Server
Chaque système de gestion de base de données gère COUNT(DISTINCT) avec ses propres particularités. Voici les différences que j’ai relevées au fil de mes projets et que je transmets systématiquement à mes étudiants.
| Fonctionnalité | MySQL | PostgreSQL | SQL Server | Oracle |
|---|---|---|---|---|
| COUNT(DISTINCT col) | Oui | Oui | Oui | Oui |
| COUNT(DISTINCT col1, col2) | Oui | Non | Non | Non |
| COUNT(DISTINCT expr) | Oui | Oui | Oui | Oui |
| Fonctions analytiques avec DISTINCT | MySQL 8+ | Oui | Non | Oui |
| Estimation (HyperLogLog) | Non natif | Extension | APPROX_COUNT_DISTINCT | APPROX_COUNT_DISTINCT |
MySQL est le plus permissif : il accepte COUNT(DISTINCT) avec plusieurs colonnes séparées par des virgules. C’est pratique mais non portable.
PostgreSQL impose la sous-requête pour le multi-colonnes, mais offre en contrepartie une excellente gestion des fonctions de fenêtrage (window functions) avec DISTINCT :
-- PostgreSQL : nombre de produits distincts avec fenêtre
SELECT
client_id,
produit,
COUNT(DISTINCT produit) OVER () AS total_produits_distincts
FROM commandes;
SQL Server propose depuis la version 2019 la fonction APPROX_COUNT_DISTINCT pour les comptages approximatifs sur de très gros volumes. Selon la documentation officielle de Microsoft sur APPROX_COUNT_DISTINCT, cette fonction garantit une marge d’erreur de 2 % avec un intervalle de confiance de 97 % :
-- SQL Server 2019+
SELECT APPROX_COUNT_DISTINCT(client_id) AS estimation_clients
FROM commandes;
Oracle dispose également de APPROX_COUNT_DISTINCT depuis la version 12c, avec un fonctionnement similaire basé sur l’algorithme HyperLogLog. Pour des requêtes Oracle complexes, le comptage distinct reste parfaitement compatible avec les clauses analytiques propres à ce SGBD.
Performances et optimisation de COUNT(DISTINCT)
COUNT(DISTINCT) a un coût en performances plus élevé que COUNT(*). Le moteur SQL doit stocker toutes les valeurs en mémoire pour éliminer les doublons avant de compter. Sur des tables volumineuses, cela peut devenir un vrai problème.
Impact sur les performances :
- COUNT(*) parcourt l’index ou la table et incrémente un compteur : complexité O(n)
- COUNT(DISTINCT) doit trier ou hasher les valeurs pour détecter les doublons : complexité O(n log n) ou O(n) avec hash
- Sur une table de 10 millions de lignes, COUNT(DISTINCT) peut être 3 à 10 fois plus lent que COUNT(*)
Optimisation par index :
La première chose à faire est de créer un index sur la colonne concernée. Le moteur SQL pourra alors effectuer un « index scan » au lieu d’un « full table scan » :
CREATE INDEX idx_commandes_client ON commandes(client_id);
Avec cet index, COUNT(DISTINCT client_id) lira uniquement l’index B-tree sans toucher à la table, ce qui accélère considérablement l’exécution. Pour vérifier que votre index est bien utilisé, utilisez EXPLAIN (MySQL, PostgreSQL) ou SET STATISTICS IO ON (SQL Server).

Optimisation par approximation :
Si vous n’avez pas besoin d’un résultat exact, les fonctions d’approximation sont jusqu’à 50 fois plus rapides. D’après la documentation Snowflake sur le comptage de valeurs distinctes, la fonction APPROX_COUNT_DISTINCT utilise l’algorithme HyperLogLog avec une marge d’erreur typique de 1 à 2 %. C’est largement suffisant pour des dashboards ou des rapports d’activité où la tendance compte plus que le chiffre exact.
Optimisation par matérialisation :
Pour des requêtes exécutées fréquemment, envisagez une vue matérialisée ou une table de pré-agrégation :
CREATE TABLE stats_quotidiennes AS
SELECT
date_commande,
COUNT(DISTINCT client_id) AS clients_uniques,
COUNT(DISTINCT produit) AS produits_vendus
FROM commandes
GROUP BY date_commande;
Cette approche est courante dans les entrepôts de données où les mêmes agrégations sont consultées des centaines de fois par jour.
Cas pratiques et erreurs courantes
Après des années à corriger les copies de mes étudiants et à débugger du SQL en production, voici les erreurs les plus fréquentes que je rencontre avec COUNT(DISTINCT).
Erreur n°1 : placer DISTINCT au mauvais endroit
-- INCORRECT : DISTINCT s'applique au SELECT, pas à COUNT
SELECT DISTINCT COUNT(client_id) FROM commandes;
-- CORRECT : DISTINCT à l'intérieur de COUNT
SELECT COUNT(DISTINCT client_id) FROM commandes;
La première requête retourne le COUNT total (6), puis applique DISTINCT sur ce résultat unique, ce qui ne change rien. Le résultat est 6, pas 4.
Erreur n°2 : oublier les NULL
-- Si client_id contient des NULL, ils ne sont PAS comptés
INSERT INTO commandes VALUES (7, NULL, 'Casque', 79.99, '2026-01-21');
SELECT COUNT(DISTINCT client_id) FROM commandes;
-- Retourne toujours 4, pas 5
Si vous devez inclure les NULL dans le comptage, utilisez COALESCE :
SELECT COUNT(DISTINCT COALESCE(client_id, -1)) FROM commandes;
-- Retourne 5 : les 4 clients + la valeur -1 pour NULL
Erreur n°3 : confusion avec SELECT DISTINCT
SELECT DISTINCT retourne les lignes uniques, COUNT(DISTINCT) retourne un nombre. Ce sont deux opérations fondamentalement différentes :
-- Retourne la LISTE des clients uniques
SELECT DISTINCT client_id FROM commandes;
-- Retourne le NOMBRE de clients uniques
SELECT COUNT(DISTINCT client_id) FROM commandes;
Erreur n°4 : utiliser COUNT(DISTINCT *)
-- INCORRECT : cette syntaxe génère une erreur
SELECT COUNT(DISTINCT *) FROM commandes;
-- CORRECT : utilisez une sous-requête
SELECT COUNT(*) FROM (SELECT DISTINCT * FROM commandes) AS t;
COUNT(DISTINCT *) n’est pas une syntaxe SQL valide. Pour compter les lignes entièrement distinctes, passez par une sous-requête. N’oubliez pas de bien documenter vos requêtes avec des commentaires SQL lorsque la logique devient complexe.
Cas pratique : taux de rétention client
Voici un exemple concret de requête métier utilisant COUNT(DISTINCT) pour calculer un taux de rétention :
SELECT
DATE_FORMAT(date_commande, '%Y-%m') AS mois,
COUNT(DISTINCT client_id) AS clients_actifs,
LAG(COUNT(DISTINCT client_id)) OVER (ORDER BY DATE_FORMAT(date_commande, '%Y-%m')) AS clients_mois_precedent,
ROUND(
COUNT(DISTINCT client_id) * 100.0 /
NULLIF(LAG(COUNT(DISTINCT client_id)) OVER (ORDER BY DATE_FORMAT(date_commande, '%Y-%m')), 0),
2
) AS taux_retention_pct
FROM commandes
GROUP BY DATE_FORMAT(date_commande, '%Y-%m');
Cette requête combine COUNT(DISTINCT) avec les fonctions de fenêtrage LAG et ROUND pour produire un indicateur métier directement exploitable. Pour assembler ce type de résultats provenant de plusieurs requêtes, la clause SQL UNION peut s’avérer très utile.
Alternatives à COUNT(DISTINCT)
Dans certains cas, COUNT(DISTINCT) n’est pas la meilleure option. Voici les alternatives que je recommande selon le contexte.
Sous-requête avec DISTINCT :
-- Équivalent à COUNT(DISTINCT client_id)
SELECT COUNT(*)
FROM (SELECT DISTINCT client_id FROM commandes) AS clients;
Cette approche est parfois plus performante sur certains SGBD car le plan d’exécution peut être différent. Elle est aussi plus lisible quand la logique est complexe.
CTE (Common Table Expression) :
WITH clients_uniques AS (
SELECT DISTINCT client_id
FROM commandes
WHERE montant > 50
)
SELECT COUNT(*) AS nb_clients_premium
FROM clients_uniques;
Les CTE améliorent considérablement la lisibilité des requêtes complexes et permettent de réutiliser le résultat intermédiaire.
EXISTS pour vérifier l’unicité :
Si vous avez besoin de savoir si une valeur existe au moins une fois plutôt que de la compter, EXISTS est plus efficace :
-- Plus rapide que COUNT(DISTINCT) quand on veut juste savoir si au moins 2 clients existent
SELECT CASE
WHEN (SELECT COUNT(*) FROM (SELECT DISTINCT client_id FROM commandes LIMIT 2) t) >= 2
THEN 'Plusieurs clients'
ELSE 'Un seul client ou aucun'
END AS diagnostic;
L’utilisation de APPROX_COUNT_DISTINCT (SQL Server, Oracle, BigQuery, Snowflake) reste l’alternative la plus pertinente pour les très gros volumes. Selon la documentation W3Resource sur COUNT avec DISTINCT, cette fonction d’agrégation est compatible avec la norme SQL et supportée par la quasi-totalité des SGBD modernes.
À retenir
- Placez toujours DISTINCT à l’intérieur des parenthèses de COUNT, jamais avant la fonction
- Créez un index dédié sur chaque colonne utilisée dans un COUNT(DISTINCT) fréquent
- Pour le multi-colonnes, privilégiez la sous-requête avec DISTINCT plutôt que CONCAT pour éviter les collisions
- Utilisez APPROX_COUNT_DISTINCT sur les tables de plus de 10 millions de lignes quand un résultat exact n’est pas nécessaire
- Combinez COUNT(DISTINCT) avec GROUP BY et HAVING pour des analyses segmentées et filtrées
Questions fréquentes
How to count distinct records in SQL?
Pour compter les enregistrements distincts en SQL, utilisez la syntaxe SELECT COUNT(DISTINCT nom_colonne) FROM nom_table. Le mot-clé DISTINCT se place à l’intérieur des parenthèses de COUNT. Si vous devez compter des lignes entièrement distinctes (toutes colonnes confondues), utilisez une sous-requête : SELECT COUNT(*) FROM (SELECT DISTINCT * FROM nom_table) AS t. Les valeurs NULL sont automatiquement exclues du comptage.
What is the difference between COUNT(*) and COUNT(DISTINCT)?
COUNT(*) compte toutes les lignes d’une table, y compris les doublons et les lignes contenant des NULL. COUNT(DISTINCT colonne) ne compte que les valeurs uniques de la colonne spécifiée, en excluant les NULL et les doublons. Par exemple, sur une table de 1 000 commandes passées par 200 clients différents, COUNT(*) retourne 1 000 tandis que COUNT(DISTINCT client_id) retourne 200.
How to count distinct values in a table?
Pour compter les valeurs distinctes d’une colonne, écrivez SELECT COUNT(DISTINCT colonne) FROM table. Pour compter les combinaisons uniques de plusieurs colonnes, utilisez une sous-requête : SELECT COUNT(*) FROM (SELECT DISTINCT col1, col2 FROM table) AS t. Sur MySQL uniquement, vous pouvez aussi écrire COUNT(DISTINCT col1, col2) directement. Pour segmenter par catégorie, ajoutez GROUP BY à votre requête.
Can we use COUNT and DISTINCT together in SQL?
Oui, COUNT et DISTINCT se combinent avec la syntaxe COUNT(DISTINCT colonne). Cette combinaison est supportée par tous les SGBD majeurs : MySQL, PostgreSQL, SQL Server, Oracle, SQLite, et les plateformes cloud comme BigQuery et Snowflake. Vous pouvez l’utiliser dans un SELECT simple, avec GROUP BY, dans des sous-requêtes, et même avec des fonctions de fenêtrage sur certains SGBD.
COUNT(DISTINCT) est-il plus lent que COUNT(*) ?
Oui, COUNT(DISTINCT) est généralement 3 à 10 fois plus lent que COUNT(*) car le moteur SQL doit stocker et comparer les valeurs pour éliminer les doublons. Pour optimiser les performances, créez un index sur la colonne concernée, utilisez des vues matérialisées pour les requêtes fréquentes, ou optez pour APPROX_COUNT_DISTINCT (disponible sur SQL Server 2019+, Oracle 12c+ et Snowflake) qui offre une estimation avec une marge d’erreur de 2 % pour un gain de vitesse considérable.
Comment utiliser COUNT(DISTINCT) avec GROUP BY ?
La syntaxe est SELECT colonne_groupe, COUNT(DISTINCT colonne_comptee) FROM table GROUP BY colonne_groupe. Par exemple, pour compter le nombre de clients distincts par produit : SELECT produit, COUNT(DISTINCT client_id) AS nb_clients FROM commandes GROUP BY produit. Vous pouvez filtrer les groupes avec HAVING : HAVING COUNT(DISTINCT client_id) >= 5 pour ne garder que les produits achetés par au moins 5 clients différents.
Formatrice IT indépendante depuis 2016, ancienne étudiante BTS SIO SLAM. 6 ans d'expérience en entreprise.