Les 20 questions SQL les plus posées en entretien d'embauche
Découvrez les 20 questions SQL les plus fréquentes en entretien technique avec les réponses détaillées et les pièges à connaître pour chaque question.
Thomas Leroy
Après avoir accompagné plus de 500 candidats en préparation d'entretiens SQL, Thomas Leroy a identifié les 20 questions qui reviennent le plus souvent — chez les startups comme dans les grands groupes. Chaque question est présentée avec sa réponse, une explication et les pièges à connaître.
Ces 20 questions sont classées par niveau de difficulté : des fondamentaux aux sujets avancés.
📌 Ce qu'il faut retenir
Les 5 premières questions sont des éliminatoires — une erreur est rédhibitoire
Les questions 6-12 sont le cœur de l'évaluation pour la majorité des postes
Les questions 13-20 vous distinguent des autres candidats
Connaître la réponse ne suffit pas — il faut savoir expliquer et identifier les pièges
Niveau débutant
Question 1 : Quelle est la différence entre WHERE et HAVING ?
Réponse attendue :
WHERE filtre les lignes individuelles avant le regroupement (GROUP BY)
HAVING filtre les groupes après le regroupement
-- WHERE : filtre les commandes > 100 avant de grouperSELECT client, SUM(montant) FROM commandes
WHERE montant >100GROUPBY client;
-- HAVING : filtre les groupes dont le total > 500SELECT client, SUM(montant) AS total FROM commandes
GROUPBY client
HAVINGSUM(montant) >500;
<div class="callout callout-warning"><p class="callout-title">⚠️ Attention</p><p>Piège : mettre une condition d'agrégation dans WHERE (`WHERE SUM(montant) > 500`) provoque une erreur.</p>
</div>
Pour approfondir, consultez notre [guide GROUP BY et HAVING](/group-by-having-sql-guide-exercices).
### Question 2 : Quelle est la différence entre INNER JOIN et LEFT JOIN ?
**Réponse attendue** :
- `INNER JOIN` ne retourne que les lignes ayant une correspondance dans les deux tables
- `LEFT JOIN` retourne toutes les lignes de la table de gauche, avec NULL pour les colonnes de droite quand il n'y a pas de correspondance
```sql-- INNER JOIN : seulement les clients avec commandesSELECT c.nom, co.montant
FROM clients c INNERJOIN commandes co ON c.id = co.client_id;
-- LEFT JOIN : tous les clients, même sans commandeSELECT c.nom, co.montant
FROM clients c LEFTJOIN commandes co ON c.id = co.client_id;
<div class="callout callout-warning"><p class="callout-title">⚠️ Attention</p><p>Piège : un `WHERE` sur une colonne de la table de droite transforme un LEFTJOIN en INNER JOIN.</p></div>
Voir notre [guide complet des JOINs](/joins-sql-inner-left-right-full-exercices).
### Question 3 : Quelle est la différence entre COUNT(*), COUNT(col) et COUNT(DISTINCT col) ?
**Réponse attendue** :
- `COUNT(*)` : nombre total de lignes (y compris NULL)
- `COUNT(col)` : nombre de valeurs non-NULL dans la colonne
- `COUNT(DISTINCT col)` : nombre de valeurs **uniques** non-NULL
```sqlSELECTCOUNT(*) AS total_lignes, -- 5COUNT(email) AS emails_remplis, -- 4 (1 NULL)COUNT(DISTINCT email) AS emails_uniques -- 3 (1 doublon)FROM utilisateurs;
💡 Bon à savoir
Cette distinction est cruciale pour analyser la qualité des données. Prenons l'exemple de Sarah, data analyst chez E-commerce Pro : elle utilise COUNT(*) pour compter les commandes totales, COUNT(code_promo) pour voir combien ont utilisé un code promo, et COUNT(DISTINCT client_id) pour identifier les clients uniques.
Question 4 : Comment trouver les doublons dans une table ?
Réponse attendue :
SELECT email, COUNT(*) AS nb
FROM utilisateurs
GROUPBY email
HAVINGCOUNT(*) >1;
Pour lister les lignes en doublon avec leur détail :
WITH doublons AS (
SELECT*, ROW_NUMBER() OVER (PARTITIONBY email ORDERBY id) AS rn
FROM utilisateurs
)
SELECT*FROM doublons WHERE rn >1;
💡 Bon à savoir
La détection de doublons varie selon le contexte métier. Par exemple, chez Banque Digitale, Marc vérifie les doublons sur (nom, prénom, date_naissance) car un même email peut légitimement appartenir à plusieurs membres d'une famille.
Question 5 : Quelle est la différence entre DELETE, TRUNCATE et DROP ?
Réponse attendue :
Commande
Effet
Rollback possible
WHERE possible
Réinitialise auto-increment
DELETE
Supprime des lignes
Oui
Oui
Non
TRUNCATE
Supprime toutes les lignes
Non (en général)
Non
Oui
DROP
Supprime la table entière
Non
Non
N/A
Erreurs fréquentes des débutants
Basé sur mon expérience de formateur, voici les erreurs les plus courantes :
Oublier GROUP BY après SELECT avec agrégation
Confondre JOIN et WHERE pour les conditions de jointure
Utiliser COUNT(*) au lieu de COUNT(colonne) pour compter les valeurs non-nulles
Oublier DISTINCT dans les sous-requêtes avec EXISTS
💡 Bon à savoir
Exemple concret : Julie, développeuse junior chez FinTech Solutions, a appris à ses dépens qu'un DELETE sans WHERE supprime TOUTE la table. Depuis, elle utilise systématiquement BEGIN TRANSACTION et vérifie avec SELECT avant de valider par COMMIT.
Pièges spécifiques aux débutants en entretien
D'après les retours de 150+ recruteurs, voici les erreurs éliminatoires :
Confusion sur les NULL : ne pas comprendre que WHERE colonne != 'valeur' exclut les NULL
Mauvaise syntaxe des agrégations : écrire SELECT nom, COUNT(*) sans GROUP BY
Confondre les types de JOIN : utiliser INNER JOIN quand on veut toutes les lignes de la table principale
Ne pas tester mentalement sur un petit jeu de données avant de donner la réponse
Validation des bases - questions bonus
Pour vous auto-évaluer sur le niveau débutant :
Différence entre NULL et chaîne vide : NULL signifie "valeur inconnue", '' est une chaîne de longueur zéro
Ordre d'exécution SQL : FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
Utilisation de DISTINCT : élimine les doublons sur l'ensemble des colonnes sélectionnées
Niveau intermédiaire
Question 6 : Comment trouver le N-ième salaire le plus élevé ?
Réponse attendue :
-- Avec DENSE_RANK (gère les doublons)WITH salaires_classes AS (
SELECT salaire, DENSE_RANK() OVER (ORDERBY salaire DESC) AS rang
FROM employes
)
SELECTDISTINCT salaire FROM salaires_classes WHERE rang =2;
⚠️ Attention
Piège : utiliser `RANK()` au lieu de `DENSE_RANK()` peut retourner un résultat vide si le salaire le plus élevé a des doublons.
Approche alternative avec LIMIT/OFFSET (moins robuste) :
Question 7 : Comment fonctionne une sous-requête corrélée ?
Réponse attendue :
Une sous-requête corrélée fait référence à une colonne de la requête externe et est exécutée une fois pour chaque ligne de la requête externe.
-- Employés gagnant plus que la moyenne de leur départementSELECT e.nom, e.salaire
FROM employes e
WHERE e.salaire > (
SELECTAVG(e2.salaire) FROM employes e2
WHERE e2.departement_id = e.departement_id
);
⚠️ Attention
Piège : la performance peut être mauvaise sur de gros volumes car la sous-requête est exécutée N fois.
Alternative plus performante avec window function :
WITH avg_dept AS (
SELECT nom, salaire,
AVG(salaire) OVER (PARTITIONBY departement_id) AS avg_dept_salaire
FROM employes
)
SELECT nom, salaire FROM avg_dept WHERE salaire > avg_dept_salaire;
Question 8 : Quelle est la différence entre UNION et UNION ALL ?
Réponse attendue :
UNION : combine les résultats et supprime les doublons (tri implicite)
UNION ALL : combine les résultats et garde les doublons (pas de tri)
-- UNION : 3 lignes si des doublons existentSELECT nom FROM clients_france
UNIONSELECT nom FROM clients_belgique;
-- UNION ALL : toutes les lignes, doublons inclusSELECT nom FROM clients_france
UNIONALLSELECT nom FROM clients_belgique;
💡 Bon à savoir
Piège : `UNION ALL` est toujours plus performant. Utilisez `UNION` uniquement si vous avez besoin de dédupliquer. Exemple concret : Lucas, BI developer chez Retail Group, utilise UNION ALL pour agréger les ventes de 15 filiales (120M de lignes/mois), puis applique DISTINCT seulement sur le résultat final pour optimiser les performances.
Question 9 : Comment écrire un CASE WHEN avec agrégation ?
Réponse attendue :
SELECT
departement,
COUNT(CASEWHEN genre ='F'THEN1END) AS nb_femmes,
COUNT(CASEWHEN genre ='M'THEN1END) AS nb_hommes,
ROUND(
100.0*COUNT(CASEWHEN genre ='F'THEN1END) /COUNT(*), 1
) AS pct_femmes
FROM employes
GROUPBY departement;
Pattern avancé pour les métriques métier :
SELECT
DATE_TRUNC('month', date_commande) AS mois,
COUNT(*) AS total_commandes,
COUNT(CASEWHEN montant >100THEN1END) AS commandes_premium,
SUM(CASEWHEN statut ='remboursee'THEN montant ELSE0END) AS ca_rembourse,
AVG(CASEWHEN canal ='web'THEN montant END) AS panier_moyen_web
FROM commandes
GROUPBY DATE_TRUNC('month', date_commande);
💡 Bon à savoir
Cette technique est particulièrement utile pour créer des tableaux de bord. Chez Retail Analytics, Emma utilise cette approche pour analyser les ventes par région et catégorie en une seule requête, évitant ainsi de multiples appels depuis son dashboard Power BI.
Question 10 : Qu'est-ce qu'une CTE et quand l'utiliser ?
Réponse attendue :
Une CTE (Common Table Expression) est une requête nommée temporaire définie avec WITH. Elle améliore la lisibilité et peut être référencée plusieurs fois.
WITH ventes_mensuelles AS (
SELECT
DATE_TRUNC('month', date_vente) AS mois,
SUM(montant) AS total
FROM ventes GROUPBY1
)
SELECT mois, total,
LAG(total) OVER (ORDERBY mois) AS mois_precedent,
ROUND(100.0* (total -LAG(total) OVER (ORDERBY mois)) /LAG(total) OVER (ORDERBY mois), 1) AS croissance_pct
FROM ventes_mensuelles;
Question 11 : Comment trouver les clients sans commande ?
Réponse attendue — trois approches :
-- 1. LEFT JOIN + IS NULL (anti-join) - souvent le plus rapideSELECT c.nom FROM clients c
LEFTJOIN commandes co ON c.id = co.client_id
WHERE co.id ISNULL;
-- 2. NOT EXISTS - performant sur gros volumesSELECT c.nom FROM clients c
WHERENOTEXISTS (SELECT1FROM commandes co WHERE co.client_id = c.id);
-- 3. NOT IN (attention aux NULL !)SELECT nom FROM clients
WHERE id NOTIN (SELECT client_id FROM commandes WHERE client_id ISNOT NULL);
⚠️ Attention
Piège : `NOT IN` avec des NULL dans la sous-requête retourne 0 lignes. Si la colonne client_id peut contenir des NULL, ajoutez toujours `WHERE client_id IS NOT NULL`.
Cas d'usage réel : identifier les prospects inactifs pour les campagnes de relance.
Question 12 : Comment calculer un cumul (running total) ?
Réponse attendue :
SELECT
date_vente,
montant,
SUM(montant) OVER (ORDERBY date_vente) AS cumul,
SUM(montant) OVER (ORDERBY date_vente ROWSBETWEEN UNBOUNDED PRECEDING ANDCURRENTROW) AS cumul_explicite
FROM ventes;
Pour un cumul par groupe (ex: par vendeur) :
SELECT
vendeur,
date_vente,
montant,
SUM(montant) OVER (PARTITIONBY vendeur ORDERBY date_vente) AS cumul_vendeur
FROM ventes;
La fenêtre par défaut avec ORDER BY est ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, ce qui produit un cumul.
Validation des acquis - métriques métier courantes
Pour maîtriser le niveau intermédiaire, sachez calculer :
Taux de conversion : COUNT(CASE WHEN achat THEN 1 END) * 100.0 / COUNT(*)
Évolution période : (valeur_actuelle - valeur_precedente) * 100.0 / valeur_precedente
Top N par groupe : ROW_NUMBER() OVER (PARTITION BY groupe ORDER BY metrique DESC) <= 3
Spécificités par secteur d'activité
D'après mon expérience avec différents clients :
E-commerce : Focus sur les analyses de cohortes, calculs de panier moyen, taux de conversion par canal
Finance : Emphasis sur les window functions, calculs de volatilité, gestion des données temporelles
SaaS : Questions sur la rétention, MRR (Monthly Recurring Revenue), analyses de funnel
Santé : Requêtes sur les données longitudinales, respect des contraintes RGPD, anonymisation
Niveau avancé
Question 13 : Expliquez ROW_NUMBER, RANK et DENSE_RANK
Réponse attendue :
Les trois attribuent un numéro de classement, mais diffèrent pour les ex æquo :
ROW_NUMBER : numéro unique, pas d'ex æquo
RANK : même rang pour les ex æquo, avec saut (1, 2, 2, 4)
DENSE_RANK : même rang pour les ex æquo, sans saut (1, 2, 2, 3)
SELECT
nom,
salaire,
ROW_NUMBER() OVER (ORDERBY salaire DESC) AS row_num,
RANK() OVER (ORDERBY salaire DESC) AS rank_val,
DENSE_RANK() OVER (ORDERBY salaire DESC) AS dense_rank_val
FROM employes;
Question 14 : Comment calculer un taux de rétention mois par mois ?
Réponse attendue :
WITH premiere_activite AS (
SELECT
utilisateur_id,
DATE_TRUNC('month', MIN(date_activite)) AS mois_inscription
FROM activites
GROUPBY utilisateur_id
),
activite_mensuelle AS (
SELECT
utilisateur_id,
DATE_TRUNC('month', date_activite) AS mois_activite
FROM activites
GROUPBY utilisateur_id, DATE_TRUNC('month', date_activite)
),
cohorte_retention AS (
SELECT
p.mois_inscription,
a.mois_activite,
EXTRACT(EPOCH FROM a.mois_activite - p.mois_inscription) / (30.44*24*3600) AS mois_depuis_inscription,
COUNT(DISTINCT a.utilisateur_id) AS utilisateurs_actifs,
(SELECTCOUNT(DISTINCT utilisateur_id) FROM premiere_activite WHERE mois_inscription = p.mois_inscription) AS cohorte_taille
FROM premiere_activite p
INNERJOIN activite_mensuelle a ON p.utilisateur_id = a.utilisateur_id
GROUPBY p.mois_inscription, a.mois_activite
)
SELECT
mois_inscription,
ROUND(mois_depuis_inscription) AS mois_N,
utilisateurs_actifs,
cohorte_taille,
ROUND(100.0* utilisateurs_actifs / cohorte_taille, 1) AS taux_retention
FROM cohorte_retention
ORDERBY mois_inscription, mois_depuis_inscription;
💡 Bon à savoir
Cette requête est un classique dans les entreprises SaaS. Chez StreamApp, Maxime a optimisé cette analyse en pré-calculant les cohortes dans une table dédiée, mise à jour quotidiennement, réduisant le temps d'exécution de 45s à 800ms sur 50M d'événements.
Question 15 : Comment optimiser une requête lente ?
Réponse attendue :
Méthode systématique :
Analyser le plan d'exécution : EXPLAIN ANALYZE
Identifier les goulots : Seq Scan, Hash Join coûteux, tris lents
Optimiser par ordre de priorité :
Index sur les colonnes de WHERE et JOIN
Réécrire les sous-requêtes corrélées
Limiter les colonnes sélectionnées
Utiliser les statistiques à jour
-- Avant : requête lenteSELECT*FROM commandes c, clients cl
WHERE c.client_id = cl.id
ANDYEAR(c.date_commande) =2024ANDUPPER(cl.pays) ='FRANCE';
-- Après : optimiséSELECT c.id, c.montant, cl.nom
FROM commandes c
INNERJOIN clients cl ON c.client_id = cl.id
WHERE c.date_commande >='2024-01-01'AND c.date_commande <'2025-01-01'AND cl.pays ='france';
-- Index suggérés :-- CREATE INDEX idx_commandes_date_client ON commandes(date_commande, client_id);-- CREATE INDEX idx_clients_pays ON clients(pays);
💡 Bon à savoir
Cas réel : Antoine, senior data engineer chez Logistics Corp, a divisé par 50 le temps d'exécution d'une requête en remplaçant une sous-requête corrélée par un EXISTS et en ajoutant un index composite sur (date_livraison, statut). De 2 minutes à 2,4 secondes sur 10 millions de lignes.
Une CTE récursive se référence elle-même pour traverser des structures hiérarchiques (arbre managérial, catégories imbriquées, graphes).
Structure : partie d'ancrage (non récursive) + partie récursive + condition d'arrêt.
WITHRECURSIVE hierarchie AS (
-- Partie d'ancrage : nœuds racineSELECT id, nom, manager_id, 0AS niveau, ARRAY[id] AS chemin
FROM employes WHERE manager_id ISNULLUNIONALL-- Partie récursiveSELECT e.id, e.nom, e.manager_id, h.niveau +1, h.chemin || e.id
FROM employes e
INNERJOIN hierarchie h ON e.manager_id = h.id
WHERE e.id !=ALL(h.chemin) -- Évite les cyclesAND h.niveau <10-- Limite de profondeur
)
SELECT niveau, REPEAT(' ', niveau) || nom AS nom_indente, chemin
FROM hierarchie
ORDERBY chemin;
⚠️ Attention
Les CTE récursives nécessitent une condition d'arrêt pour éviter les boucles infinies. Toujours inclure une limitation par niveau ou par profondeur maximale, et détecter les cycles si les données peuvent en contenir.
Cas d'usage :
Organigrammes d'entreprise
Arborescences de catégories e-commerce
Calcul de plus courts chemins
Déploiement de droits par héritage
Question 17 : Comment faire un pivot en SQL ?
Réponse attendue :
-- Pivot manuel avec CASE WHEN (le plus compatible)SELECT
vendeur,
SUM(CASEWHEN trimestre ='Q1'THEN montant ELSE0END) AS q1,
SUM(CASEWHEN trimestre ='Q2'THEN montant ELSE0END) AS q2,
SUM(CASEWHEN trimestre ='Q3'THEN montant ELSE0END) AS q3,
SUM(CASEWHEN trimestre ='Q4'THEN montant ELSE0END) AS q4,
SUM(montant) AS total_annuel
FROM ventes
GROUPBY vendeur;
Pivot dynamique (PostgreSQL avec crosstab) :
-- Nécessite l'extension tablefuncCREATE EXTENSION IF NOTEXISTS tablefunc;
SELECT*FROM crosstab(
'SELECT vendeur, trimestre, SUM(montant) FROM ventes GROUP BY 1, 2 ORDER BY 1, 2',
'VALUES (''Q1''), (''Q2''), (''Q3''), (''Q4'')'
) AS pivot(vendeur text, Q1 numeric, Q2 numeric, Q3 numeric, Q4 numeric);
Anti-pivot (UNPIVOT) :
-- Transformer colonnes en lignesSELECT vendeur, trimestre, montant
FROM (
SELECT vendeur, 'Q1'AS trimestre, q1 AS montant FROM ventes_pivot
UNIONALLSELECT vendeur, 'Q2', q2 FROM ventes_pivot
UNIONALLSELECT vendeur, 'Q3', q3 FROM ventes_pivot
UNIONALLSELECT vendeur, 'Q4', q4 FROM ventes_pivot
) unpivot
WHERE montant ISNOT NULL;
C'est le pattern CASE WHEN + SUM + GROUP BY — le plus polyvalent et compatible avec tous les SGBD.
Question 18 : Quelle est la différence entre une vue et une vue matérialisée ?
Réponse attendue :
Critère
Vue
Vue matérialisée
Stockage
Pas de données stockées
Données stockées physiquement
Performance
Recalculée à chaque appel
Lecture directe
Fraîcheur
Toujours à jour
Nécessite un REFRESH
Index possible
Non
Oui
Espace disque
Aucun
Proportionnel aux données
Quand utiliser une vue matérialisée :
Requêtes complexes exécutées fréquemment
Agrégations coûteuses sur de gros volumes
Données qui changent peu souvent
Besoins de performance sur des dashboards
Exemple PostgreSQL :
-- CréationCREATE MATERIALIZED VIEW ventes_mensuelles ASSELECT
DATE_TRUNC('month', date_vente) AS mois,
SUM(montant) AS total,
COUNT(*) AS nb_transactions
FROM ventes
GROUPBY DATE_TRUNC('month', date_vente);
-- Index sur la vue matérialiséeCREATE INDEX idx_vm_mois ON ventes_mensuelles(mois);
-- Rafraîchissement
REFRESH MATERIALIZED VIEW ventes_mensuelles;
-- Rafraîchissement sans bloquer les lectures
REFRESH MATERIALIZED VIEW CONCURRENTLY ventes_mensuelles;
⚠️ Attention
Le REFRESH CONCURRENTLY nécessite un index unique sur la vue matérialisée. Il est plus lent mais ne bloque pas les lectures pendant la mise à jour.
Question 19 : Comment gérer les transactions et les locks ?
Réponse attendue :
Les transactions groupent plusieurs opérations en une unité atomique (tout ou rien). Les locks évitent les conditions de course.
-- Transaction basiqueBEGIN;
UPDATE comptes SET solde = solde -100WHERE id =1;
UPDATE comptes SET solde = solde +100WHERE id =2;
COMMIT; -- Ou ROLLBACK en cas d'erreur-- Niveaux d'isolation (PostgreSQL)SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -- Lecture saleSET TRANSACTION ISOLATION LEVEL READ COMMITTED; -- Par défautSET TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- Lecture répétableSET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- Sérialisation complète-- Verrous explicitesBEGIN;
SELECT*FROM comptes WHERE id =1FORUPDATE; -- Lock exclusif-- ... opérations ...COMMIT;
Niveaux de lock courants :
FOR UPDATE : verrou exclusif (blocage total)
FOR SHARE : verrou partagé (plusieurs lecteurs, bloque les écrivains)
FOR UPDATE SKIP LOCKED : ignore les lignes verrouillées
💡 Bon à savoir
Exemple banquier : Christophe, responsable infrastructure chez BankSys, utilise SERIALIZABLE pour les virements de gros montants et READ COMMITTED pour les mises à jour de solde quotidiennes. Le coût de performance est justifié par la garantie d'atomicité.
Voir Transactions et locks en SQL.
Question 20 : Qu'est-ce que le sharding et pourquoi le faire ?
Réponse attendue :
Le sharding consiste à diviser les données horizontalement sur plusieurs instances de base de données, chaque shard stockant une partie des données selon une clé de partitionnement.
Exemple : table clients avec 100M de lignes → 4 shards par range d'ID :
Shard 1 : IDs 1-25M
Shard 2 : IDs 25M-50M
Shard 3 : IDs 50M-75M
Shard 4 : IDs 75M-100M
Implémentation simple :
-- Déterminer le shardSELECT shard_id = (client_id %4) +1;
-- Partitionnement natif (PostgreSQL 11+)CREATE TABLE clients (
id BIGINT,
nom TEXT,
email TEXT
) PARTITIONBYRANGE (id);
CREATE TABLE clients_shard1 PARTITIONOF clients
FORVALUESFROM (1) TO (25000001);
CREATE TABLE clients_shard2 PARTITIONOF clients
FORVALUESFROM (25000001) TO (50000001);
-- ... etc
Avantages du sharding :
Augmente la capacité bien au-delà d'une seule machine
Améliore les performances en réduisant les données par requête
Permet une scalabilité horizontale linéaire
Inconvénients :
Requêtes multi-shards complexes
Rééquilibrage difficile en cas de croissance inégale
Problèmes de joins cross-shard
Augmente la complexité opérationnelle
⚠️ Attention
Le sharding ne devrait être envisagé que si vous avez réellement besoin de scalabilité horizontale (>1TB de données, >10k requêtes/sec). 99% des applications n'en ont pas besoin.
💡 Bon à savoir
Cas réel : chez TechGiant, Diane a d'abord amélioré les index et optimisé les requêtes (X10 de performance), puis du compression des données (X3), avant d'implémenter le sharding en dernier recours. Le partitionnement natif de PostgreSQL a résolu 80% des problèmes.
Tableau récapitulatif des 20 questions par niveau
Niveau
Numéro
Question
Compétence clé
Débutant
1
WHERE vs HAVING
Filtrage et agrégation
Débutant
2
INNER vs LEFT JOIN
Jointures
Débutant
3
COUNT variations
Fonctions d'agrégation
Débutant
4
Trouver doublons
GROUP BY + HAVING
Débutant
5
DELETE vs TRUNCATE vs DROP
Suppression de données
Intermédiaire
6
N-ième salaire max
Window functions
Intermédiaire
7
Sous-requête corrélée
Sous-requêtes
Intermédiaire
8
UNION vs UNION ALL
Combinaison de résultats
Intermédiaire
9
CASE WHEN + agrégation
Agrégations conditionnelles
Intermédiaire
10
CTE (WITH)
Requêtes nommées
Intermédiaire
11
Anti-join (sans match)
Jointures avancées
Intermédiaire
12
Running total (cumul)
Window functions avancées
Avancé
13
ROW_NUMBER vs RANK vs DENSE_RANK
Classement
Avancé
14
Taux de rétention
Analyse de cohortes
Avancé
15
Optimiser requête lente
Performance et indexation
Avancé
16
CTE récursive
Structures hiérarchiques
Avancé
17
Pivot et unpivot
Restructuration de données
Avancé
18
Vue vs vue matérialisée
Objets de base de données
Avancé
19
Transactions et locks
Concurrence et intégrité
Avancé
20
Sharding
Scalabilité horizontale
Conseils pour l'entretien : comment répondre efficacement
Structure recommandée pour chaque réponse
Affirmation claire (10 secondes) : la réponse directe
Explication (30 secondes) : pourquoi, comment ça marche
Exemple concret (20 secondes) : code SQL et contexte
Piège et alternative (20 secondes) : erreur courante ou approche alternative
Cas d'usage métier (15 secondes) : application pratique
Total : 95 secondes par question — adapté à un entretien technique.
Erreurs à éviter absolument
❌ Parler trop : rester concis et attendre les questions
❌ Donner le code d'abord : d'abord expliquer, puis le code illustre
❌ Oublier les pièges : montrer que vous avez réfléchi aux cas limites
❌ Prétendre savoir : dire "je ne sais pas mais..." est mieux que du charabia
❌ Ne pas valider : toujours proposer de tester mentalement sur un petit cas
Comment progresser avant l'entretien
Semaines 1-2 : couvrir les questions 1-5 (débutant), faire les exercices
Semaines 3-4 : questions 6-12 (intermédiaire), pratiquer sur des cas réels
Semaines 5-6 : questions 13-20 (avancé), implémenter sur une vraie base
Semaine 7 : simulations complètes avec chronomètre, 3 entretiens de test
Comment expliquer la différence entre INNER JOIN et LEFT JOIN en entretien ?
L'INNER JOIN retourne uniquement les lignes qui ont une correspondance dans les deux tables, tandis que LEFT JOIN retourne toutes les lignes de la table de gauche, même s'il n'y a pas de correspondance à droite. C'est une distinction fondamentale qu'il faut maîtriser pour montrer votre compréhension des jointures SQL.
Qu'est-ce qu'une requête SQL optimisée et comment la démontrer ?
Une requête optimisée minimise le temps d'exécution en utilisant les bons index, en évitant les sous-requêtes imbriquées inutiles et en limitant les données scannées. En entretien, montrez que vous pensez à la performance en expliquant vos choix (SELECT * à éviter, WHERE avant GROUP BY, etc.).
Comment structurer ma réponse face à une question SQL que je ne comprends pas complètement ?
Commencez par reformuler la question pour clarifier, posez des questions de précision sur les données ou le résultat attendu, puis proposez une approche pas à pas. Cette méthode montre votre rigueur plutôt que de rester bloqué.
Quelle est la différence entre COUNT(*), COUNT(colonne) et COUNT(DISTINCT) ?
COUNT(*) compte toutes les lignes incluant les NULL, COUNT(colonne) ignore les NULL, et COUNT(DISTINCT) compte les valeurs uniques. Ces trois variantes apparaissent souvent en entretien pour tester votre précision.
Comment utiliser GROUP BY sans se tromper et qu'éviter ?
GROUP BY regroupe les lignes par une ou plusieurs colonnes et s'utilise avec des fonctions d'agrégation (SUM, AVG, COUNT). Évitez de sélectionner des colonnes non agrégées qui ne sont pas dans GROUP BY, car c'est une erreur courante en SQL.
Prêt à vous entraîner ?
50 exercices SQL interactifs avec éditeur en ligne, chronomètre et feedback IA.