Maintenez SQLtest en vie : Aidez à couvrir les coûts du serveur de ce mois-ci de 100 $ pour garder toutes les leçons et le terrain de jeu SQL en ligne. Soutenez le projet →
Code SQL copié dans le presse-papiers

Leçon 12.4 · Temps de lecture : ~7 min

« Trouvez le deuxième salaire le plus élevé » est l'une des questions les plus fréquentes en entretien SQL, généralement suivie de « maintenant le N-ième » et « maintenant dans chaque département ». Dans cette leçon, vous la résoudrez de cinq façons : MAX avec une sous-requête, LIMIT ... OFFSET, DENSE_RANK() et une sous-requête corrélée, et vous verrez les pièges que les recruteurs guettent : salaires identiques, N-ième valeur absente et groupes.

N-ième salaire le plus élevé en SQL

L'exercice semble trivial, et c'est précisément pour cela qu'il est populaire : une première tentative fonctionne généralement sur les données d'exemple et échoue sur un cas limite. Une bonne réponse gère trois choses : les ex æquo (deux employés au même salaire), l'absence de réponse (moins de N salaires différents) et les groupes (le N-ième salaire de chaque département).

Les exemples utilisent la syntaxe de MySQL 8.0 et créent leur propre table : exécutez-les dans le bac à sable sur une base sans données d'exemple.

Données d'exemple

CREATE TABLE employee (
    id INT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    department VARCHAR(30) NOT NULL,
    salary INT NOT NULL
);

INSERT INTO employee VALUES
    (1, 'Alice', 'Sales', 5000),
    (2, 'Bob', 'Sales', 7000),
    (3, 'Carol', 'Sales', 7000),
    (4, 'Dave', 'IT', 9000),
    (5, 'Eve', 'IT', 6500),
    (6, 'Frank', 'IT', 9000),
    (7, 'Grace', 'HR', 4800),
    (8, 'Heidi', 'HR', 5200);

Les salaires distincts, du plus haut au plus bas, sont 9000, 7000, 6500, 5200, 5000, 4800. Deux personnes touchent 9000 et deux touchent 7000 : le deuxième salaire le plus élevé est donc 7000, et non 9000.


Solution 1 : le MAX sous le MAX

Le deuxième salaire le plus élevé est le plus grand salaire inférieur au maximum :

SELECT MAX(salary) AS second_highest_salary
FROM employee
WHERE salary < (SELECT MAX(salary) FROM employee);

Résultat : 7000.

C'est court, cela fonctionne dans toutes les bases, gère les ex æquo et renvoie NULL quand il n'y a pas de deuxième salaire, car le MAX d'un ensemble vide vaut NULL. Sa limite : cela ne marche que pour N = 2. Pour le troisième salaire, il faudrait un niveau d'imbrication de plus.


Solution 2 : DISTINCT avec LIMIT et OFFSET

Triez les salaires du plus haut au plus bas et sautez-en N − 1 :

SELECT DISTINCT salary
FROM employee
ORDER BY salary DESC
LIMIT 1 OFFSET 1;

Résultat : 7000.

DISTINCT est indispensable. Sans lui, la liste triée est 9000, 9000, 7000, … et OFFSET 1 tombe sur le second 9000 :

-- Faux : renvoie de nouveau le salaire maximal à cause de l'ex æquo
SELECT salary
FROM employee
ORDER BY salary DESC
LIMIT 1 OFFSET 1;

Résultat : 9000, le salaire le plus élevé, et non le deuxième.

Renvoyer NULL quand il n'y a pas de réponse

Si N dépasse le nombre de salaires distincts, la requête ne renvoie aucune ligne. Les exercices d'entretien exigent souvent un NULL à la place. Enveloppez la requête dans une sous-requête scalaire : une sous-requête qui ne trouve rien renvoie NULL.

SELECT (
    SELECT DISTINCT salary
    FROM employee
    ORDER BY salary DESC
    LIMIT 1 OFFSET 9
) AS tenth_highest_salary;

Résultat : une ligne avec NULL ; il n'y a que 6 salaires distincts.

LIMIT ... OFFSET est la syntaxe de MySQL, PostgreSQL et SQLite. SQL Server et Oracle 12c+ utilisent OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY.


Solution 3 : DENSE_RANK

Une fonction de fenêtrage classe chaque ligne, et la requête externe choisit le rang voulu. C'est la solution la plus souple, et celle qu'attendent généralement les recruteurs :

WITH ranked AS (
    SELECT name, salary,
           DENSE_RANK() OVER (ORDER BY salary DESC) AS salary_rank
    FROM employee
)
SELECT name, salary
FROM ranked
WHERE salary_rank = 2;

