> ## Content Index
> Fetch the complete content index at: https://www.idriss-benbassou.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# La déduplication en SQL : supprimer les doublons proprement (ROW_NUMBER, QUALIFY)
- URL: https://www.idriss-benbassou.com/deduplication-sql-supprimer-doublons-row-number-qualify/
- Published: 2026-06-29T14:02:11.000Z
- Updated: 2026-07-16T22:31:51.000Z
- Author: Idriss BENBASSOU
- Tags: SQL, #rating-2

Les doublons, c'est l'un des problèmes data les plus fréquents. Un export relancé deux fois, un `JOIN` qui duplique des lignes, une source pas propre, et d'un coup les chiffres sont faux. Et le réflexe `SELECT DISTINCT` ne règle presque jamais le vrai problème.

Dans cet article, on voit comment dédoublonner proprement, en détectant les doublons, comprendre la raison, et surtout choisir quelle ligne tu gardes. Les exemples viennent du [SQL Lab de DataCertification](https://datacertification.fr/apprendre-sql?ref=idriss-benbassou.com), tu peux les écrire et les exécuter directement dans ton navigateur. Si tu as besoin de revoir `ROW_NUMBER` ou les `OVER`, j'ai un [guide complet sur les window functions](https://www.idriss-benbassou.com/snowflake-sql-window-functions-over-partition-by-order-by-rows/), et pour les bases, le [guide des fondamentaux SQL](https://www.idriss-benbassou.com/apprendre-sql-guide-complet-fondamentaux/).

## D'abord, c'est quoi un doublon

Avant de supprimer quoi que ce soit, il faut savoir ce que tu supprimes. Il y a deux types de doublons, et on ne les traite pas pareil.

| Type           | Exemple                                                         | Outil                 |
| -------------- | --------------------------------------------------------------- | --------------------- |
| Doublon strict | Deux lignes 100 % identiques, toutes colonnes comprises         | DISTINCT              |
| Doublon métier | Même client, même produit, mais un id ou un timestamp différent | ROW\_NUMBER / QUALIFY |

Dans la vraie vie, c'est presque toujours le second cas. Tes lignes ne sont pas identiques totalement car il y a un petit détail technique diffèrent comme un timestamp. Et c'est exactement là que `DISTINCT` te lâche.

La seule question qui compte avant de dédoublonner c'est sur quelles colonnes deux lignes sont "les mêmes" (la clé du doublon), et laquelle tu veux garder (la plus récente ? la plus complète ?). Tant que tu n'as pas répondu à ça, tu ne dédoublonnes pas, tu vas supprimer au hasard.

## Étape 1 : détecter les doublons

Le bon réflexe c'est de regarder les doublons avant de les supprimer. Le plus simple est un `GROUP BY` sur ta clé, et tu gardes les groupes qui ont plus d'une ligne.

```sql
SELECT email, COUNT(*) AS nb
FROM users
GROUP BY email
HAVING COUNT(*) > 1;

```

Tu obtiens la liste des emails en double et leur nombre d'occurrences. Si tu veux carrément voir les paires de lignes concernées, un self-join fait le travail.

> 💡 **À toi de jouer** \==> [Self-join : trouver les doublons (ra-13)](https://datacertification.fr/exercise/ra-13?ref=idriss-benbassou.com).

## Étape 2 : DISTINCT*(utile mais vite limité)*

`DISTINCT` élimine du résultat les lignes entièrement identiques. Pour dédoublonner une liste de valeurs simples, c'est parfait.

```sql
SELECT DISTINCT category FROM products;

```

Mais il a deux limites qui le rendent inutile dans la plupart des cas réels :

- **Dès qu'une colonne diffère**, les lignes ne sont plus identiques et `DISTINCT` ne supprime rien. Un `id` ou un `created_at` qui change, et tes "doublons" passent tous au travers.
- **Il ne te laisse pas choisir** quelle ligne garder. Tu ne contrôles rien, tu écrases.

Si tu as juste besoin de compter des valeurs uniques (le nombre de clients distincts, pas de commandes) alors `COUNT(DISTINCT ...)` est l'outil. Pour supprimer des lignes en gardant la bonne, il faut passer à la vitesse supérieure.

> 💡 **À toi de jouer** : [Clients distincts par statut (gb-12)](https://datacertification.fr/exercise/gb-12?ref=idriss-benbassou.com).

## Étape 3 : ROW\_NUMBER + filtre

C'est le pattern de déduplication le plus efficace, celui que tu vas utiliser 90 % du temps. L'idée est de numéroter les lignes à l'intérieur de chaque groupe de doublons, puis ne garder que la première.

Trois décisions à prendre, toutes dans la clause `OVER` :

- `PARTITION BY` \= la clé du doublon (sur quoi deux lignes sont "les mêmes").
- `ORDER BY` \= le critère pour choisir la ligne à garder (la plus récente d'abord, par exemple).
- `WHERE rn = 1` \= on ne garde que la première de chaque groupe.

Prenons une table `commandes` où chaque client a passé plusieurs commandes, et où on veut garder uniquement la plus récente par client.

Table `commandes` :

| id | client | produit | date\_commande |
| -- | ------ | ------- | -------------- |
| 1  | Alice  | Clavier | 2024-01-10     |
| 2  | Alice  | Souris  | 2024-02-15     |
| 3  | Bob    | Casque  | 2024-03-05     |
| 4  | Bob    | Webcam  | 2024-01-20     |
| 5  | Clara  | Clavier | 2024-01-05     |

```sql
WITH derniere_commande AS (
  SELECT client, produit, date_commande,
    ROW_NUMBER() OVER (
      PARTITION BY client
      ORDER BY date_commande DESC
    ) AS rn
  FROM commandes
)
SELECT client, produit, date_commande
FROM derniere_commande
WHERE rn = 1
ORDER BY client;

```

Résultat : une seule ligne par client, la plus récente.

| client | produit | date\_commande |
| ------ | ------- | -------------- |
| Alice  | Souris  | 2024-02-15     |
| Bob    | Casque  | 2024-03-05     |
| Clara  | Clavier | 2024-01-05     |

> 💡 **À toi de jouer** : [CTE pour dédoublonner (ct-21)](https://datacertification.fr/exercise/ct-21?ref=idriss-benbassou.com).

## Étape 4 : QUALIFY, la solution des DW modernes

Le pattern ci-dessus marche partout, mais il est un peu verbeux car il faut un CTE juste pour pouvoir filtrer. Les DW modernes proposent un raccourci, `QUALIFY`, qui filtre une window function directement, sans CTE ni sous-requête.

```sql
-- Même résultat
SELECT client, produit, date_commande
FROM commandes
QUALIFY ROW_NUMBER() OVER (
  PARTITION BY client
  ORDER BY date_commande DESC
) = 1;

```

`QUALIFY` est à la window function ce que `HAVING` est à [l'agrégat](https://www.idriss-benbassou.com/apprendre-sql-guide-complet-fondamentaux/) donc un filtre qui s'applique après son calcul. C'est la façon la plus propre de dédoublonner quand ton moteur le supporte.

Nb, tous ne le supportent pas :

| Moteur     | QUALIFY natif        |
| ---------- | -------------------- |
| Snowflake  | ✅                    |
| BigQuery   | ✅                    |
| Databricks | ✅                    |
| DuckDB     | ✅                    |
| Teradata   | ✅                    |
| PostgreSQL | ❌ (passe par un CTE) |
| MySQL      | ❌                    |
| SQL Server | ❌                    |
| SQLite     | ❌                    |

C'est pour ça que dans le SQL Lab (qui tourne sur SQLite), tu pratiques la version CTE de l'étape 3\. Sur Snowflake ou BigQuery au travail, tu pourras la raccourcir avec `QUALIFY`. Le raisonnement est identique, seule l'écriture change. ([pour pratiquer Qualify](https://datacertification.fr/analytics-exercise/an-fnd-006?ref=idriss-benbassou.com))

## Étape 5 : Choisir quelle ligne garder

Dédoublonner, ce n'est pas supprimer des lignes au hasard, c'est décider laquelle survit. Tout se joue dans l'`ORDER BY` du `OVER` :

- La plus récente : `ORDER BY date_commande DESC`.
- La première arrivée : `ORDER BY date_commande ASC`.
- La plus complète : `ORDER BY (CASE WHEN email IS NULL THEN 1 ELSE 0 END)` pour pousser les lignes incomplètes en dernier.

💡

Nb : si ton critère de tri produit des égalités, `ROW_NUMBER` en choisit quand même une, mais de façon non déterministe (elle peut changer d'une exécution à l'autre).

Pour un résultat stable, ajoute une colonne qui départage, typiquement l'`id` :

```sql
ROW_NUMBER() OVER (
  PARTITION BY client
  ORDER BY date_commande DESC, id DESC
)

```

Et à ne pas confondre avec `RANK` car `ROW_NUMBER` donne un numéro unique (idéal pour garder exactement une ligne), alors que `RANK` attribue le même rang aux lignes identiques (donc tu peux en garder plusieurs sans le vouloir). Pour de la déduplication stricte, c'est `ROW_NUMBER`.

Le même pattern sert aussi à garder les N premières lignes par groupe (par exemple, les 2 meilleurs salaires par département etc..) en remplaçant juste `= 1` par `<= 2`.

> 💡 **À toi de jouer** : [Top 2 salaires par département (wf-17)](https://datacertification.fr/exercise/wf-17?ref=idriss-benbassou.com).

## Cas particulier : dédoublonner un résultat avec UNION

Quand tu combines deux requêtes, `UNION` retourne un résultat distinct, donc il élimine les doublons entre les deux jeux de données. `UNION ALL`, lui, garde tout et évite le coût de déduplication.

```sql
SELECT name FROM employees
UNION            -- dédoublonne
SELECT name FROM contractors;

```

> 💡 **À toi de jouer** : [UNION : combiner deux résultats (ra-01)](https://datacertification.fr/exercise/ra-01?ref=idriss-benbassou.com).

## Pour aller plus loin et pratiquer

La déduplication, comme tout le SQL, s'apprend en écrivant les requêtes, pas en les lisant. Les modules "CTEs", "Window Functions" et "Requêtes avancées" du [SQL Lab](https://datacertification.fr/apprendre-sql?ref=idriss-benbassou.com) couvrent tous ces patterns avec un éditeur intégré et une validation automatique. Commence par l'exercice [ct-21](https://datacertification.fr/exercise/ct-21?ref=idriss-benbassou.com), c'est le pattern de référence.

Si tu travailles déjà sur des pipelines (qualité de données, modélisation, incrémental), le [Lab Analytics Engineering](https://datacertification.fr/analytics-engineering?ref=idriss-benbassou.com) applique la déduplication à des cas complets.

Si tu débutes sur SQL, voir le [guide des fondamentaux SQL](https://www.idriss-benbassou.com/apprendre-sql-guide-complet-fondamentaux/), les [window functions](https://www.idriss-benbassou.com/snowflake-sql-window-functions-over-partition-by-order-by-rows/), et la différence [CTE vs table dérivée](https://www.idriss-benbassou.com/sql-derived-tables-vs-cte/).

Tu veux que je t'accompagne sur un projet data (qualité de données, modélisation, performance SQL, Snowflake, dbt) ? 

👉 [Réserver un appel de 30 minutes](https://calendly.com/idriss-benbassou-datavio/30min?ref=idriss-benbassou.com)

## Questions fréquentes

#### Comment supprimer les doublons en SQL ?

Pour des lignes strictement identiques, `SELECT DISTINCT` peut suffire mais pour des doublons métier (même clé mais une colonne qui diffère), on utilise `ROW_NUMBER() OVER (PARTITION BY clé ORDER BY critère)` puis on garde les lignes où le numéro vaut 1\. Sur Snowflake, BigQuery, Databricks ou DuckDB, `QUALIFY` permet d'écrire la même chose sans CTE.

#### DISTINCT ou ROW\_NUMBER, lequel choisir ?

`DISTINCT` quand les lignes sont entièrement identiques et que tu n'as pas besoin de choisir laquelle garder. `ROW_NUMBER` dès que les lignes partagent une clé mais diffèrent sur une colonne, et que tu veux décider quelle ligne survit (la plus récente, la plus complète).

#### C'est quoi QUALIFY en SQL ?

`QUALIFY` filtre le résultat d'une window function directement dans la requête, sans passer par un CTE ou une sous-requête. C'est l'équivalent de `HAVING` pour les fonctions windows. Il est disponible sur Snowflake, BigQuery, Databricks, DuckDB et Teradata, mais pas sur PostgreSQL, MySQL, SQL Server ni SQLite.

#### Comment garder la ligne la plus récente par groupe ?

On numérote les lignes avec `ROW_NUMBER() OVER (PARTITION BY la_clé ORDER BY la_date DESC)`, puis on garde celles où le numéro vaut 1\. Le `PARTITION BY` définit le groupe, l'`ORDER BY ... DESC` met la ligne la plus récente en premier.

#### ROW\_NUMBER, RANK ou DENSE\_RANK pour dédoublonner ?

`ROW_NUMBER` pour de la déduplication stricte car il attribue un numéro unique, donc filtrer sur 1 garde exactement une ligne par groupe. `RANK` et `DENSE_RANK` donnent le même rang aux même lignes, ce qui peut garder plusieurs lignes. En cas d'égalité sur le critère de tri, ajoute une colonne (comme l'`id`) pour un résultat déterministe.

#### Comment détecter les doublons avant de les supprimer ?

Un `GROUP BY` sur la clé suspectée avec `HAVING COUNT(*) > 1` liste les valeurs en double et leur nombre d'occurrences. Pour voir les lignes concernées deux à deux, un self-join sur la clé fait le travail.

####