Les candidats échouent fréquemment sur six concepts SQL majeurs en entretien data, notamment les fonctions fenêtrées, HAVING vs WHERE, et la gestion des NULLs. Comprenez ces pièges grâce à des exemples concrets pour booster votre performance en entretien.
3 principaux points à retenir.
- Maîtrisez les fonctions fenêtrées avec ORDER BY pour des résultats fiables.
- Sachez utiliser HAVING pour filtrer après agrégation, pas WHERE.
- Apprenez à gérer les NULLs et privilégiez CTEs pour la clarté.
Pourquoi les fonctions fenêtrées sont-elles si difficiles
Les fonctions fenêtrées, c’est le chaînon manquant entre de simples requêtes et des analyses de données puissantes. Pourtant, malgré leur potentiel, elles sont souvent mal comprises, notamment à cause d’une concentration excessive sur la seule syntaxe. Beaucoup se contentent de réciter comme une formule magique, sans creuser l’importance de la fenêtre, du PARTITION BY, et surtout de l’ORDER BY.
Pourquoi l’ORDER BY est-il si crucial, notamment dans des fonctions comme LAG() ou LEAD() ? Imaginez que vous souhaitiez comparer le montant des transactions d’un utilisateur d’un mois à l’autre. Si vous omettez l’ORDER BY, la base de données devient un peu comme un chef cuisinier sans recette : le résultat est aléatoire. Les données peuvent être présentées dans un ordre fou, ce qui fausse votre analyse.
Regardons cela avec un petit exemple. Supposons que nous avons une table transactions :
| user_id | amount | transaction_date |
|---------|--------|------------------|
| 1 | 100 | 2023-01-01 |
| 1 | 200 | 2023-02-01 |
| 1 | 50 | 2023-03-01 |
Si l’on utilise LAG() sans ORDER BY, on pourrait écrire :
SELECT
user_id,
amount,
LAG(amount) OVER (PARTITION BY user_id) AS previous_amount
FROM
transactions;
Sans ORDER BY, le résultat est imprévisible :
| user_id | amount | previous_amount |
|---------|--------|-----------------|
| 1 | 100 | NULL |
| 1 | 200 | NULL |
| 1 | 50 | NULL |
La clé ici ? Ajouter ORDER BY transaction_date à notre fonction :
SELECT
user_id,
amount,
LAG(amount) OVER (PARTITION BY user_id ORDER BY transaction_date) AS previous_amount
FROM
transactions;
Cette fois, on obtient :
| user_id | amount | previous_amount |
|---------|--------|-----------------|
| 1 | 100 | NULL |
| 1 | 200 | 100 |
| 1 | 50 | 200 |
En omettant ORDER BY, on a introduit une incertitude qui aurait pu compromettre une analyse cruciale. Pour maîtriser les fonctions fenêtrées, asseyez-vous confortablement avec les détails et développez une compréhension robuste de la décomposition de vos données. Les bonnes pratiques incluent une documentation rigoureuse, des tests unitaires pour vos requêtes et, surtout, ne jamais négliger l’ORDER BY dans les fonctions qui s’en servent. Si cela vous intéresse davantage, vous pouvez lire cet article utile ici.
Quand utiliser HAVING plutôt que WHERE
Ça y est, vous êtes face à la fameuse question sur SQL en entretien : quand utiliser HAVING plutôt que WHERE ? Allez, je vous guide à travers ce labyrinthe syntaxique. L’astuce clé réside dans l’ordre d’exécution des clauses SQL. En gros, WHERE filtre les lignes avant l’agrégation, tandis que HAVING intervient après. Cette distinction peut sembler subtile au premier abord, mais elle est cruciale pour obtenir des résultats fiables.
Imaginez que vous ayez une table appelée ventes, où chaque ligne représente des transactions de produits. Si vous voulez compter le nombre de ventes par produit et obtenir uniquement ceux qui dépassent 100 unités, voici où la confusion pourrait survenir :
SELECT produit, COUNT(*) AS nombre_ventes
FROM ventes
WHERE COUNT(*) > 100
GROUP BY produit;
Ouch ! Utiliser COUNT(*) dans le WHERE va vous conduire à une erreur. WHERE ne peut pas traiter les agrégations parce qu’il agit avant le GROUP BY. Pour corriger cela, on doit utiliser HAVING :
SELECT produit, COUNT(*) AS nombre_ventes
FROM ventes
GROUP BY produit
HAVING COUNT(*) > 100;
Maintenant, ça fonctionne comme sur des roulettes. En employant HAVING, vous dites au serveur SQL d’effectuer le décompte de ventes pour chaque produit puis de filtrer ceux qui comptent plus de 100 ventes. Simple, non ?
Qui aurait cru que cette petite nuance puisse faire toute la différence entre un candidat qui brille en entretien et un autre qui se retrouve noyé sous les questions. La maîtrise de cette distinction vous permettra non seulement de briller devant un recruteur, mais aussi d’améliorer la fiabilité de vos requêtes dans le monde réel. Et ça, en tant que data analyst, c’est un vrai atout ! Un bon conseil : un peu de pratique et de réflexion sur les ordres d’exécution vous permettront de naviguer impeccable sur le terrain des requêtes SQL. Pour ceux qui veulent plonger encore plus dans le monde des questions d’entretien SQL, jetez un œil ici.
Comment simplifier les comparaisons temporelles avec les self-joins
Le self-join, vous connaissez ? C’est un outil que beaucoup négligent, mais il a le potentiel de transformer vos requêtes SQL en véritables chefs-d’œuvre d’optimisation. Imaginez que vous devez comparer des taux de change à deux dates différentes. En utilisant une sous-requête corrélée, vous pourriez vous retrouver dans un véritable imbroglio. Et si on simplifiait tout ça avec un self-join ?
Pour illustrer, prenons un exemple concret. Supposons que vous ayez une table `taux_change` avec les colonnes `date`, `devise` et `taux`. Si vous souhaitez comparer les taux de change d’une devise précise à deux dates, voilà comment une sous-requête pourrait le faire, mais attendez-vous à une complexité inutile :
SELECT a.devise,
a.taux AS taux_date1,
(SELECT b.taux FROM taux_change b WHERE b.devise = a.devise AND b.date = '2023-01-01') AS taux_date2
FROM taux_change a
WHERE a.date = '2023-12-01' AND a.devise = 'EUR';
Complexe, non ? Et maintenant, regardons le même résultat avec un self-join qui rend la requête beaucoup plus limpide :
SELECT a.devise,
a.taux AS taux_date1,
b.taux AS taux_date2
FROM taux_change a
JOIN taux_change b ON a.devise = b.devise
WHERE a.date = '2023-12-01' AND b.date = '2023-01-01' AND a.devise = 'EUR';
Avec ce self-join, vous avancez à pas de géant vers la clarté. Les avantages sont nombreux :
- Lisibilité : La requête est plus directe et facile à comprendre.
- Performance : Moins de calculs à chaque exécution, le moteur SQL peut optimiser la jointure.
- Maintenance : Si vous avez besoin de modifier la requête, le self-join reste bien plus simple à ajuster.
Et ce n’est pas tout. Les self-joins peuvent également vous faire gagner un temps précieux lors des évaluations en entretien, car les examinateurs apprécient la capacité à simplifier les requêtes tout en atteignant les mêmes objectifs. N’oubliez pas de doser avec sagesse logique et simplicité. Pour en apprendre plus sur les subtilités de SQL, rendez-vous sur ce site là.
Pourquoi préférer les CTE aux subqueries imbriquées
Les subqueries imbriquées. Qui n’a jamais eu à jongler avec des couches de requêtes et à se demander : « où ça s’arrête ? » Ces petites merveilles de complexité peuvent donner l’impression d’être un Jedi de la base de données, mais la réalité est bien différente. À un moment donné, un simple projet peut se transformer en un véritable casse-tête. Imaginez un scénario avec deux tables : Acteurs et Genres. Si vous devez récupérer les acteurs ayant joué dans un genre spécifique, une subquery imbriquée pourrait ressembler à cela :
SELECT a.nom FROM Acteurs a WHERE a.id IN (
SELECT p.id_acteur FROM Productions p WHERE p.genre_id IN (
SELECT g.id FROM Genres g WHERE g.nom = 'Thriller'
)
);
Visuellement, c’est déjà le parcours du combattant. Qui pourrait soutenir que cette structure est lisible ? Il faut être une sorte de coder explicatif pour démêler les couches et comprendre ce qui se passe vraiment. Et si une modification est nécessaire, préparez-vous à un voyage à travers ces niveaux d’imbrication, un vrai cauchemar pour la maintenance.
C’est là que les Common Table Expressions (CTEs) entrent en jeu, transformant une logique confuse en un code plus simple à lire et à modifier. Reprenons l’exemple précédent avec des CTE :
WITH GenreSelect AS (
SELECT id FROM Genres WHERE nom = 'Thriller'
), ActorProductions AS (
SELECT id_acteur FROM Productions WHERE genre_id IN (SELECT id FROM GenreSelect)
)
SELECT a.nom FROM Acteurs a WHERE a.id IN (SELECT id_acteur FROM ActorProductions);
Voilà ! C’est plus clair, plus modulable. Chaque partie du code a un sens et peut être comprise isolément. Les CTE permettent également d’optimiser les performances, surtout dans des environnements où la taille des données peut être écrasante. C’est un petit détail qui a un grand impact sur la collaboration dans les équipes data. Chacun peut comprendre plus rapidement ce que fait le code, faciliter les révisions, et améliorer la documentation de manière organique plutôt qu’à coup de commentaires illisibles.
En fin de compte, délaissez ces subqueries compliquées pour adopter une approche CTE. Vous ne serez pas seulement un développeur de données, mais un architecte de solutions intelligibles. Et rappelez-vous, un bon code se lit comme un bon livre, sans que le lecteur ait besoin de feuilleter les pages pendant des heures.
Comment gérer efficacement les NULLs en SQL
Gérer les NULLs en SQL, c’est un peu comme jongler avec des grenades dégoupillées : si tu fais pas gaffe, tu vas tout faire sauter. Les NULLs, ce ne sont pas des valeurs. Ils représentent une absence d’information. Alors pourquoi tant de candidats, lors des entretiens, s’entêtent-ils à utiliser = NULL au lieu de IS NULL? La réponse, c’est que le fonctionnement de NULL est si contre-intuitif qu’énormément de gens passent à côté.
La vraie nature des NULLs est qu’ils ne sont égaux à rien, pas même à eux-mêmes. Cela veut dire qu’une condition comme NULL = NULL renvoie… eh bien, NULL ! Par là, je veux dire qu’elle renvoie un résultat indéfini. En gros, c’est l’impasse assurée. Imagine un FULL OUTER JOIN entre deux tables sur l’ID client. Si certains IDs dans la première table sont NULL et que tu utilises = pour essayer de les comparer, tu vas prendre des chemins tortueux et finir par obtenir des résultats faussés. Voici un exemple simple :
SELECT a.client_id, b.client_id
FROM table_a a
FULL OUTER JOIN table_b b
ON a.client_id = b.client_id;
Dans ce cas, tout ce qui était à NULL dans les IDs ne se liera à rien, espérant que tu ne fasses pas partie de ceux qui croient qu’un NULL est égal à un autre NULL. Résultat : de nombreuses lignes manquantes dans ton dataset.
Voici où la fonction COALESCE entre en scène. Elle remplace les valeurs NULL par la première valeur non-NULL dans la liste fournie. Par exemple :
SELECT COALESCE(a.client_id, 'Inconnu') AS client_id
FROM table_a a;
Dans cet exemple, si a.client_id est NULL, il sera remplacé par « Inconnu ». C’est une méthode élégante pour gérer les NULLs sans tracas. En maîtrisant le fonctionnement des NULLs et en utilisant judicieusement IS NULL et COALESCE, tu peux transformer un potentiel désastre en un succès éclatant.
Quelle méthode utiliser pour dédupliquer par groupe efficacement
Quand on parle de dé-duplication de données en SQL, on se retrouve souvent face à un dilemme : comment choisir la bonne ligne unique par groupe ? C’est un classique que pas mal d’analystes ratent en tentant d’utiliser GROUP BY avec LIMIT ou même DISTINCT ON. En fin de compte, ces méthodes sont souvent inadaptées et peuvent rapidement tourner au cauchemar. Mais pas de panique ! L’arme secrète, ce sont les fonctions de fenêtre, et plus précisément, ROW_NUMBER() et RANK().
Ces deux fonctions sont idéales pour choisir la meilleure ligne par groupe, tout en gardant le code lisible et flexible. Prenons par exemple le cas où voulons déterminer le meilleur article vendu par mois. Supposons que nous ayons une table ventes avec les colonnes mois, article et chiffre_affaires. Voici comment utiliser RANK() pour obtenir nos résultats :
SELECT mois, article, chiffre_affaires,
RANK() OVER (PARTITION BY mois ORDER BY chiffre_affaires DESC) AS rank
FROM ventes
WHERE rank = 1;
Avec cette requête, on partitionne les données par mois et on classe les articles en fonction de leur chiffre_affaires. L’utilisation de RANK() permet de gérer les cas d’égalité. Imaginons deux articles ayant le même chiffre d’affaires, ils recevront tous les deux le rang 1, ce qui nous donne la flexibilité d’accueillir cette éventualité.
En revanche, si vous n’avez qu’une seule ligne à conserver par groupe et que vous ne voulez pas gérer d’égalité, choisissez ROW_NUMBER(). Cette fonction attribue un numéro unique à chaque ligne dans une partition, peu importe qu’il y ait des égalités ou non. Le choix entre ces deux méthodes dépend donc de ce que vous cherchez à accomplir.
Lors d’un entretien d’embauche dans le domaine de la data, la maîtrise de ces concepts ne pourra que jouer en votre faveur. Les recruteurs recherchent des candidats capables d’envisager des solutions élégantes et efficaces. Alors, si vous voulez briller, assurez-vous de connaître la différence entre ces deux fonctions et de savoir quand les utiliser à bon escient. D’ailleurs, si vous souhaitez vous auto-évaluer sur vos compétences SQL, ce thread sur Reddit pourrait vous apporter de précieux éclaircissements.
Prêt à maîtriser ces concepts pour réussir vos entretiens SQL ?
Ces six concepts SQL – fonctions fenêtrées, HAVING vs WHERE, self-joins, CTEs, gestion des NULLs et déduplication par groupe – sont des pierres d’achoppement classiques en entretien data. Les comprendre et les appliquer correctement vous distingue nettement des candidats moyens. En maîtrisant ces notions, votre code devient plus fiable, lisible, efficace, et surtout adapté aux exigences réelles du métier. En somme, c’est votre ticket pour sortir du lot et convaincre vos recruteurs.
FAQ
Quels sont les concepts SQL les plus fréquemment ratés en entretien ?
Pourquoi ORDER BY est-il essentiel dans les fonctions fenêtrées ?
Quelle est la différence entre WHERE et HAVING en SQL ?
Comment gérer les NULLs efficacement dans les requêtes SQL ?
Pourquoi privilégier les CTE aux sous-requêtes imbriquées ?
A propos de l’auteur
Franck Scandolera est consultant expert en Data Engineering et Analytics, avec plus de 10 ans d’expérience en environnement SQL et automatisation de données. En tant que formateur reconnu, il accompagne des professionnels dans toute la francophonie pour booster leurs compétences SQL, en mettant l’accent sur la clarté, la performance et les bonnes pratiques métiers indispensables.
⭐ Expert et formateur en Tracking avancé, Analytics Engineering et Automatisation IA (n8n, Make) ⭐
Ref clients : Logis Hôtel, Yelloh Village, BazarChic, Fédération Football Français, Texdecor…
Mon terrain de jeu :
Data & Analytics engineering : tracking propre RGPD, entrepôt de données (GTM server, BigQuery…), modèles (dbt/Dataform), dashboards décisionnels (Looker, SQL, Python).
Automatisation IA des taches Data, Marketing, RH, compta etc : conception de workflows intelligents robustes (n8n, Make, App Script, scraping) connectés aux API de vos outils et LLM (OpenAI, Mistral, Claude…).
Engineering IA pour créer des applications et agent IA sur mesure : intégration de LLM (OpenAI, Mistral…), RAG, assistants métier, génération de documents complexes, APIs, backends Node.js/Python.






