SQL : ORDER BY et LIMIT
Trier les résultats, paginer, récupérer les N premiers — syntaxe, tri multi-colonnes, OFFSET et performances
ORDER BY — trier les résultats
Sans ORDER BY, SQL ne garantit aucun ordre dans les résultats. La base de données est libre de retourner les lignes dans n’importe quel ordre — souvent celui du stockage physique, mais ce n’est pas fiable. Si tu veux un ordre prévisible, il faut toujours le demander explicitement.
ORDER BY trie le résultat final d’une requête. C’est l’avant-dernière étape d’exécution (juste avant LIMIT). Tu peux trier en ordre croissant (ASC, par défaut) ou décroissant (DESC).
SELECT nom, age FROM clients
ORDER BY nom; — équivalent à ORDER BY nom ASC
— Tri décroissant (Z→A, grand→petit)
SELECT nom, age FROM clients
ORDER BY age DESC;
— Trier par date décroissante (les plus récents d’abord)
SELECT * FROM commandes
ORDER BY date_commande DESC;
ORDER BY fonctionne sur tous les types : nombres (numérique), texte (alphabétique, sensible à la collation), dates (chronologique), et même les NULL (voir section 4). Le tri alphabétique dépend de la collation de la base — en UTF-8, les accents sont généralement triés correctement en français.
Tri multi-colonnes
Quand tu tries par plusieurs colonnes, SQL utilise la première colonne comme tri principal. En cas d’égalité sur la première colonne, il utilise la deuxième pour départager, et ainsi de suite. Chaque colonne peut avoir son propre sens de tri (ASC ou DESC).
SELECT nom, ville, age FROM clients
ORDER BY ville ASC, nom ASC;
— ville | nom | age
— Lyon | Alice | 28
— Lyon | Bob | 34
— Paris | Charlie | 22
— Paris | Diana | 31
— Sens de tri mixte : ville croissante, âge décroissant
SELECT nom, ville, age FROM clients
ORDER BY ville ASC, age DESC;
— Trier par numéro de colonne (déconseillé mais possible)
SELECT nom, ville, age FROM clients
ORDER BY 2, 1; — 2 = ville, 1 = nom
Le tri par numéro de colonne (ORDER BY 2, 1) est fragile : si tu réorganises les colonnes du SELECT, les numéros changent silencieusement. Préfère toujours nommer les colonnes explicitement pour un code maintenable.
Tri conditionnel (CASE)
Parfois tu as besoin d’un ordre de tri personnalisé qui ne correspond ni à l’ordre alphabétique ni à l’ordre numérique. Par exemple, trier des statuts dans un ordre métier précis (en_attente → payée → expédiée → livrée), ou afficher certaines lignes en priorité. Le CASE dans ORDER BY te permet de définir un tri sur mesure.
SELECT * FROM commandes
ORDER BY
CASE statut
WHEN ‘en_attente’ THEN 1
WHEN ‘payee’ THEN 2
WHEN ‘expediee’ THEN 3
WHEN ‘livree’ THEN 4
WHEN ‘annulee’ THEN 5
END;
— Afficher les VIP en premier, puis les autres par nom
SELECT * FROM clients
ORDER BY
CASE WHEN role = ‘vip’ THEN 0 ELSE 1 END,
nom ASC;
— MySQL : FIELD() — raccourci pour l’ordre personnalisé
SELECT * FROM commandes
ORDER BY FIELD(statut, ‘en_attente’, ‘payee’, ‘expediee’, ‘livree’, ‘annulee’);
ORDER BY et les NULL
Le tri des NULL varie selon le SGBD — c’est un piège classique quand tu migres une requête d’un système à l’autre. En MySQL et SQL Server, les NULL sont considérés comme les plus petites valeurs (ils apparaissent en premier en ASC). En PostgreSQL et Oracle, les NULL sont considérés comme les plus grandes valeurs (ils apparaissent en dernier en ASC).
SELECT nom, telephone FROM clients
ORDER BY telephone ASC;
— MySQL : NULL en premier (plus petit)
— PostgreSQL : NULL en dernier (plus grand)
— PostgreSQL : contrôler explicitement la position des NULL
SELECT * FROM clients
ORDER BY telephone ASC NULLS LAST; — NULL à la fin
SELECT * FROM clients
ORDER BY telephone DESC NULLS FIRST; — NULL au début
— MySQL : émuler NULLS LAST avec CASE ou IS NULL
SELECT * FROM clients
ORDER BY telephone IS NULL ASC, telephone ASC;
— IS NULL retourne 0 (non null) ou 1 (null)
— Les non-null (0) passent avant les null (1)
| SGBD | NULL en ASC | NULL en DESC | NULLS FIRST/LAST |
|---|---|---|---|
| MySQL | En premier | En dernier | ❌ Non supporté (émuler avec IS NULL) |
| PostgreSQL | En dernier | En premier | ✅ Supporté nativement |
| SQL Server | En premier | En dernier | ❌ Non supporté |
| Oracle | En dernier | En premier | ✅ Supporté nativement |
LIMIT — limiter le nombre de lignes
LIMIT restreint le nombre de lignes retournées. C’est la dernière étape d’exécution d’une requête — elle s’applique après le tri (ORDER BY). Sans ORDER BY, LIMIT retourne N lignes dans un ordre non déterministe, ce qui est rarement ce que tu veux.
SELECT * FROM commandes
ORDER BY date_commande DESC
LIMIT 10;
— Le client avec le plus gros chiffre d’affaires
SELECT client, SUM(montant) AS ca
FROM commandes
GROUP BY client
ORDER BY ca DESC
LIMIT 1;
— Top 5 des produits les plus chers
SELECT nom, prix FROM produits
ORDER BY prix DESC
LIMIT 5;
La syntaxe pour limiter les résultats varie d’un SGBD à l’autre. C’est l’une des rares différences syntaxiques majeures entre les moteurs SQL :
SELECT * FROM produits LIMIT 10;
— SQL Server
SELECT TOP 10 * FROM produits;
— Oracle (12c+)
SELECT * FROM produits
FETCH FIRST 10 ROWS ONLY;
— SQL standard (ANSI SQL:2008) — le plus portable
SELECT * FROM produits
ORDER BY prix DESC
FETCH FIRST 10 ROWS ONLY;
Pagination avec OFFSET
Pour afficher les résultats page par page (comme dans une liste de produits e-commerce), tu combines LIMIT (nombre de résultats par page) avec OFFSET (nombre de lignes à sauter). OFFSET 0 commence au début, OFFSET 10 saute les 10 premières lignes, etc.
SELECT * FROM produits
ORDER BY nom
LIMIT 10 OFFSET 0;
— Page 2 : résultats 11 à 20
SELECT * FROM produits
ORDER BY nom
LIMIT 10 OFFSET 10;
— Page 3 : résultats 21 à 30
SELECT * FROM produits
ORDER BY nom
LIMIT 10 OFFSET 20;
— Formule générale : OFFSET = (page – 1) * taille_page
— Syntaxe raccourcie MySQL (LIMIT offset, count)
SELECT * FROM produits
ORDER BY nom
LIMIT 10, 10; — LIMIT offset, count → page 2
— ⚠️ Confusant : le 1er nombre est l’offset, pas le count
⚠️ OFFSET a un problème de performance majeur : LIMIT 10 OFFSET 100000 force la base à lire 100 010 lignes et à en jeter 100 000. Plus l’OFFSET est grand, plus la requête est lente. C’est acceptable pour les premières pages, mais catastrophique pour les grandes tables avec une pagination profonde.
Pagination performante (cursor-based)
Au lieu de sauter N lignes avec OFFSET, la pagination par curseur utilise la dernière valeur vue comme point de départ. Au lieu de dire « saute 1000 lignes », tu dis « donne-moi les lignes après l’ID 1000 ». La base utilise l’index et va directement au bon endroit — pas besoin de scanner les lignes précédentes.
SELECT * FROM produits
ORDER BY id
LIMIT 10 OFFSET 100000; — lit 100 010 lignes
— ✅ Pagination par curseur (rapide, même sur les « pages » profondes)
— Page 1 : pas de condition
SELECT * FROM produits
ORDER BY id
LIMIT 10;
— Dernière ligne retournée : id = 10
— Page suivante : partir de la dernière valeur vue
SELECT * FROM produits
WHERE id > 10 — le curseur
ORDER BY id
LIMIT 10;
— Page encore après : id > dernier_id_de_la_page_précédente
SELECT * FROM produits
WHERE id > 20
ORDER BY id
LIMIT 10;
| Critère | OFFSET | Cursor (WHERE id > last) |
|---|---|---|
| Performance | ⚠️ Dégradée sur les pages profondes | ✅ Constante quelle que soit la page |
| Accès direct à une page | ✅ Oui (page 42 directement) | ❌ Non (pages séquentielles uniquement) |
| Résultats stables si données changent | ❌ Non (lignes décalées si INSERT/DELETE) | ✅ Oui (basé sur la valeur, pas la position) |
| Complexité | Simple | Un peu plus complexe (stocker le curseur) |
En pratique : OFFSET est parfait pour les petites tables et les premières pages. Pour les fils d’actualité infinis (Twitter, Reddit), les APIs, et les tables de millions de lignes, utilise la pagination par curseur. C’est le pattern utilisé par toutes les grandes apps.
Erreurs fréquentes
| Erreur | Problème | Solution |
|---|---|---|
| LIMIT sans ORDER BY | Les N lignes retournées sont aléatoires | Toujours combiner LIMIT avec ORDER BY |
| OFFSET sur des millions de lignes | Performance catastrophique | Pagination par curseur (WHERE id > last) |
| Tri sur une colonne sans index | Tri en mémoire coûteux (filesort) | Ajouter un index sur la colonne triée |
| ORDER BY dans une sous-requête | Ignoré par certains SGBD (l’optimiseur le retire) | ORDER BY uniquement dans la requête principale |
| Tri par numéro de colonne | Fragile si les colonnes changent | Nommer les colonnes explicitement |
| NULL mal placés dans le tri | Comportement différent selon le SGBD | NULLS FIRST/LAST ou IS NULL |
Questions fréquentes
📊 GROUP BY & HAVING
🔢 Fonctions d’agrégation
🔗 Jointures SQL
🔍 Sous-requêtes
⚡ Les index SQL
✏️ INSERT, UPDATE, DELETE
🗄️ Cours SQL complet
🏠 Hub Programmation
ORDER BY et LIMIT en SQL — Trier et paginer
Référence : sql.sh ORDER BY
