%% nav-debut : généré par gen_navigation.py, ne pas modifier à la main %%
> [!nav]
> ↩ [[Code et Développement/Développement d'Applications/SQL IV/SQL IV|Accueil · SQL IV]]
>
> | | [[Code et Développement/Développement d'Applications/SQL IV/02_Garder les premières lignes - LIMIT\|Garder les premières lignes : LIMIT →]] |
> |:---|---:|
%% nav-fin %%
[[Code et Développement/Développement d'Applications/SQL IV/SQL IV|SQL IV]]
# Supprimer les doublons : `DISTINCT`
## Introduction
Bienvenue dans SQL IV ! Avant d'aborder les sous-requêtes, nous allons compléter la requête de sélection avec quatre outils : `DISTINCT`, `LIMIT`, `UNION` et la jointure complète.
Le premier, `DISTINCT`, répond à un problème que vous avez sûrement déjà rencontré : **des lignes en double dans le résultat**.
### Objectifs du chapitre
À la fin de ce chapitre, vous serez capable de :
- Comprendre d'où viennent les doublons dans un résultat.
- Supprimer les doublons avec `DISTINCT`.
- Compter des valeurs différentes avec `COUNT(DISTINCT …)`.
- Choisir entre `DISTINCT` et `GROUP BY`.
---
## Les tables utilisées dans SQL IV
Dans tout le cours SQL IV, nous reprenons les tables *Clients* et *Commandes* du chapitre [[Code et Développement/Développement d'Applications/SQL I/05_Comment répondre à une demande|Comment répondre à une demande]] de SQL I.
**Table `Clients`**
| id_client | nom_client | ville_client | date_inscription |
| --------- | ---------- | ------------ | ---------------- |
| 1 | Martin | Paris | 2022-03-15 |
| 2 | Dupont | Lyon | 2021-11-02 |
| 3 | Bernard | Marseille | 2023-01-20 |
| 4 | Petit | Lille | 2022-07-08 |
| 5 | Robert | Bordeaux | 2023-05-12 |
**Table `Commandes`**
| id_commande | id_client | date_commande | montant_total |
| ----------- | --------- | ------------- | ------------- |
| 101 | 1 | 2023-01-10 | 250.00 |
| 102 | 1 | 2023-02-18 | 120.00 |
| 103 | 2 | 2023-03-05 | 560.00 |
| 104 | 2 | 2023-04-22 | 80.00 |
| 105 | 2 | 2023-06-01 | 310.00 |
| 106 | 3 | 2023-07-14 | 150.00 |
| 107 | 4 | 2023-08-03 | 400.00 |
> [!note] À garder en tête
> - Robert (client 5) n'a passé **aucune** commande.
> - Dupont a passé 3 commandes, Martin 2, Bernard et Petit 1 chacun.
---
## 1. D'où viennent les doublons ?
On nous demande : ***dans quelles villes habitent les clients qui ont passé au moins une commande ?***
Une première requête :
```sql
SELECT C.ville_client
FROM Clients C INNER JOIN Commandes Co ON C.id_client = Co.id_client
ORDER BY C.ville_client;
```
Résultat :
| ville_client |
| ------------ |
| Lille |
| Lyon |
| Lyon |
| Lyon |
| Marseille |
| Paris |
| Paris |
Lyon apparaît 3 fois et Paris 2 fois. Pourquoi ?
Posons-nous la question habituelle : **à quoi correspond un enregistrement en sortie du `FROM` ?**
Avec cette jointure, un enregistrement correspond à **une commande avec son client**. Il y a 7 commandes, donc 7 enregistrements. Dupont (Lyon) a passé 3 commandes : sa ville apparaît 3 fois.
La requête n'est pas fausse : elle affiche la ville du client **de chaque commande**. Mais ce n'est pas ce qu'on nous demande : nous voulons **la liste des villes**, chacune une seule fois.
### Ce que vous savez déjà faire (SQL I)
Pour n'avoir chaque ville qu'une fois, on peut regrouper les enregistrements par ville avec `GROUP BY` (voir le chapitre [[Code et Développement/Développement d'Applications/SQL I/03_ Opérations entre les enregistrements|Opérations entre les enregistrements]]) :
```sql
SELECT C.ville_client
FROM Clients C INNER JOIN Commandes Co ON C.id_client = Co.id_client
GROUP BY C.ville_client
ORDER BY C.ville_client;
```
| ville_client |
| ------------ |
| Lille |
| Lyon |
| Marseille |
| Paris |
C'est juste. Mais on forme des groupes sans faire aucun calcul dessus : le `GROUP BY` est utilisé ici seulement pour supprimer les doublons. SQL propose un mot clé fait exactement pour cela.
---
## 2. Une autre solution : le mot clé `DISTINCT`
### 2.1. Syntaxe
`DISTINCT` se place juste après `SELECT` :
```sql
SELECT DISTINCT champ1, champ2, ...
FROM ...
```
Il supprime du résultat les lignes **identiques**, pour n'en garder qu'un exemplaire.
### 2.2. Exemple
```sql
SELECT DISTINCT C.ville_client
FROM Clients C INNER JOIN Commandes Co ON C.id_client = Co.id_client
ORDER BY C.ville_client;
```
Après le `SELECT`, nous avions les 7 villes. Le `DISTINCT` supprime les lignes en double (en rouge) :
| ville_client |
| ------------ |
| Lille |
| Lyon |
| <em style="color:red;text-decoration: line-through;">Lyon</em> |
| <em style="color:red;text-decoration: line-through;">Lyon</em> |
| Marseille |
| Paris |
| <em style="color:red;text-decoration: line-through;">Paris</em> |
Résultat final :
| ville_client |
| ------------ |
| Lille |
| Lyon |
| Marseille |
| Paris |
Maintenant, un enregistrement correspond à **une ville où habite au moins un client ayant commandé**.
### 2.3. `DISTINCT` porte sur la ligne entière
> [!warning] Attention
> `DISTINCT` ne s'applique pas à un seul champ mais à **toute la ligne** du résultat. Une ligne n'est supprimée que si **tous** ses champs sont identiques à ceux d'une autre ligne.
```sql
SELECT DISTINCT C.ville_client, Co.montant_total
FROM Clients C INNER JOIN Commandes Co ON C.id_client = Co.id_client;
```
Ici, aucune ligne n'est supprimée : les trois lignes de Lyon ont des montants différents (560, 80 et 310), elles ne sont donc pas identiques. Le résultat contient toujours 7 lignes.
> [!tip] La question à se poser
> Avant d'écrire `DISTINCT`, regardez les champs de votre `SELECT` : **ce sont eux, tous ensemble, qui définissent ce qu'est un doublon.**
---
## 3. Compter des valeurs différentes : `COUNT(DISTINCT …)`
On nous demande : ***combien de clients différents ont passé commande ?***
### Ce que vous savez déjà faire (SQL I et SQL III)
Un simple `COUNT(id_client)` sur *Commandes* compterait 7 : une fois par commande, et non une fois par client. Il faut donc d'abord obtenir **un enregistrement par client** (`GROUP BY id_client`), puis compter ces enregistrements avec une sous-requête dans le `FROM` :
```sql
SELECT COUNT(*) AS nb_clients_differents
FROM (
SELECT id_client
FROM Commandes
GROUP BY id_client
) AS t;
```
| nb_clients_differents |
| --------------------- |
| 4 |
### Une autre solution : `COUNT(DISTINCT …)`
`COUNT(DISTINCT …)` fait la même chose en une seule requête :
```sql
SELECT COUNT(id_client) AS nb_commandes_avec_client,
COUNT(DISTINCT id_client) AS nb_clients_differents
FROM Commandes;
```
| nb_commandes_avec_client | nb_clients_differents |
| ------------------------ | --------------------- |
| 7 | 4 |
- `COUNT(id_client)` compte les enregistrements où `id_client` n'est pas `NULL` : il y en a 7, un par commande.
- `COUNT(DISTINCT id_client)` compte les **valeurs différentes** de `id_client` : 1, 2, 3 et 4, soit 4 clients.
> [!note] `COUNT(DISTINCT …)` et les groupes
> `COUNT(DISTINCT …)` fonctionne aussi avec un `GROUP BY`. Par exemple, pour connaître le nombre de clients différents ayant commandé chaque mois, on regroupe par mois et on compte `COUNT(DISTINCT id_client)` dans chaque groupe.
> [!warning] Un seul champ dans `COUNT(DISTINCT …)`
> Écrivez toujours **un seul champ** entre les parenthèses de `COUNT(DISTINCT …)`.
---
## 4. `DISTINCT` ou `GROUP BY` ?
Comme nous l'avons vu au point 1, ces deux requêtes donnent le même résultat :
```sql
SELECT DISTINCT ville_client
FROM Clients;
```
```sql
SELECT ville_client
FROM Clients
GROUP BY ville_client;
```
Mais elles n'ont pas le même rôle :
| | `DISTINCT` | `GROUP BY` |
| --- | --- | --- |
| Rôle | Supprimer les lignes en double **du résultat** | Former des groupes **pour faire des calculs** (`COUNT`, `SUM`, `AVG`…) |
| Quand l'utiliser ? | On veut une liste sans doublon, sans calcul | On veut un calcul par groupe |
> [!tip] Règle simple
> - Je veux **une liste sans doublon** → `DISTINCT`.
> - Je veux **un calcul par groupe** → `GROUP BY`.
---
## 5. Où se place `DISTINCT` dans l'ordre d'exécution ?
Nous ajoutons une étape aux 6 étapes vues dans SQL I. `DISTINCT` est exécuté **juste après le `SELECT`** : il faut d'abord savoir quels champs sont affichés pour savoir quelles lignes sont identiques.
1. `FROM`
2. `WHERE`
3. `GROUP BY`
4. `HAVING`
5. `SELECT`
6. **`DISTINCT`**
7. `ORDER BY`
Nous compléterons cet ordre avec `LIMIT` au chapitre suivant, et nous verrons au chapitre [[Code et Développement/Développement d'Applications/SQL IV/10_Calculer sur un groupe - PARTITION BY|Calculer sur un groupe : PARTITION BY]] que les fonctions de fenêtre sont calculées à l'étape du `SELECT`.
---
## 6. Le piège : `DISTINCT` comme pansement
> [!warning] Ne cachez pas un problème avec `DISTINCT`
> Quand des doublons apparaissent alors que vous ne les attendiez pas, la tentation est grande d'ajouter `DISTINCT` pour les faire disparaître.
>
> **Avant cela, demandez-vous d'où ils viennent.** Le plus souvent, c'est une jointure qui multiplie les enregistrements. Si vous faites ensuite un calcul (`SUM`, `COUNT`…), il sera faux, et le `DISTINCT` du `SELECT` ne le corrigera pas.
>
> Exemple : en joignant *Clients* et *Commandes*, Dupont apparaît 3 fois. Si on additionne une information du client (une remise annuelle, par exemple) après cette jointure, elle sera comptée 3 fois.
`DISTINCT` est le bon outil **quand on a compris** pourquoi les doublons sont là et qu'on veut une simple liste.
---
> [!important] Ce qu'il faut retenir
> - Les doublons viennent souvent d'une jointure : **un enregistrement en sortie du `FROM` n'est pas forcément ce que l'on veut afficher**.
> - `SELECT DISTINCT` supprime les lignes **entièrement** identiques du résultat.
> - `COUNT(DISTINCT champ)` compte les valeurs différentes d'un champ.
> - `DISTINCT` pour une liste, `GROUP BY` pour un calcul par groupe.
> - Ordre d'exécution : … `SELECT` → `DISTINCT` → `ORDER BY`.
Prochaine étape, ne garder que les premières lignes d'un résultat :
[[Code et Développement/Développement d'Applications/SQL IV/02_Garder les premières lignes - LIMIT|Garder les premières lignes : LIMIT]]
%% nav-debut : généré par gen_navigation.py, ne pas modifier à la main %%
> [!nav]
> ↩ [[Code et Développement/Développement d'Applications/SQL IV/SQL IV|Accueil · SQL IV]]
>
> | | [[Code et Développement/Développement d'Applications/SQL IV/02_Garder les premières lignes - LIMIT\|Garder les premières lignes : LIMIT →]] |
> |:---|---:|
%% nav-fin %%
---
🗓️ Historique
Dernière mise à jour : `30 octobre 2026`
Rédigé par : [[Vincent TELLIER]]