Résultat : Bob et Carol, tous deux à 7000.

Elle répond aussi à la question suivante : « qui touche le deuxième salaire le plus élevé ». Remplacez 2 par n'importe quel N. Seul DENSE_RANK convient ici :

salaryROW_NUMBERRANKDENSE_RANK
9000111
9000211
7000332
7000432
6500553

ROW_NUMBER donne des numéros différents à des salaires égaux, et RANK laisse des trous après les ex æquo : RANK = 2 ne trouve donc personne. DENSE_RANK numérote les valeurs distinctes sans trous.


Solution 4 : sous-requête corrélée

Avant les fonctions de fenêtrage, la réponse classique était : un salaire est le N-ième plus élevé si exactement N salaires distincts lui sont supérieurs ou égaux.

SELECT DISTINCT e1.salary
FROM employee e1
WHERE 3 = (
    SELECT COUNT(DISTINCT e2.salary)
    FROM employee e2
    WHERE e2.salary >= e1.salary
);

Résultat : 6500, le troisième salaire le plus élevé.

Cela fonctionne dans toutes les bases, mais la sous-requête s'exécute pour chaque ligne et devient lente sur une grande table. Citez-la comme alternative et utilisez DENSE_RANK en pratique.


Solution 5 : le N-ième dans chaque groupe

La question suivante habituelle : « le deuxième salaire le plus élevé de chaque département ». Ajoutez PARTITION BY à la fonction de fenêtrage :

WITH ranked AS (
    SELECT department, name, salary,
           DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS salary_rank
    FROM employee
)
SELECT department, name, salary
FROM ranked
WHERE salary_rank = 2
ORDER BY department;

Résultat : HR – Grace (4800), IT – Eve (6500), Sales – Alice (5000).

Dans Sales, Bob et Carol partagent le salaire maximal : le deuxième est donc les 5000 d'Alice. Un département avec un seul salaire distinct serait absent du résultat.


Quelle solution choisir

SolutionNEx æquoPas de réponseGroupes
MAX sous MAX2 seulementOuiNULLDifficile
DISTINCT ... LIMIT OFFSETQuelconqueAvec DISTINCTVide ; NULL dans une sous-requête scalaireNon
DENSE_RANK()QuelconqueOuiVidePARTITION BY
Sous-requête corréléeQuelconqueOuiVideAjouter une condition

En entretien, commencez par DENSE_RANK, expliquez pourquoi ni ROW_NUMBER ni RANK, et indiquez comment renvoyer NULL.

Pratique


Points clés :

  • La deuxième plus grande valeur est MAX(x) WHERE x < (SELECT MAX(x) ...) ; elle gère les ex æquo et renvoie NULL quand il n'y en a pas.
  • Avec LIMIT 1 OFFSET N-1, utilisez toujours DISTINCT, sinon les ex æquo décalent la réponse.
  • Une sous-requête scalaire transforme « aucune ligne » en NULL.
  • DENSE_RANK() résout n'importe quel N, renvoie les employés et, avec PARTITION BY, fonctionne par groupe.
  • ROW_NUMBER et RANK donnent de mauvaises réponses quand les valeurs se répètent.

Questions fréquentes

Comment trouver le deuxième salaire le plus élevé en SQL ?

La requête portable la plus courte est SELECT MAX(salary) FROM employee WHERE salary < (SELECT MAX(salary) FROM employee);. Elle renvoie le plus grand salaire sous le maximum, ignore les ex æquo et renvoie NULL si tout le monde gagne la même chose.

Comment trouver le N-ième salaire le plus élevé en SQL ?

Utilisez DENSE_RANK() OVER (ORDER BY salary DESC) dans une CTE et filtrez sur le rang N, ou SELECT DISTINCT salary ... ORDER BY salary DESC LIMIT 1 OFFSET N-1. Les deux gèrent correctement les salaires identiques.

Pourquoi ne pas utiliser ROW_NUMBER ou RANK ?

ROW_NUMBER numérote différemment des salaires égaux : un ex æquo en tête place la même valeur en deuxième position. RANK saute des numéros après les ex æquo : avec deux salaires maximaux, il n'y a pas du tout de rang 2. DENSE_RANK numérote les valeurs distinctes de façon continue.

Comment renvoyer NULL s'il n'y a pas de N-ième salaire ?

Placez la requête dans une sous-requête scalaire : SELECT (SELECT DISTINCT salary FROM employee ORDER BY salary DESC LIMIT 1 OFFSET 9) AS result;. Quand la requête interne ne trouve rien, la requête externe renvoie quand même une ligne avec NULL.