N premières lignes par groupe avec ROW_NUMBER
Le schéma classique de partitionnement et de classement pour obtenir les 3 premiers éléments de chaque catégorie
N premières lignes par groupe avec ROW_NUMBER est une leçon SQL Interview Prep gratuite sur CoddyKit. Ceci est la leçon 1 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage SQL Interview Prep, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours SQL Interview Prep comprend 4 leçons au total.
La question des N premiers par groupe
L'une des questions SQL les plus courantes en entretien semble simple : « Retournez les 3 employés les mieux payés de chaque service. » Les candidats qui utilisent immédiatement LIMIT échouent, car LIMIT limite l'ensemble du résultat, et non chaque groupe.
Le recruteur vérifie si vous connaissez les fonctions de fenêtre. La réponse classique consiste à numéroter les lignes à l'intérieur de chaque groupe, puis à conserver celles dont le numéro est ≤ N. Cette leçon construit ce schéma étape par étape.
Pourquoi LIMIT ne suffit pas
Supposons que vous écriviez la requête ci-dessous. Elle renvoie seulement 3 lignes au total pour toute la table, et non 3 par service.
LIMIT (ou TOP, ou FETCH FIRST) s'applique à l'ensemble du résultat final. Le SQL standard ne prévoit pas de LIMIT par groupe. Lorsque vous proposez LIMIT 3 pour un problème portant sur chaque groupe, le recruteur comprend que vous n'avez pas assimilé le partitionnement.
-- WRONG: only 3 rows total, not 3 per department
SELECT department, name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3;Découvrez ROW_NUMBER
ROW_NUMBER() est une fonction de fenêtre qui attribue à chaque ligne un entier unique et sans trous, selon un ordre donné. Utilisée seule, elle numérote l'ensemble du résultat.
L'ingrédient essentiel est PARTITION BY : la numérotation recommence à 1 pour chaque groupe. Combinez PARTITION BY department avec ORDER BY salary DESC et chaque service obtient son propre classement 1, 2, 3, ... selon le salaire.
SELECT
name,
department,
salary,
ROW_NUMBER() OVER (
PARTITION BY department
ORDER BY salary DESC
) AS rn
FROM employees;Lecture du résultat numéroté
Après l'exécution de la requête précédente, chaque ligne contient une valeur rn. Dans chaque service, le salaire le plus élevé reçoit rn = 1, le suivant reçoit 2, et ainsi de suite. Un nouveau service recommence à 1.
- Ventes : Ana (1), Bo (2), Cal (3), Dee (4)
- Ingénierie : Eve (1), Fin (2), Gus (3)
Les « 3 premiers par service » signifient donc simplement « conserver les lignes où rn <= 3 ».
Vous ne pouvez pas filtrer rn dans WHERE
L'étape suivante semble naturellement être WHERE rn <= 3, mais elle échoue. Les fonctions de fenêtre sont calculées après la clause WHERE dans l'ordre d'exécution logique ; l'alias rn n'existe donc pas encore lorsque WHERE est exécuté.
Les recruteurs apprécient particulièrement ce piège. La solution consiste à calculer la fonction de fenêtre dans une sous-requête ou une CTE, puis à filtrer le résultat de cette requête interne dans une requête externe.
-- ERROR: rn does not exist in WHERE
SELECT name, department, salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM employees
WHERE rn <= 3;La solution classique avec une CTE
Placez la numérotation dans une CTE nommée ranked, puis sélectionnez ses données en appliquant le filtre dans le WHERE externe. C'est la réponse que les recruteurs veulent voir, et elle se lit clairement.
Mémorisez cette structure : partitionner selon le groupe, trier selon la mesure, filtrer rn ≤ N dans la requête externe. Elle s'applique aux N premiers, qu'il s'agisse du premier, des cinq premiers ou de toute autre valeur de N, en ne changeant qu'un nombre.
WITH ranked AS (
SELECT
name, department, salary,
ROW_NUMBER() OVER (
PARTITION BY department
ORDER BY salary DESC
) AS rn
FROM employees
)
SELECT name, department, salary
FROM ranked
WHERE rn <= 3
ORDER BY department, rn;La forme avec une sous-requête
Si le dialecte du recruteur est ancien ou s'il préfère les sous-requêtes, la même logique peut être placée dans une table dérivée dans FROM. N'oubliez pas qu'une table dérivée doit avoir un alias (r ici), sinon vous obtenez une erreur de syntaxe.
Les formes avec CTE et avec table dérivée sont interchangeables pour ce problème. Choisissez celle que le recruteur trouve la plus lisible ; les deux sont tout aussi correctes.
SELECT name, department, salary
FROM (
SELECT name, department, salary,
ROW_NUMBER() OVER (
PARTITION BY department ORDER BY salary DESC
) AS rn
FROM employees
) AS r
WHERE rn <= 3;Le meilleur élément de chaque groupe
« Trouver l'employé le mieux payé de chaque service » revient simplement à prendre N = 1. Définissez le filtre sur rn = 1.
Pourquoi ne pas utiliser MAX(salary) avec GROUP BY department ? Parce que MAX vous donne la valeur du salaire, mais pas le reste de la ligne de cet employé : son nom, sa date d'embauche, etc. ROW_NUMBER conserve la ligne complète gagnante, ce qui correspond généralement à ce que la question demande réellement.
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY department ORDER BY salary DESC
) AS rn
FROM employees
)
SELECT name, department, salary, hire_date
FROM ranked
WHERE rn = 1;Ajouter un critère de départage déterministe
ROW_NUMBER renvoie toujours exactement N lignes, même lorsque des salaires sont égaux. Mais la ligne ex æquo qui reçoit rn = 1 est choisie arbitrairement si vous ne départagez pas l'égalité. Si deux personnes gagnent 90000 et que vous ne conservez que rn = 1, la personne choisie peut changer d'une exécution à l'autre.
Ajoutez une clé de tri secondaire et unique, comme employee_id, afin que le résultat soit stable et reproductible. Les recruteurs apprécient les candidats qui mentionnent spontanément le déterminisme.
ROW_NUMBER() OVER (
PARTITION BY department
ORDER BY salary DESC, employee_id ASC
) AS rnUn exemple concret détaillé
Étant donné une table sales contenant region, product et revenue, retournez les 2 produits générant le plus de revenus par région. Même méthode : partitionner selon region, trier selon revenue DESC, conserver rn <= 2.
Remarquez que seules la colonne de partitionnement et la colonne de mesure changent. La structure reste identique, quel que soit le domaine métier.
WITH ranked AS (
SELECT region, product, revenue,
ROW_NUMBER() OVER (
PARTITION BY region ORDER BY revenue DESC, product
) AS rn
FROM sales
)
SELECT region, product, revenue
FROM ranked
WHERE rn <= 2
ORDER BY region, rn;Performances et points à mentionner
Pour aller au-delà de la simple justesse, mentionnez :
- Un index sur
(department, salary DESC)aide le moteur à produire efficacement des lignes triées pour chaque partition. - L'approche avec une fonction de fenêtre parcourt la table une seule fois, ce qui est bien plus efficace qu'une sous-requête corrélée exécutée pour chaque ligne.
- Pour les cas portant sur les N premiers avec N = 1 dans de très grands volumes, certains moteurs prennent en charge
DISTINCT ON(Postgres) comme raccourci, maisROW_NUMBERreste la solution standard portable.
Indiquez toujours votre critère de départage et confirmez la valeur de N.
Vérification rapide
Vérifiez votre compréhension du schéma des N premiers par groupe.
Récapitulatif : les N premiers par groupe
Le schéma en une phrase : partitionner selon le groupe, trier selon la mesure, attribuer ROW_NUMBER, puis conserver rn ≤ N dans une requête externe.
LIMITlimite l'ensemble complet, jamais chaque groupe.- Vous ne pouvez pas filtrer l'alias de la fonction de fenêtre dans
WHERE; placez-le dans une CTE ou une sous-requête. - Ajoutez un critère de départage unique pour obtenir des résultats déterministes.
- Le meilleur élément conserve la ligne gagnante complète, contrairement à
MAX+GROUP BY.
Modifiez un seul nombre et la même requête permet d'obtenir le meilleur élément, les cinq premiers ou toute autre valeur de N.
Questions Fréquemment Posées
La leçon « N premières lignes par groupe avec ROW_NUMBER » est-elle gratuite ?
Oui — le texte complet de « N premières lignes par groupe avec ROW_NUMBER » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours SQL Interview Prep, passe à CoddyKit PRO. Le cours SQL Interview Prep comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « N premières lignes par groupe avec ROW_NUMBER » ?
Le schéma classique de partitionnement et de classement pour obtenir les 3 premiers éléments de chaque catégorie Tu pratiques SQL Interview Prep avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer SQL Interview Prep ?
Aucune expérience préalable n'est requise. SQL Interview Prep sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 1 sur 4.
Combien de temps prend la leçon « N premières lignes par groupe avec ROW_NUMBER » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon SQL Interview Prep ?
Oui. Chaque leçon SQL Interview Prep inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- N premières lignes par groupe avec ROW_NUMBER
- Gérer les égalités parmi les N premiers
- Dédupliquer les lignes en toute sécurité
- Conserver la dernière ligne par clé