> ## 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.

# Apprendre le SQL : le guide complet des fondamentaux (data, 2026)
- URL: https://www.idriss-benbassou.com/apprendre-sql-guide-complet-fondamentaux/
- Published: 2026-06-20T16:33:48.000Z
- Updated: 2026-06-20T16:33:48.000Z
- Author: Idriss BENBASSOU
- Tags: SQL, #rating-1

Quand j'ai commencé en data en 2011 (oui, ça fait 15 ans, et non, ça ne me rajeunit pas 😅​), j'ai lu plusieurs cours SQL d'affilée. À la fin, je savais réciter la syntaxe d'un `SELECT`, mais devant une vraie table je restais bloqué. Le problème, ce n'est pas la syntaxe. Le vrai sujet, c'est de comprendre comment une requête raisonne, dans quel ordre elle lit les données, et pourquoi le même résultat peut s'écrire de plusieurs façons.

Pour être clair et honnête, tu peux lire tous les articles SQL du monde, tu ne seras jamais bon si tu ne pratiques pas et que tu n'écris pas toi-même les requêtes. C'est exactement pour ça que les exemples viennent tous du [SQL Lab de DataCertification](https://datacertification.fr/apprendre-sql?ref=idriss-benbassou.com), où tu peux écrire chaque requête dans ton navigateur et voir le résultat tout de suite. Pour les sujets plus avancés ([window functions](https://www.idriss-benbassou.com/snowflake-sql-window-functions-over-partition-by-order-by-rows/),[ joins spécialisés](https://www.idriss-benbassou.com/joins-specialises-sql-semi-anti-asof-lateral-full-outer-join/), CTE récursives), j'ai déjà des articles dédiés.

## La seule chose à comprendre avant tout le reste : l'ordre d'exécution

L'erreur qui bloque 90 % des débutants. On croit qu'une requête SQL s'exécute dans l'ordre où on l'écrit, de haut en bas. C'est faux.

Tu écris la requête dans cet ordre :

```sql
SELECT   ...
FROM     ...
WHERE    ...
GROUP BY ...
HAVING   ...
ORDER BY ...
LIMIT    ...

```

Mais la base de données, elle, l'exécute dans cet ordre :

| Étape | Clause      | Ce qu'elle fait                           |
| ----- | ----------- | ----------------------------------------- |
| 1     | FROM / JOIN | Choisit les tables et les relie           |
| 2     | WHERE       | Filtre les lignes, une par une            |
| 3     | GROUP BY    | Regroupe les lignes restantes             |
| 4     | HAVING      | Filtre les groupes                        |
| 5     | SELECT      | Choisit les colonnes et calcule les alias |
| 6     | ORDER BY    | Trie le résultat                          |
| 7     | LIMIT       | Coupe les N premières lignes              |

Une fois que tu as ça en tête, plein de comportements bizarres deviennent logiques :

- Tu ne peux pas filtrer un calcul d'agrégat (`COUNT`, `AVG`) dans le `WHERE`, parce que le `WHERE` s'exécute **avant** le regroupement. Pour ça, il y a `HAVING`.
- Tu ne peux pas réutiliser un alias défini dans le `SELECT` à l'intérieur du `WHERE`, parce que le `SELECT` passe **après** le `WHERE`. La colonne n'existe pas encore.
- Le `LIMIT` arrive en dernier, donc il s'applique au résultat déjà trié, pas aux lignes brutes.

Retiens cet ordre. C'est la clé qui débloque tout le reste de l'article.

## La table qu'on va utiliser

Les exemples ci-dessous viennent de plusieurs exercices du SQL Lab. Selon l’exercice, la table utilisée peut changer (`employees`, `products`, etc.). Chaque requête est pensée pour fonctionner dans le lab associé.

| id | name          | email            | salary | department\_id | hire\_date |
| -- | ------------- | ---------------- | ------ | -------------- | ---------- |
| 1  | Alice Martin  | alice@corp.com   | 55000  | 1              | 2022-01-15 |
| 2  | Bob Dupont    | bob@corp.com     | 62000  | 2              | 2021-06-01 |
| 3  | Charlie Leroy | charlie@corp.com | 48000  | 1              | 2023-03-20 |
| 4  | Diana Moreau  | diana@corp.com   | 71000  | 3              | 2020-09-10 |
| 5  | Eve Bernard   | eve@corp.com     | 53000  | 2              | 2022-11-05 |

## 1\. Lire des données : SELECT, ORDER BY, LIMIT

Tout commence par `SELECT`. Tu choisis les colonnes, tu pars d'une table avec `FROM`.

```sql
-- Toutes les colonnes
SELECT * FROM employees;

-- Seulement deux colonnes
SELECT name, salary FROM employees;

```

`SELECT *` retourne tout. Pratique pour explorer mais à éviter en production car tu transportes des colonnes dont tu n'as pas besoin. Nomme toujours les colonnes une fois que tu sais ce que tu veux.

[![DataCertification - Exercice SELECT ](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-40.png)](https://datacertification.fr/exercise/sb-01?ref=idriss-benbassou.com)

DataCertification - Exercice `SELECT`

Pour trier, c'est `ORDER BY`. Par défaut c'est croissant (`ASC`), tu ajoutes `DESC` pour l'inverse.

```sql
SELECT name, salary
FROM employees
ORDER BY salary DESC;

```

Résultat : Diana (71000) en haut, puis Bob, Alice, Eve, Charlie. Et si tu ne veux que le top 3, tu ajoutes `LIMIT 3`.

> 💡 **À toi de jouer** : [Tous les employés (sb-01)](https://datacertification.fr/exercise/sb-01?ref=idriss-benbassou.com) puis [Trier par salaire (sb-04)](https://datacertification.fr/exercise/sb-04?ref=idriss-benbassou.com) dans le module *Les bases SELECT*.

[![DataCertification - Exercice Order By](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-41.png)](https://datacertification.fr/exercise/sb-04?ref=idriss-benbassou.com)

DataCertification - Exercice Order By

## 2\. Filtrer les lignes : WHERE

`WHERE` garde uniquement les lignes qui respectent une condition.

```sql
SELECT *
FROM employees
WHERE salary > 55000;

```

Sur notre table, ça retourne Bob (62000) et Diana (71000). Les opérateurs de comparaison (`>`, `<`, `=`, `>=`, `<=`, `<>`) marchent aussi bien sur les nombres que sur les dates.

[![DataCertification - Exercice Where](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-42.png)](https://datacertification.fr/exercise/sb-03?ref=idriss-benbassou.com)

DataCertification - Exercice `Where`

Tu peux combiner les conditions avec `AND` et `OR`, et utiliser quelques opérateurs bien pratiques :

```sql
-- Plusieurs valeurs possibles
WHERE department_id IN (1, 2)

-- Un intervalle
WHERE salary BETWEEN 50000 AND 60000

-- Un motif de texte (% = n'importe quoi)
WHERE email LIKE '%@corp.com'

-- Une valeur manquante
WHERE email IS NULL

```

⚠️

Attention au dernier. On ne teste ****jamais** un `NULL` avec `=`. `WHERE email = NULL` ne renvoie rien, même s'il y a des valeurs nulles. Un `NULL` veut dire "inconnu", et inconnu n'est égal à rien, pas même à lui-même. Toujours `IS NULL` ou `IS NOT NULL`. Ce piège revient en permanence, j'en parle en détail dans [NOT IN vs NOT EXISTS : pourquoi le résultat diffère](https://www.idriss-benbassou.com/anti-joins-sql-not-in-not-exists-null/).

> 💡 **À toi de jouer** : [Filtrer par salaire (sb-03)](https://datacertification.fr/exercise/sb-03?ref=idriss-benbassou.com).

## 3\. Agréger : COUNT, SUM, AVG, GROUP BY, HAVING

Jusqu'ici, on lit des lignes. Maintenant on calcule sur des paquets de lignes et c'est le coeur de l'analyse des données.

Une fonction d'agrégation prend plusieurs lignes et renvoie une seule valeur :

```sql
SELECT COUNT(*) AS total_produits FROM products;
SELECT MIN(price) AS prix_min, MAX(price) AS prix_max FROM products;

```

[![Data Certification - Exercice d'agrégation](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-43.png)](https://datacertification.fr/exercise/fa-01?ref=idriss-benbassou.com)

Data Certification - Exercice d'agrégation

💡

Un point qui piège tout le monde : `COUNT(*)` compte toutes les lignes, `COUNT(colonne)` ignore les `NULL` de cette colonne. Deux résultats différents sur la même table.

Le vrai pouvoir arrive avec `GROUP BY`. Au lieu d'un seul total, tu obtiens un total par catégorie :

```sql
SELECT departement, AVG(salaire) AS salaire_moyen
FROM employees
GROUP BY departement;

```

Tu obtiens une ligne par département, avec son salaire moyen. La règle d'or est que toute colonne du `SELECT` qui n'est pas dans une fonction d'agrégation doit être dans le `GROUP BY`.

[![Data Certification - Exercice Group by](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-44.png)](https://datacertification.fr/exercise/gb-03?ref=idriss-benbassou.com)

Data Certification - Exercice Group by

Et si tu veux filtrer les groupes (par exemple, ne garder que les départements dont le salaire moyen dépasse 50000) ? Pas avec `WHERE`, car le `WHERE` filtre avant le regroupement, et donc il ne connaît pas encore la moyenne. C'est le rôle de `HAVING` :

```sql
SELECT departement, AVG(salaire) AS salaire_moyen
FROM employees
GROUP BY departement
HAVING AVG(salaire) > 50000;

```

`WHERE` filtre les lignes, `HAVING` filtre les groupes. Retiens cette phrase, elle résume 80 % des erreurs de débutant sur les agrégats.

> 💡 **À toi de jouer** : [Compter les produits (fa-01)](https://datacertification.fr/exercise/fa-01?ref=idriss-benbassou.com) puis [Salaire moyen par département (gb-03)](https://datacertification.fr/exercise/gb-03?ref=idriss-benbassou.com).

## 4\. Relier les tables : les JOINs

Dans la vraie vie, tes données sont éclatées sur plusieurs tables. Les employés d'un côté, les départements de l'autre, reliés par une clé (`department_id`). Le `JOIN` les recolle.

```sql
SELECT employees.name, departments.name AS department
FROM employees
INNER JOIN departments
  ON employees.department_id = departments.id;

```

`INNER JOIN` garde uniquement les lignes qui ont une correspondance des deux côtés. Un employé sans département valide disparaît du résultat.

[![Data Certification - Exercice Join](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-45.png)](https://datacertification.fr/exercise/jo-01?ref=idriss-benbassou.com)

Data Certification - Exercice Join

Les quatre joins de base à connaître :

| Join            | Ce qu'il garde                                                               |
| --------------- | ---------------------------------------------------------------------------- |
| INNER JOIN      | Seulement les lignes qui matchent des deux côtés                             |
| LEFT JOIN       | Toutes les lignes de gauche, complétées par la droite (NULL si pas de match) |
| RIGHT JOIN      | L'inverse du LEFT                                                            |
| FULL OUTER JOIN | Toutes les lignes des deux côtés                                             |

Dans 95 % des cas, tu utilises `INNER` ou `LEFT`. Le `LEFT JOIN` est ton ami quand tu veux garder tous les éléments d'une table même sans correspondance (tous les clients, y compris ceux qui n'ont jamais commandé).

⚠️

Le piège classique est c'est la duplication. Si un employé a plusieurs lignes correspondantes à droite, il apparaît plusieurs fois, et tes totaux explosent. Donc quand un `SUM` te sort un chiffre trois fois trop grand, c'est presque toujours ça.

Une fois les joins de base maîtrisés, il y a tout un monde de joins spécialisés (SEMI, ANTI, ASOF, LATERAL) qui résolvent des cas que les joins classiques galèrent à traiter. Je les détaille ici : [JOINs spécialisés en SQL : SEMI, ANTI, ASOF, LATERAL et FULL OUTER](https://www.idriss-benbassou.com/joins-specialises-sql-semi-anti-asof-lateral-full-outer-join/).

> 💡 **À toi de jouer** : [Premier INNER JOIN (jo-01)](https://datacertification.fr/exercise/jo-01?ref=idriss-benbassou.com).

## 5\. La logique conditionnelle et les NULL : CASE, COALESCE

`CASE` est le if/else du SQL. Il crée une colonne dont la valeur dépend de conditions.

```sql
SELECT name, salary,
  CASE
    WHEN salary < 35000 THEN 'Junior'
    WHEN salary <= 45000 THEN 'Confirmé'
    ELSE 'Senior'
  END AS niveau
FROM employees;

```

Les conditions sont évaluées dans l'ordre, et la première qui est vraie gagne. `ELSE` ramasse tout le reste. C'est l'outil que tu utilises pour transformer de la donnée brute en catégories métier.

[![Data Certification - Exercice Case](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-46.png)](https://datacertification.fr/exercise/cc-01?ref=idriss-benbassou.com)

Data Certification - Exercice Case

`COALESCE`, lui, gère les `NULL`. Il prend une liste de valeurs et renvoie la première qui n'est pas nulle :

```sql
SELECT name, COALESCE(phone, 'Non renseigné') AS telephone
FROM employees;

```

Si `phone` est `NULL`, tu affiches "Non renseigné" au lieu d'une case vide. Indispensable dès que tu prépares des données pour un dashboard car des fois un `NULL` qui traîne dans un rapport, c'est moche et ça fausse les calculs.

> 💡 **À toi de jouer** : [Premier CASE WHEN (cc-01)](https://datacertification.fr/exercise/cc-01?ref=idriss-benbassou.com) puis [COALESCE : remplacer les NULL (cc-03)](https://datacertification.fr/exercise/cc-03?ref=idriss-benbassou.com).

## 6\. Structurer ses requêtes : sous-requêtes et CTE

Une sous-requête, c'est une requête à l'intérieur d'une autre. Le cas le plus courant est de comparer chaque ligne à un calcul global.

```sql
SELECT *
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);

```

La sous-requête calcule le salaire moyen, et la requête principale garde les employés au-dessus. La base exécute d'abord la parenthèse, puis le reste.

[![Data Certification - Exercice sous-requêtes](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-47.png)](https://datacertification.fr/exercise/sq-01?ref=idriss-benbassou.com)

Data Certification - Exercice sous-requêtes

Le problème des sous-requêtes, c'est qu'empilées, elles deviennent illisibles. C'est là qu'arrivent [les CTE (Common Table Expressions)](https://www.idriss-benbassou.com/sql-derived-tables-vs-cte/). Tu déclares une sous-requête nommée avec `WITH`, puis tu l'utilises comme si c'était une table :

```sql
WITH hauts_salaires AS (
  SELECT * FROM employees WHERE salary > 50000
)
SELECT * FROM hauts_salaires;

```

Même résultat qu'une sous-requête, mais ta logique est découpée en blocs nommés que tu lis de haut en bas. Sur une requête complexe, c'est le jour et la nuit. Je compare les deux approches en détail dans [CTE vs table dérivée en SQL](https://www.idriss-benbassou.com/sql-derived-tables-vs-cte/).

> 💡 **À toi de jouer** : [Salaire supérieur à la moyenne (sq-01)](https://datacertification.fr/exercise/sq-01?ref=idriss-benbassou.com) puis [Premier CTE simple (ct-01)](https://datacertification.fr/exercise/ct-01?ref=idriss-benbassou.com).

## 7\. Le palier au-dessus : les window functions

Dernier fondamental avant le niveau avancé, et le plus puissant. Une window function calcule sur un groupe de lignes sans les fusionner. Contrairement à `GROUP BY` qui réduit dix lignes en une, elle garde tes dix lignes et ajoute une colonne calculée.

```sql
SELECT name, salary,
  ROW_NUMBER() OVER (ORDER BY salary DESC) AS rang
FROM employees;

```

Chaque employé garde sa ligne, et reçoit en plus son rang de salaire. C'est ce qui débloque les classements, les totaux cumulés, les comparaisons d'une ligne à la précédente, la déduplication propre etc..

[![Data Certification - Exercice window functions](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-48.png)](https://datacertification.fr/exercise/wf-01?ref=idriss-benbassou.com)

Data Certification - Exercice window functions

Les window functions méritent un article à elles seules : [Window Functions SQL : OVER, PARTITION BY, ROW\_NUMBER, LAG (8 exemples)](https://www.idriss-benbassou.com/snowflake-sql-window-functions-over-partition-by-order-by-rows/). 

Commence par l'exercice ci-dessous, puis enchaîne avec l'article quand tu veux aller plus loin.

> 💡 **À toi de jouer** : [Numéroter les employés (wf-01)](https://datacertification.fr/exercise/wf-01?ref=idriss-benbassou.com).

## Quelques bonnes pratiques à adopter

Du SQL qui marche, c'est bien. Du SQL que ton toi du futur (ou ton collègue) relit sans souffrir, c'est mieux. Quelques habitudes qui font la différence :

- `SELECT *` en production. Pratique pour explorer, mais en prod tu récupères des colonnes inutiles. Nomme tes colonnes.
- Empiler les sous-requêtes. Pour une requête lisible et maintenable, privilégie les CTE.
- L'ancienne syntaxe de jointure **`FROM a, b WHERE a.id = b.id`.** Ça marche, mais c'est illisible, et un oubli de condition te fait un produit cartésien. Écris des `JOIN ... ON` explicites.
- Le **`GROUP BY 1, 2`** par numéro de colonne. Rapide à taper, fragile à maintenir car dès que tu changes l'ordre du `SELECT`, le regroupement change en silence. Nomme les colonnes.
- Les alias cryptiques comme `AS ca`, `AS t1`, `AS x` te font gagner trois secondes et en perdre dix au relecteur. Donne des noms parlants.
- La logique dupliquée. Si tu copies-colles le même bloc de calcul deux fois, factorise-le dans une CTE. Une seule source de vérité, une seule chose à corriger.

## Comment vraiment progresser

Lire ce guide te donne la carte. Mais le SQL ne s'apprend pas en lisant, il s'apprend en écrivant des requêtes en se trompant, et le plus important en comprenant pourquoi. C'est exactement pour ça que j'ai construit le [SQL Lab](https://datacertification.fr/sql?ref=idriss-benbassou.com), un éditeur intégré au navigateur, des exercices progressifs, et une validation automatique qui te dit tout de suite si ta requête est juste. Rien à installer.

Quand tu seras à l'aise avec ces fondamentaux, deux directions :

- Le [Lab Analytics Engineering](https://datacertification.fr/analytics-engineering?ref=idriss-benbassou.com) pour appliquer le SQL à des workflows complets : modélisation, qualité de données, déduplication, SCD, incrémental, cohortes.
- Mes articles avancés sur les [window functions](https://www.idriss-benbassou.com/snowflake-sql-window-functions-over-partition-by-order-by-rows/), les [joins spécialisés](https://www.idriss-benbassou.com/joins-specialises-sql-semi-anti-asof-lateral-full-outer-join/), le [MERGE](https://www.idriss-benbassou.com/merge-sql-insert-update-delete-en-une-requete-snowflake-dbt-bigquery/) et les [SCD](https://www.idriss-benbassou.com/scd-slowly-changing-dimensions-type-1-2-3-6-sql-dbt-snowflake/).

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

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

## Questions fréquentes

#### Combien de temps faut-il pour apprendre le SQL ?

Les bases (SELECT, WHERE, ORDER BY, agrégations, JOINs) s'acquièrent en quelques jours de pratique régulière. Tu es opérationnel sur des requêtes utiles en une à deux semaines. La maîtrise des sujets avancés (window functions, optimisation, CTE récursives) demande plus de temps, mais tu peux déjà travailler sérieusement avec les fondamentaux.

#### Par où commencer pour apprendre SQL quand on débute ?

Commence par comprendre l'ordre d'exécution d'une requête, puis SELECT, WHERE et ORDER BY. Ajoute ensuite les agrégations (COUNT, SUM, AVG, GROUP BY), puis les JOINs. C'est l'ordre suivi dans ce guide et dans le SQL Lab. ****Le plus important est de pratiquer en écrivant des requêtes, pas seulement en lisant.**

#### Faut-il installer une base de données pour apprendre SQL ?

Non. Tu peux écrire et exécuter des requêtes directement dans le navigateur avec le SQL Lab de DataCertification, sans installer PostgreSQL, MySQL ou SQLite. C'est la façon la plus rapide de commencer à pratiquer.

#### Quelle est la différence entre WHERE et HAVING ?

`WHERE` filtre les lignes avant le regroupement. `HAVING` filtre les groupes après le `GROUP BY`. On utilise `HAVING` quand la condition porte sur un résultat d'agrégation, comme garder uniquement les départements dont le salaire moyen dépasse un seuil.

#### Le SQL est-il utile pour un data analyst ou un data engineer ?

Oui, c'est la compétence de base des deux métiers. Un data analyst écrit des requêtes pour explorer et restituer la donnée, un data engineer construit des pipelines de transformation en SQL. Quel que soit l'outil (Snowflake, BigQuery, dbt, Power BI), tout repose sur SQL.

#### Quelle différence entre SELECT \* et nommer les colonnes ?

`SELECT *` retourne toutes les colonnes, pratique pour explorer une table. En production, on nomme les colonnes pour ne avoir que ce dont on a besoin.