Technology  /  SQL

๐Ÿ—„๏ธ SQL 40 guides ยท updated 2026

The language of data โ€” from SELECT and JOINs to window functions, query plans, and the performance tuning that separates juniors from seniors.

SQL ORDER BY

ORDER BY sorts query results before theyโ€™re returned. Without it, the database returns rows in an indeterminate order that can change between runs. If result order matters, ORDER BY is required.


Basic Syntax

SELECT columns
FROM table_name
ORDER BY column ASC; -- ascending (Aโ†’Z, lowest to highest)
ORDER BY column DESC; -- descending (Zโ†’A, highest to lowest)

ASC is the default โ€” you can omit it:

ORDER BY name -- same as ORDER BY name ASC
ORDER BY price DESC -- descending: most expensive first

Ascending vs Descending

-- Products alphabetically A โ†’ Z
SELECT name, category FROM products ORDER BY name ASC;
-- Most expensive first
SELECT name, price FROM products ORDER BY price DESC;
-- Most recent orders first
SELECT order_id, amount, created_at FROM orders ORDER BY created_at DESC;
-- Oldest first (chronological)
SELECT order_id, created_at FROM orders ORDER BY created_at ASC;

Multi-Column Sorting

Specify multiple columns for primary โ†’ secondary โ†’ tertiary sort:

-- Primary: category alphabetically
-- Secondary: within each category, most expensive first
SELECT name, category, price
FROM products
ORDER BY
category ASC,
price DESC;

Each column has its own direction independently:

-- Employees: by department (Aโ†’Z), then by hire date (oldest first), then salary (highest first)
SELECT name, department, hire_date, salary
FROM employees
ORDER BY
department ASC,
hire_date ASC,
salary DESC;

ORDER BY Column Position

Reference by position number in the SELECT list (1-indexed):

SELECT category, COUNT(*) AS cnt, AVG(price) AS avg_price
FROM products
GROUP BY category
ORDER BY 2 DESC, 3 DESC; -- sort by cnt, then avg_price

Position numbers are useful with long expressions but reduce readability โ€” the number gives no hint of whatโ€™s being sorted.


ORDER BY Column Alias

Most databases allow sorting by an alias defined in SELECT:

SELECT
customer_id,
SUM(amount) AS total_spent
FROM orders
GROUP BY customer_id
ORDER BY total_spent DESC; -- alias defined in SELECT

ORDER BY Expression

Sort on a computed value, not just a raw column:

-- Sort by computed revenue
SELECT order_id, quantity, unit_price
FROM order_items
ORDER BY quantity * unit_price DESC;
-- Sort by string length
SELECT name FROM products ORDER BY LENGTH(name);
-- Sort by age at signup (oldest first)
SELECT name, birth_date FROM users
ORDER BY EXTRACT(YEAR FROM AGE(NOW(), birth_date)) DESC;

Handling NULLs in ORDER BY

Default NULL sorting varies by database. Control it explicitly:

-- PostgreSQL: NULLs at the end regardless of direction
ORDER BY last_login DESC NULLS LAST
ORDER BY last_login ASC NULLS LAST
-- NULLs first
ORDER BY last_login DESC NULLS FIRST
-- MySQL / SQL Server workaround: push NULLs to end
ORDER BY
CASE WHEN last_login IS NULL THEN 1 ELSE 0 END,
last_login DESC;

Custom Sort Order with CASE WHEN

When default alphabetical or numeric order isnโ€™t what you need:

-- Business-logic sort: show 'urgent' status first, then 'pending', then others
SELECT ticket_id, subject, status, created_at
FROM tickets
ORDER BY
CASE status
WHEN 'urgent' THEN 1
WHEN 'pending' THEN 2
WHEN 'open' THEN 3
ELSE 4
END ASC,
created_at ASC;

ORDER BY with LIMIT (Top-N)

The most common pattern โ€” get the top N rows by some criterion:

-- Top 10 products by revenue
SELECT product_id, name, total_revenue
FROM product_summary
ORDER BY total_revenue DESC
LIMIT 10;
-- 3 most recently updated records
SELECT id, name, updated_at FROM items
ORDER BY updated_at DESC
LIMIT 3;
-- 5 cheapest active products
SELECT name, price FROM products
WHERE is_active = TRUE
ORDER BY price ASC
LIMIT 5;

ORDER BY in Subqueries and CTEs

In most databases, ORDER BY in a subquery without LIMIT has no guaranteed effect on the outer query โ€” the database may not preserve order between query layers. Sorting only matters in the final result:

-- Sorting in an outer query is meaningful
SELECT * FROM (
SELECT customer_id, SUM(amount) AS total FROM orders GROUP BY 1
) t
ORDER BY total DESC;
-- Sorting inside a CTE without LIMIT is ignored in the final output
WITH ranked AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY score DESC) AS rn
FROM scores
)
SELECT * FROM ranked WHERE rn <= 10 ORDER BY score DESC;

Practical Examples

Sales report sorted by month and then category:

SELECT
DATE_TRUNC('month', order_date) AS month,
category,
SUM(amount) AS revenue
FROM orders
JOIN products USING (product_id)
GROUP BY 1, 2
ORDER BY 1 DESC, revenue DESC;

Customer leaderboard:

SELECT
customer_id,
name,
SUM(amount) AS lifetime_value,
COUNT(*) AS order_count
FROM customers
JOIN orders USING (customer_id)
WHERE orders.status = 'completed'
GROUP BY customer_id, name
ORDER BY lifetime_value DESC
LIMIT 50;

Recently active users:

SELECT user_id, email, last_active_at
FROM users
WHERE last_active_at IS NOT NULL
ORDER BY last_active_at DESC NULLS LAST
LIMIT 100;