SQL querying uses the SELECT statement to describe which columns and rows to retrieve — WHERE filters rows, ORDER BY sorts results, LIMIT restricts output count, and CASE provides conditional expressions — all evaluated in a defined logical order: FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY, LIMIT.
SQL is declarative — you describe the data you want, and the database figures out the most efficient way to retrieve it. The SELECT statement is the core tool for every query.
SELECT basics
-- Basic SELECT: choose which columns, from which table
SELECT name, department, salary
FROM employees;
-- All columns (avoid in production code — fragile if schema changes)
SELECT * FROM employees;
-- Column aliases
SELECT
name AS employee_name,
salary AS annual_salary,
salary / 12.0 AS monthly_salary,
UPPER(department) AS dept_upper
FROM employees;
-- DISTINCT — remove duplicate rows
SELECT DISTINCT department FROM employees;
-- LIMIT and OFFSET — pagination
SELECT name, salary
FROM employees
ORDER BY salary DESC
LIMIT 10 OFFSET 20; -- rows 21-30WHERE — filtering rows
-- Comparison operators
SELECT * FROM employees WHERE salary > 80000;
SELECT * FROM employees WHERE department = 'Engineering';
SELECT * FROM employees WHERE hire_date >= '2020-01-01';
-- Logical operators: AND, OR, NOT
SELECT * FROM employees
WHERE department = 'Engineering'
AND salary > 80000;
SELECT * FROM employees
WHERE department = 'Engineering'
OR department = 'Design';
-- IN — match any value in a list (cleaner than multiple OR)
SELECT * FROM employees
WHERE department IN ('Engineering', 'Design', 'Product');
-- BETWEEN — inclusive range
SELECT * FROM employees
WHERE salary BETWEEN 60000 AND 90000;
-- LIKE — pattern matching
-- % = zero or more characters, _ = exactly one character
SELECT * FROM employees WHERE name LIKE 'P%'; -- starts with P
SELECT * FROM employees WHERE email LIKE '%@gmail.com';
SELECT * FROM employees WHERE name LIKE '_riya'; -- 5-letter names ending in "riya"
-- ILIKE — case-insensitive LIKE (PostgreSQL)
SELECT * FROM employees WHERE name ILIKE 'priya';
-- NULL checks — NEVER use = NULL
SELECT * FROM employees WHERE manager_id IS NULL;
SELECT * FROM employees WHERE phone IS NOT NULL;ORDER BY and CASE
-- ORDER BY — sort results
SELECT name, salary, department
FROM employees
ORDER BY salary DESC, name ASC; -- salary descending, then name ascending
-- CASE — conditional expression (like if/else in SQL)
SELECT
name,
salary,
CASE
WHEN salary >= 100000 THEN 'Senior'
WHEN salary >= 70000 THEN 'Mid-level'
WHEN salary >= 50000 THEN 'Junior'
ELSE 'Entry'
END AS level
FROM employees;
-- CASE in ORDER BY — custom sort order
SELECT name, status
FROM orders
ORDER BY
CASE status
WHEN 'urgent' THEN 1
WHEN 'pending' THEN 2
WHEN 'complete' THEN 3
ELSE 4
END;
-- COALESCE — return first non-null value
SELECT
name,
COALESCE(phone, mobile, 'no contact') AS contact_number
FROM employees;
-- NULLIF — return NULL if two values are equal (prevents division by zero)
SELECT revenue / NULLIF(expenses, 0) AS ratio FROM financials;Subqueries
-- Subquery in WHERE — scalar subquery returns one value
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
-- Subquery in WHERE — returns a list (use with IN)
SELECT name FROM employees
WHERE department_id IN (
SELECT id FROM departments WHERE location = 'Mumbai'
);
-- Correlated subquery — references the outer query (runs once per row)
SELECT e.name, e.salary
FROM employees e
WHERE e.salary = (
SELECT MAX(e2.salary)
FROM employees e2
WHERE e2.department = e.department
);
-- "Employees who earn the most in their department"
-- Subquery in FROM clause (derived table / inline view)
SELECT dept_name, avg_salary
FROM (
SELECT department AS dept_name,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department
) AS dept_stats
WHERE avg_salary > 75000;
-- EXISTS — check if any matching rows exist (often faster than IN)
SELECT name FROM employees e
WHERE EXISTS (
SELECT 1 FROM projects p
WHERE p.lead_id = e.id
);String and date functions
-- String functions (PostgreSQL)
SELECT
UPPER(name), -- PRIYA SHARMA
LOWER(email), -- priya@example.com
LENGTH(name), -- 11
TRIM(BOTH ' ' FROM name), -- strip whitespace
SUBSTRING(name FROM 1 FOR 5), -- first 5 chars
POSITION('.' IN email), -- find position
REPLACE(phone, '-', ''), -- remove dashes
CONCAT(first_name, ' ', last_name), -- or use || operator
first_name || ' ' || last_name AS full_name
FROM employees;
-- Date functions
SELECT
CURRENT_DATE, -- today
CURRENT_TIMESTAMP, -- now with time zone
hire_date + INTERVAL '30 days', -- date arithmetic
AGE(hire_date), -- e.g. "3 years 2 months"
EXTRACT(YEAR FROM hire_date), -- 2021
DATE_TRUNC('month', hire_date), -- first day of month
TO_CHAR(hire_date, 'DD/MM/YYYY') -- format as string
FROM employees;
-- Type casting
SELECT
'42'::integer, -- PostgreSQL cast syntax
CAST('42' AS integer), -- standard SQL cast
42::text,
'2026-06-10'::dateWHERE filters rows before grouping; HAVING filters groups after grouping. You cannot use aggregate functions (COUNT, SUM, AVG) in a WHERE clause — they don't exist yet when WHERE runs. Use HAVING to filter on aggregates. A query with both: WHERE runs first on individual rows, GROUP BY groups them, then HAVING filters the groups.NULL = NULL is NULL (unknown), not TRUE. In SQL, NULL represents an unknown value. Comparing NULL to anything — including another NULL — produces NULL, not TRUE or FALSE. This is why WHERE phone = NULL never matches anything. Always use IS NULL or IS NOT NULL.SELECT and select are identical. But string comparisons respect case: WHERE name = 'priya' will not match "Priya". Use LOWER(name) = 'priya' or ILIKE (PostgreSQL) for case-insensitive search.The query execution order
SQL queries are written in a specific order, but executed in a different order. The logical execution order is: (1) FROM — identify the base tables and joins; (2) WHERE — filter individual rows; (3) GROUP BY — group rows into sets; (4) HAVING — filter groups; (5) SELECT — compute output columns and expressions; (6) DISTINCT — remove duplicates; (7) ORDER BY — sort results; (8) LIMIT/OFFSET — restrict output count. This ordering explains why you cannot reference a SELECT alias in a WHERE clause (WHERE runs before SELECT computes aliases) but can reference it in ORDER BY (ORDER BY runs after SELECT).
The relational model and Codd's rules
SQL is based on Edgar F. Codd's relational model (1970 paper: "A Relational Model of Data for Large Shared Data Banks"). The model defines: relations (tables) as sets of tuples (rows); attributes (columns) have domains (types); the relational algebra defines operations (select σ, project π, join ⋈, union ∪, difference −, product ×). SQL's SELECT-FROM-WHERE corresponds to the relational algebra expression π(σ(R)) — project (SELECT columns) of a select (WHERE conditions) of a relation (FROM table). Codd defined 12 rules for a "fully relational" DBMS. No commercial database satisfies all 12; PostgreSQL satisfies the most.