AI InterviewSearch…Search problems, topics, or pages…Ctrl KGet started
Skip to content

03 Sorting and limiting / Lesson 2 of 5

Sorting on several keys, ties and custom orders

14 min · +10 XP · 2 exercises · 5 questions

Read the text14 min
Exercises0/2
Quiz5 q

Quick answer

How does ORDER BY with multiple columns work in SQL?

With several sort keys, ORDER BY a, b sorts every row by a first, and only rows with equal a are compared on b; a third key breaks the ties left after that. Each key takes its own direction, so ORDER BY class ASC, score DESC lists each class's best score first. End with a unique key to make the order fully deterministic.

Real orderings almost always have more than one rule. A class list is sorted by class, then within each class by score, best first, then by name when two students share a score. A support queue shows urgent tickets first, then the oldest. A leaderboard ranks by points, then by who reached them first. SQL expresses each of those with one ORDER BY clause holding several sort keys, each with its own direction. This lesson covers how the keys combine, mixing ascending and descending, why the last key should be unique, and how to sort by an order that is not alphabetical or numeric — the custom order.

Keys are compared left to right

SELECT class, name, score
FROM students
ORDER BY class, score DESC, name;

The database compares two rows on the first key, class. If they differ, that decides their order and the other keys are never looked at. Only when the classes are equal does it compare score (descending), and only if those are equal too does it compare name.

classnamescore
AMeera91
AArjun85
ATara85
BKiran78
BDev60

Arjun and Tara share a class and a score, so the third key, name, puts Arjun first. Kiran's 78 sorts below Meera's 91 not because of the score but because class B comes after class A: a later key never overrides an earlier one. That is what people mean by "sort by class, then by score": a dictionary-style, lexicographic comparison.

Each key has its own direction

ASC and DESC attach to the key they follow. In ORDER BY class, score DESC, name the class and the name are ascending and only the score is descending. A very common bug is writing ORDER BY score, name DESC while meaning "both descending" — that sorts scores from lowest to highest. To reverse both:

ORDER BY score DESC, name DESC

Mixing directions is normal: "newest first, then alphabetical" is ORDER BY created_at DESC, title.

Make the last key unique

Two rows that tie on every key may come back in either order. The database is free to choose, and the choice can change from one run to the next — when the table grows, when the plan switches to an index, when a parallel scan finishes in a different order. SQL also makes no promise that sorting is stable: you cannot sort by name in one step and rely on that order surviving a later sort by score.

So if the order matters, finish with a key that no two rows share, usually the primary key:

SELECT id, name, points
FROM players
ORDER BY points DESC, id;

Now every row has exactly one place. This is not pedantry. Pagination (two lessons ahead) skips and repeats rows when the order is not unique, test output flips between runs, and a "first come, first served" ranking is only fair if the tie-breaker says who came first — a joined_at timestamp, then the id in case two players joined in the same second.

Sorting on computed keys

A key can be any expression, which lets you sort on facts the table does not store directly:

-- Longest titles first, then alphabetical
SELECT title
FROM books
ORDER BY CHAR_LENGTH(title) DESC, title;

-- Biggest discount in money, not in percent
SELECT name, price, discount_pct
FROM products
ORDER BY price * discount_pct / 100 DESC, id;

Boolean expressions are especially useful. In MySQL a comparison yields 1 for true and 0 for false, so sorting on a condition moves the matching rows to one end:

-- Featured products first (featured = 'yes' gives 1), then by price
SELECT name, featured, price
FROM products
ORDER BY featured = 'yes' DESC, price;

You saw the same trick with ORDER BY col IS NULL, col for NULLs-last in the previous lesson.

Custom orders with CASE

Statuses, priorities, sizes and weekdays have a natural order that is neither alphabetical nor numeric. Alphabetically, high < low < medium, which no one wants. The portable fix is to map each value to a rank with a CASE expression and sort on the rank:

SELECT id, title, priority
FROM tickets
ORDER BY CASE priority
           WHEN 'high'   THEN 1
           WHEN 'medium' THEN 2
           WHEN 'low'    THEN 3
           ELSE 4
         END,
         id;
idtitlepriority
2Checkout brokenhigh
5Refund stuckhigh
1Typo on FAQmedium
4Dark modelow

CASE gets a full lesson in the next module; for now read it as "if priority is 'high' the key is 1, if 'medium' it is 2", and so on. The ELSE 4 sends unexpected values, including NULL, to the end instead of the front — without it they would get NULL, which MySQL sorts first.

MySQL has a shorthand: FIELD(value, 'a', 'b', 'c') returns the position of value in the list (1, 2, 3) and 0 when it is not there.

ORDER BY FIELD(priority, 'high', 'medium', 'low'), id

Beware the 0: an unlisted value sorts before high. If the data can hold other values, use CASE with an ELSE, or list the order backwards and sort DESC. FIELD is MySQL-only; CASE works everywhere, which is why interview answers usually use it. A third option, when the order is real data rather than presentation, is to store a rank column (or a lookup table with one) and sort on that.

