%% 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]]