SQL SELECT Queries Cheat Sheet
Quick-reference for SELECT fundamentals: filtering with WHERE, sorting with ORDER BY, limiting results, and conditional expressions. Each section includes copy-ready snippets with inline output comments.
SELECT Basics
SELECT retrieves columns from one or more tables. Use * for all columns or list specific names.
SELECT *
FROM users;SELECT id, name, email
FROM users;SELECT
first_name AS name,
email AS contact
FROM users;AS is optional — `first_name name` works too — but AS is clearer.
SELECT
name,
price,
price * 0.9 AS discounted_price
FROM products;WHERE Clause
WHERE filters rows before they reach GROUP BY, SELECT, or ORDER BY. Only rows where the condition evaluates to TRUE are included.
SELECT * FROM products WHERE price > 100;
SELECT * FROM products WHERE price <= 50;
SELECT * FROM products WHERE status <> 'archived';<> is the standard SQL "not equal." Some databases also accept !=.
-- AND binds tighter than OR — always parenthesize
SELECT * FROM users
WHERE (role = 'admin' OR role = 'editor')
AND is_active = true;SELECT * FROM users
WHERE NOT is_deleted;BETWEEN, IN, LIKE
Shorthand operators for range checks, set membership, and pattern matching.
SELECT * FROM orders
WHERE total BETWEEN 100 AND 500;
-- equivalent: total >= 100 AND total <= 500For timestamps, prefer >= start AND < end to avoid off-by-one bugs.
SELECT * FROM orders
WHERE status IN ('pending', 'processing', 'shipped');SELECT * FROM users
WHERE role NOT IN ('banned', 'suspended');NOT IN returns no rows if the list contains NULL. Use NOT EXISTS instead.
-- % = zero or more characters, _ = exactly one
SELECT * FROM users
WHERE email LIKE '%@gmail.com';
SELECT * FROM products
WHERE sku LIKE 'PRD-____'; -- 4 characters after PRD-SELECT * FROM products
WHERE name ILIKE '%phone%';MySQL LIKE is case-insensitive by default (depends on collation). PostgreSQL requires ILIKE.
IS NULL / IS NOT NULL
NULL comparisons require IS NULL — the = operator never matches NULL.
-- WRONG: = NULL never matches (returns UNKNOWN)
SELECT * FROM users WHERE deleted_at = NULL;
-- RIGHT: use IS NULL
SELECT * FROM users WHERE deleted_at IS NULL;
SELECT * FROM users WHERE deleted_at IS NOT NULL;SELECT
name,
COALESCE(nickname, name) AS display_name
FROM users;
-- Returns nickname if non-NULL, otherwise nameORDER BY
Sort results by one or more columns. Default direction is ASC (ascending).
SELECT * FROM products
ORDER BY price ASC; -- cheapest first (default)
SELECT * FROM products
ORDER BY price DESC; -- most expensive firstSELECT * FROM users
ORDER BY last_name ASC, first_name ASC;-- PostgreSQL: control where NULLs appear
SELECT * FROM users
ORDER BY last_login DESC NULLS LAST;Default NULL position varies by database. PostgreSQL: NULLs sort last in ASC, first in DESC.
SELECT * FROM products
ORDER BY price * quantity DESC;LIMIT and OFFSET
Restrict how many rows are returned and skip rows for pagination.
-- First 10 rows
SELECT * FROM products
ORDER BY created_at DESC
LIMIT 10;-- Page 3 (rows 21-30)
SELECT * FROM products
ORDER BY id
LIMIT 10 OFFSET 20;OFFSET-based pagination gets slower on later pages. For large tables, use keyset (cursor) pagination instead.
-- SQL Server: TOP N
SELECT TOP 10 * FROM products ORDER BY price DESC;
-- Standard SQL (also works in PostgreSQL):
SELECT * FROM products
ORDER BY price DESC
FETCH FIRST 10 ROWS ONLY;DISTINCT
Remove duplicate rows from the result set.
SELECT DISTINCT country
FROM users
ORDER BY country;-- Unique combinations of country + city
SELECT DISTINCT country, city
FROM users;-- First row per country (by name)
SELECT DISTINCT ON (country)
country, city, name
FROM users
ORDER BY country, name;DISTINCT ON is PostgreSQL-specific. Other databases use ROW_NUMBER() window function.
Column and Table Aliases
Aliases rename columns in output or shorten table references. They exist only for the duration of the query.
SELECT
COUNT(*) AS total_orders,
SUM(amount) AS revenue
FROM orders;SELECT u.name, o.total
FROM users u
JOIN orders o ON o.user_id = u.id;-- WRONG: alias not yet available in WHERE
SELECT price * 0.9 AS sale_price
FROM products
WHERE sale_price < 50; -- ERROR!
-- RIGHT: repeat the expression
SELECT price * 0.9 AS sale_price
FROM products
WHERE price * 0.9 < 50;Aliases are resolved in SELECT (step 6 of SQL execution order). WHERE runs at step 2.
CASE WHEN Expression
SQL's if/else. Use CASE in SELECT, WHERE, ORDER BY, or inside aggregate functions.
SELECT name,
CASE status
WHEN 'active' THEN 'Active'
WHEN 'inactive' THEN 'Inactive'
ELSE 'Unknown'
END AS status_label
FROM users;SELECT name, price,
CASE
WHEN price < 10 THEN 'budget'
WHEN price < 100 THEN 'mid-range'
ELSE 'premium'
END AS tier
FROM products;-- Custom sort order
SELECT * FROM tickets
ORDER BY
CASE priority
WHEN 'critical' THEN 1
WHEN 'high' THEN 2
WHEN 'medium' THEN 3
ELSE 4
END;SELECT
COUNT(*) AS total,
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) AS paid,
SUM(CASE WHEN status = 'refunded' THEN amount ELSE 0 END) AS refunded_total
FROM orders;Logical Operators
AND, OR, and NOT combine conditions. AND binds tighter than OR — always parenthesize mixed expressions.
-- What you wrote:
SELECT * FROM users
WHERE role = 'admin' OR role = 'editor' AND is_active;
-- What SQL evaluates:
-- role = 'admin' OR (role = 'editor' AND is_active)
-- ALL admins (even inactive) plus active editors!
-- Fix: parenthesize
SELECT * FROM users
WHERE (role = 'admin' OR role = 'editor') AND is_active;SELECT * FROM products
WHERE category NOT IN ('deprecated', 'hidden');
SELECT * FROM users
WHERE name NOT LIKE 'test%';
SELECT * FROM orders
WHERE total NOT BETWEEN 0 AND 1; -- likely bad dataType Casting
Convert values between types with CAST or the :: shorthand (PostgreSQL).
SELECT CAST('2024-01-15' AS DATE);
SELECT CAST(price AS INTEGER) FROM products;
SELECT CAST(42 AS TEXT);SELECT '2024-01-15'::date;
SELECT price::int FROM products;
SELECT 42::text;:: is PostgreSQL-specific. Use CAST for portability across databases.
Complete Query Example
A realistic SELECT combining multiple clauses in the correct order.
SELECT
category,
COUNT(*) AS product_count,
ROUND(AVG(price), 2) AS avg_price,
CASE
WHEN AVG(price) > 100 THEN 'premium'
WHEN AVG(price) > 25 THEN 'mid'
ELSE 'budget'
END AS tier
FROM products
WHERE is_active = true
AND created_at >= '2024-01-01'
GROUP BY category
HAVING COUNT(*) >= 3
ORDER BY avg_price DESC
LIMIT 10;Clause order: SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT.
Can you write this from memory?
Select all orders from the orders table where the status equals 'shipped'.