Sorting rows of a few groups

A multi-key sort is also how you make a report readable before you know GROUP BY. Sorting by department, then by salary descending, lays out each department as a block with its best-paid people at the top:

SELECT dept, name, salary
FROM employees
ORDER BY dept, salary DESC, name;

Picking just the top row of each block is a different problem — "top-N per group" — and needs window functions, which come later in the course. Sorting only arranges rows; it never removes them.

Before you move on

  • Keys compare left to right; a later key only breaks ties left by the earlier ones.
  • ASC/DESC belong to one key each.
  • SQL sorting is not stable; end with a unique key when order matters.
  • Expressions and boolean conditions make good keys.
  • Custom orders: CASE with an ELSE, or MySQL's FIELD (unlisted values give 0 and sort first).

Common questions

Does DESC apply to all columns in ORDER BY?

No. DESC applies only to the key it follows. In ORDER BY score, name DESC the score is ascending and only the name is descending; to sort both descending you write ORDER BY score DESC, name DESC.

How do I sort by a custom order in MySQL?

Map each value to a rank and sort on the rank: ORDER BY CASE status WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END. MySQL also offers ORDER BY FIELD(status, 'high', 'medium', 'low'), which returns a value's position in the list and 0 for anything not listed.

Why do I need a tie-breaker in ORDER BY?

Rows with equal sort keys may come back in any order, and that order can change between runs or after an index is added. Adding a unique column such as the primary key as the last key makes every row's position fixed, which pagination, reports and tests all rely on.

Is SQL ORDER BY a stable sort?

SQL makes no stability promise. Rows that tie on every sort key are not guaranteed to keep insertion order or any other order, so you cannot rely on an earlier sort surviving a later one. Spell out every key you care about in one ORDER BY.

Mark as readSign in to keep your place.

SQL ORDER BY Multiple Columns: Tie-Breakers and Custom Order

ORDER BY with several columns sorts by the first key and breaks ties with the next. Mixed ASC and DESC, deterministic tie-breakers, and custom orders with CASE.

  • Course: SQL study plan
  • Module: Sorting and limiting
  • Kind: Lesson
  • Reading time: 14 min
  • Runtime: MySQL 8 syntax on SQLite 3.49

How does ORDER BY with multiple columns work in SQL?

With several sort keys, ORDER BY a, b sorts every row by a first, and only rows with equal a are compared on b; a third key breaks the ties left after that. Each key takes its own direction, so ORDER BY class ASC, score DESC lists each class's best score first. End with a unique key to make the order fully deterministic.

Lesson

Real orderings almost always have more than one rule. A class list is sorted by class, then within each class by score, best first, then by name when two students share a score. A support queue shows urgent tickets first, then the oldest. A leaderboard ranks by points, then by who reached them first. SQL expresses each of those with one ORDER BY clause holding several sort keys, each with its own direction. This lesson covers how the keys combine, mixing ascending and descending, why the last key should be unique, and how to sort by an order that is not alphabetical or numeric — the custom order.

Keys are compared left to right

SELECT class, name, score
FROM students
ORDER BY class, score DESC, name;

The database compares two rows on the first key, class. If they differ, that decides their order and the other keys are never looked at. Only when the classes are equal does it compare score (descending), and only if those are equal too does it compare name.

classnamescore
AMeera91
AArjun85
ATara85
BKiran78
BDev60

Arjun and Tara share a class and a score, so the third key, name, puts Arjun first. Kiran's 78 sorts below Meera's 91 not because of the score but because class B comes after class A: a later key never overrides an earlier one. That is what people mean by "sort by class, then by score": a dictionary-style, lexicographic comparison.

Each key has its own direction

ASC and DESC attach to the key they follow. In ORDER BY class, score DESC, name the class and the name are ascending and only the score is descending. A very common bug is writing ORDER BY score, name DESC while meaning "both descending" — that sorts scores from lowest to highest. To reverse both:

ORDER BY score DESC, name DESC

Mixing directions is normal: "newest first, then alphabetical" is ORDER BY created_at DESC, title.

Make the last key unique

Two rows that tie on every key may come back in either order. The database is free to choose, and the choice can change from one run to the next — when the table grows, when the plan switches to an index, when a parallel scan finishes in a different order. SQL also makes no promise that sorting is stable: you cannot sort by name in one step and rely on that order surviving a later sort by score.

So if the order matters, finish with a key that no two rows share, usually the primary key:

SELECT id, name, points
FROM players
ORDER BY points DESC, id;

Now every row has exactly one place. This is not pedantry. Pagination (two lessons ahead) skips and repeats rows when the order is not unique, test output flips between runs, and a "first come, first served" ranking is only fair if the tie-breaker says who came first — a joined_at timestamp, then the id in case two players joined in the same second.

Sorting on computed keys

A key can be any expression, which lets you sort on facts the table does not store directly:

-- Longest titles first, then alphabetical
SELECT title
FROM books
ORDER BY CHAR_LENGTH(title) DESC, title;

-- Biggest discount in money, not in percent
SELECT name, price, discount_pct
FROM products
ORDER BY price * discount_pct / 100 DESC, id;

Boolean expressions are especially useful. In MySQL a comparison yields 1 for true and 0 for false, so sorting on a condition moves the matching rows to one end:

-- Featured products first (featured = 'yes' gives 1), then by price
SELECT name, featured, price
FROM products
ORDER BY featured = 'yes' DESC, price;

You saw the same trick with ORDER BY col IS NULL, col for NULLs-last in the previous lesson.

Custom orders with CASE

Statuses, priorities, sizes and weekdays have a natural order that is neither alphabetical nor numeric. Alphabetically, high < low < medium, which no one wants. The portable fix is to map each value to a rank with a CASE expression and sort on the rank:

SELECT id, title, priority
FROM tickets
ORDER BY CASE priority
           WHEN 'high'   THEN 1
           WHEN 'medium' THEN 2
           WHEN 'low'    THEN 3
           ELSE 4
         END,
         id;
idtitlepriority
2Checkout brokenhigh
5Refund stuckhigh
1Typo on FAQmedium
4Dark modelow

CASE gets a full lesson in the next module; for now read it as "if priority is 'high' the key is 1, if 'medium' it is 2", and so on. The ELSE 4 sends unexpected values, including NULL, to the end instead of the front — without it they would get NULL, which MySQL sorts first.

MySQL has a shorthand: FIELD(value, 'a', 'b', 'c') returns the position of value in the list (1, 2, 3) and 0 when it is not there.

ORDER BY FIELD(priority, 'high', 'medium', 'low'), id

Beware the 0: an unlisted value sorts before high. If the data can hold other values, use CASE with an ELSE, or list the order backwards and sort DESC. FIELD is MySQL-only; CASE works everywhere, which is why interview answers usually use it. A third option, when the order is real data rather than presentation, is to store a rank column (or a lookup table with one) and sort on that.

Sorting rows of a few groups

A multi-key sort is also how you make a report readable before you know GROUP BY. Sorting by department, then by salary descending, lays out each department as a block with its best-paid people at the top:

SELECT dept, name, salary
FROM employees
ORDER BY dept, salary DESC, name;

Picking just the top row of each block is a different problem — "top-N per group" — and needs window functions, which come later in the course. Sorting only arranges rows; it never removes them.

Before you move on

  • Keys compare left to right; a later key only breaks ties left by the earlier ones.
  • ASC/DESC belong to one key each.
  • SQL sorting is not stable; end with a unique key when order matters.
  • Expressions and boolean conditions make good keys.
  • Custom orders: CASE with an ELSE, or MySQL's FIELD (unlisted values give 0 and sort first).

Common questions

Does DESC apply to all columns in ORDER BY?

No. DESC applies only to the key it follows. In ORDER BY score, name DESC the score is ascending and only the name is descending; to sort both descending you write ORDER BY score DESC, name DESC.

How do I sort by a custom order in MySQL?

Map each value to a rank and sort on the rank: ORDER BY CASE status WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END. MySQL also offers ORDER BY FIELD(status, 'high', 'medium', 'low'), which returns a value's position in the list and 0 for anything not listed.

Why do I need a tie-breaker in ORDER BY?

Rows with equal sort keys may come back in any order, and that order can change between runs or after an index is added. Adding a unique column such as the primary key as the last key makes every row's position fixed, which pagination, reports and tests all rely on.

Is SQL ORDER BY a stable sort?

SQL makes no stability promise. Rows that tie on every sort key are not guaranteed to keep insertion order or any other order, so you cannot rely on an earlier sort surviving a later one. Spell out every key you care about in one ORDER BY.

Exercises

Class ranking

The students table:

idnameclassscore
1TaraA85
2DevB60
3MeeraA91
4KiranB78
5ArjunA85

List class, name, score: classes in alphabetical order, within a class the highest score first, and students with the same class and score alphabetically by name.

class | name | score
A | Meera | 91
A | Arjun | 85
A | Tara | 85
B | Kiran | 78
B | Dev | 60

Support queue in priority order

The tickets table:

idtitlepriorityopened_on
1Typo on FAQmedium2024-05-03
2Checkout brokenhigh2024-05-04
3Add dark modelow2024-05-01
4Login loophigh2024-05-02
5Slow searchmedium2024-05-03
6Old ticketurgent2024-04-01

List id, title, priority in queue order: high first, then medium, then low, then any other value, including NULL, last. Within a priority, the oldest opened_on first; then by id.

id | title | priority
4 | Login loop | high
2 | Checkout broken | high
1 | Typo on FAQ | medium
5 | Slow search | medium
3 | Add dark mode | low
6 | Old ticket | urgent

In this module: Sorting and limiting

← ORDER BY — putting rows in order · LIMIT and OFFSET — taking a slice of the result →