Questions d'entretien SQL, avec des réponses à exécuter

La plupart des entretiens SQL reprennent les mêmes idées. Voici les plus courantes, avec une réponse brève et une requête qui fonctionne dans PostgreSQL. Les exemples utilisent une petite base de boulangerie (customers, products, orders et order_items), pour que vous puissiez tester chacun d'eux dans une leçon de ce site.

Trouver la deuxième valeur la plus élevée

Une réponse simple consiste à trier par ordre décroissant et à sauter une ligne. Elle renvoie une seule ligne, même si plusieurs lignes partagent le deuxième prix.

Si les ex æquo comptent, classez avec DENSE_RANK et gardez le rang 2. DENSE_RANK ne saute aucun numéro après des ex æquo : le rang 2 est donc toujours la deuxième valeur distincte.

SELECT name, unit_price
FROM (
  SELECT name, unit_price, DENSE_RANK() OVER (ORDER BY unit_price DESC) AS r
  FROM products
) AS ranked
WHERE r = 2;

Résultat : Chocolate cake slice, à 4.20.

Trouver les doublons

Groupez par la colonne qui devrait être unique et gardez les groupes qui contiennent plus d'une ligne.

SELECT order_date, COUNT(*) AS orders
FROM orders
GROUP BY order_date
HAVING COUNT(*) > 1
ORDER BY order_date;

Résultat : Trois dates, chacune avec deux commandes : les 1er, 2 et 6 septembre.

Trouver les lignes sans correspondance

Utilisez un LEFT JOIN et gardez les lignes où le côté droit est NULL, ou utilisez NOT EXISTS. Les deux répondent à « quels clients n'ont jamais passé de commande ? ».

Évitez NOT IN avec une sous-requête qui peut renvoyer NULL : un seul NULL rend toute la condition indéterminée et vous n'obtenez aucune ligne.

SELECT c.first_name, c.last_name
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.customer_id
WHERE o.order_id IS NULL
ORDER BY c.customer_id;

Résultat : Grace Kim et Hugo Meyer.

Premier élément de chaque groupe

Classez à l'intérieur de chaque groupe avec PARTITION BY, puis gardez le rang 1 dans une requête externe. Utilisez RANK pour conserver les ex æquo, ou ROW_NUMBER pour garder exactement une ligne par groupe.

SELECT category, name, units
FROM (
  SELECT p.category, p.name, SUM(oi.quantity) AS units,
         RANK() OVER (PARTITION BY p.category ORDER BY SUM(oi.quantity) DESC) AS rk
  FROM products AS p
  JOIN order_items AS oi ON oi.product_id = p.product_id
  GROUP BY p.category, p.name
) AS ranked
WHERE rk = 1
ORDER BY category, name;

Résultat : Le meilleur vendeur de chaque catégorie. Cake apparaît deux fois, car deux produits sont à égalité avec 3 unités.

Total cumulé

Utilisez SUM comme fonction de fenêtrage avec ORDER BY. Triez sur une colonne sans ex æquo, sinon les lignes à égalité sont additionnées ensemble.

SELECT order_id,
       SUM(quantity) AS units,
       SUM(SUM(quantity)) OVER (ORDER BY order_id) AS running_units
FROM order_items
GROUP BY order_id
ORDER BY order_id;

Résultat : Unités par commande et total cumulé, qui termine à 56.

WHERE contre HAVING

WHERE filtre les lignes avant leur regroupement : il ne peut donc pas utiliser une fonction d'agrégation. HAVING filtre les groupes après l'agrégation. Dans l'exemple, WHERE retire d'abord les produits bon marché, et HAVING garde les catégories qui en comptent au moins deux parmi les produits restants.

SELECT category, COUNT(*) AS products
FROM products
WHERE unit_price > 2.50
GROUP BY category
HAVING COUNT(*) >= 2
ORDER BY category;

Résultat : Bread et cake, avec deux produits chacun au-dessus de 2.50. Pastry et drinks n'ont qu'un seul produit au-dessus de 2.50 et sont exclues.

UNION contre UNION ALL

UNION supprime les lignes en double, ce qui demande un travail supplémentaire. UNION ALL garde tout et est plus rapide. Utilisez UNION ALL, sauf si vous avez besoin de supprimer les doublons.

SELECT city FROM customers
UNION
SELECT city FROM customers
ORDER BY city;

Résultat : Cinq villes, chacune une seule fois. Avec UNION ALL, il y aurait 16 lignes.

Questions fréquentes

Quelle est la différence entre DELETE, TRUNCATE et DROP ?
DELETE supprime les lignes que vous choisissez (WHERE) et peut être annulé. TRUNCATE vide rapidement toute la table et conserve sa structure. DROP supprime la table elle-même, avec sa structure et ses données.
Quelle est la différence entre INNER JOIN et LEFT JOIN ?
INNER JOIN ne garde que les lignes qui correspondent des deux côtés. LEFT JOIN garde chaque ligne de la table de gauche et remplit le côté droit avec NULL lorsqu'aucune correspondance n'existe.
Quelle est la différence entre RANK, DENSE_RANK et ROW_NUMBER ?
Les trois numérotent les lignes d'un ordre. RANK donne le même numéro aux ex æquo et saute les numéros suivants. DENSE_RANK donne le même numéro aux ex æquo et ne saute rien. ROW_NUMBER ne répète jamais un numéro.
Quel dialecte SQL utilisent ces réponses ?
PostgreSQL. LIMIT/OFFSET, les fonctions de fenêtrage, les CTE et les jointures fonctionnent presque partout. Dans SQL Server, utilisez TOP ou OFFSET FETCH à la place de LIMIT, et dans Oracle, FETCH FIRST.

Pratiquer