Parse ORDER BY with any number of keys, each an expression or an alias with its direction, sort the rows the WHERE keeps with a stable multi-key comparison, and plan a Sort node, except when the scan already delivers the order.
A table in this engine already comes out in one order: ascending primary key, because the B+Tree keeps it that way. Every other order has to be made. order by dept, salary desc asks for something the storage knows nothing about, and the only way to deliver it is to collect the rows and sort them.
That makes Sort different from every operator so far. A scan, a Filter and a Project each hand up a row as soon as they have one. Sort cannot: the last row the scan reads may be the one that has to come out first. It is a blocking operator, and it holds its whole input in memory. This stage sorts in memory; the next one deals with input that does not fit.
ORDER BY takes a list of keys. The first key decides; the second is only looked at when the first is equal, the third when both are, and so on. Each key has its own direction, so order by dept asc, salary desc keeps departments in alphabetical order and puts the highest salary first within each:
| id | dept | salary | why it is here |
|---|---|---|---|
| 3 | eng | 120 | eng is first; 120 is its highest salary |
| 1 | eng | 100 | ties with 5 on both keys |
| 5 | eng | 100 | so the tie keeps key order: 1, then 5 |
| 7 | hr | 10 | |
| 6 | hr | 9 | |
| 2 | ops | 90 | ties with 4 |
| 4 | ops | 90 |
Types compare the way they always have: INTs as numbers, so 9 comes before 10 and 90 before 100, and VARCHARs byte by byte, so Gus comes before ann. A key can be any expression, salary + bonus or -salary or bonus > 1, and a truth value sorts false before true.
Rows 1 and 5 above are equal on every key. Something still has to decide which comes first, and the contract says: the order they arrived in. Rows arrive from the scan in primary-key order, so a stable sort, one that never swaps equal elements, gives ties in key order, every time, for free.
An unstable sort (a textbook quicksort or heapsort) gives a valid order too, but not the same one every time, and the tests, like any user comparing two runs, need the same answer. Most languages ship a stable sort next to the unstable one; use it. Do not break ties by adding the key as a hidden last sort key: it gives the same answer here, but it hides the property the next stage depends on, when runs are merged.
NULL is not smaller or larger than a value; it is unknown. A sort still has to put it somewhere, and this engine puts it before every value. Under ASC the NULLs come first, and under DESC, which reverses everything, they come last. Two NULLs are equal, so they keep key order like any other tie.
A key that is just a name, and the alias of a select-list item, means that item: select salary * 2 as s2 from emp order by s2 sorts by the doubled salary. This holds even when the table has a column of the same name, so select -salary as salary ... order by salary sorts by the negated value. ORDER BY is the one clause that can see aliases, because it runs after the select list is known.
Only a bare name is looked up that way. Inside a larger key, order by s2 + 1, a name is a column of the table as everywhere else, and s2 is not one: 00402.
order by id asks for the order the scan already delivers. The planner sees that the first key is the table's first column, ascending, and plans no Sort at all: the plan is the one without ORDER BY. Any later keys cannot matter, since the first column never has two equal values. order by id desc still needs a Sort, because the scan only reads forwards.
Sorting is a comparison function used many times. Getting the function right is most of the stage.
Sort sits between the Filter and the Project. It is the first operator that cannot hand up a row before it has seen them all.
The planner knows the order the scan delivers. When it is the order asked for, the Sort is pure cost.
These are the exact lines the tests check. The full definition is on the output contract page, sections 39 to 44.
| Input | Output |
|---|---|
tokenize order by a asc, b desc | [KEYWORD_ORDER - order], [KEYWORD_BY - by], [IDENTIFIER - a], [KEYWORD_ASC - asc], [SYMBOL - ,], [IDENTIFIER - b], [KEYWORD_DESC - desc] |
ast select id from emp where salary > 50 order by dept, salary + bonus desc; | ... Where: salary > 50, then Order: [dept ASC, salary + bonus DESC] |
explain select id from emp order by dept, salary desc; | Project (cols=[id]), then Sort (key=dept ASC, salary DESC), then SeqScan (table=emp) |
explain select * from emp order by id; | SeqScan (table=emp) alone: no Sort |
| an unknown column in a key, or an alias inside a larger key | [ERROR:00402] <message> |
order by with no key, order id, asc desc | [ERROR:00302] <message> |
Every key prints its direction in ast and in the plan, ASC when none was written. Keys print as expressions do (section 40), and an alias key prints as the alias. The checks run in the order of section 44: columns of the select list, the WHERE, then the keys; then types in the same order; then numbers. rows_examined is the scan's; Sort adds nothing.
$droid --db staff.dbWelcome to droid!>create table emp (id int, dept varchar(10), salary int, bonus int);Create table statement executed successfully>insert into emp (id, dept, salary, bonus) values (3, 'eng', 120, 0);Insert statement executed successfully (1 rows)>insert into emp (id, dept, salary) values (1, 'ops', 90);Insert statement executed successfully (1 rows)>insert into emp (id, dept, salary, bonus) values (2, 'eng', 100, 5);Insert statement executed successfully (1 rows)>select id, dept, salary from emp order by dept, salary desc;id | dept | salary3 | eng | 1202 | eng | 1001 | ops | 90(3 rows)[EXEC] rows_examined=3>select id, bonus from emp order by bonus desc;id | bonus2 | 53 | 01 | NULL(3 rows)[EXEC] rows_examined=3>explain select id from emp order by salary;[PLAN] select id from emp order by salary[PLAN] └── Project (cols=[id])[PLAN] └── Sort (key=salary ASC)[PLAN] └── SeqScan (table=emp)>explain select id from emp order by id;[PLAN] select id from emp order by id[PLAN] └── Project (cols=[id])[PLAN] └── SeqScan (table=emp)>.exitexiting.. good bye!
The NULL bonus went last under DESC. The last plan has no Sort: ordering by the key costs nothing. The messages are yours to word; the tests check only the codes.
10 sorts before 9. Compare by the value's type.dept asc, salary desc into dept desc, salary asc and reverses ties too.order by s2 + 1 must be 00402; only a bare alias means the item.order by id. The answer is right but the plan is not: the scan already gives that order.The page above is the same for every language. These are the only language-specific hints.
qsort is not stable. Write a merge sort over an array of row pointers (it is short, and the next stage needs merging anyway), or sort an array of (keys, arrival index) pairs and use the arrival index, which is not the key, to break ties.std::stable_sort, not std::sort.slice::sort_by is stable; sort_unstable_by is not. Return an Ordering and chain keys with Ordering::then_with.std.mem.sort is stable (a block sort); std.mem.sortUnstable is not.