30 Frequently Used SQL Queries for Beginners
Thirty SQL examples are useful only if they actually run and if beginners can see what data each query reads or changes. This guide uses one small, entirely fictional SQLite database throughout. The syntax was executed locally against SQLite; other SQL engines use different conventions for row limits, dates, outer joins, data types and transactions. Do not run the INSERT, UPDATE, DELETE or CREATE INDEX statements against production tables while learning.
The sample has five fictional customers and five orders. Use a disposable SQLite database or an in-memory connection. Run the following setup once before examples 1–30. All addresses use the reserved example.test domain, and no real customer records are involved.
CREATE TABLE customers (id INTEGER PRIMARY KEY, full_name TEXT NOT NULL, city TEXT, tier TEXT, email TEXT);
CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER NOT NULL REFERENCES customers(id), total REAL NOT NULL, ordered_on TEXT NOT NULL, status TEXT NOT NULL);
INSERT INTO customers VALUES
(1,'Ada Stone','New York','Gold','ada@example.test'),
(2,'Ben Reed','Austin','Silver','ben@example.test'),
(3,'Cleo Li','New York','Gold','cleo@example.test'),
(4,'Dario Omar',NULL,'Bronze','dario@example.test'),
(5,'Erika Zhou','San Diego','Silver','erika@example.test');
INSERT INTO orders VALUES
(101,1,120.0,'2026-03-05','paid'),
(102,1,40.0,'2026-03-06','paid'),
(103,2,99.0,'2026-04-01','paid'),
(104,3,205.0,'2026-04-15','paid'),
(105,3,0.0,'2026-04-19','refunded');
The customers.id column uniquely identifies each customer, and orders.customer_id references it. One customer deliberately has no city, another has no order, and one order is marked refunded; these test cases make NULL, joins, and filters meaningful. For queries 27–30, use a transaction you roll back so you can rerun this guide without changing your practice database.
Choose named fields so downstream readers know the output contract.
SELECT full_name, city FROM customers;
The star is useful for a quick inspection but avoid it in durable production queries.
SELECT * FROM customers LIMIT 3;
WHERE reduces results to matching records.
SELECT full_name FROM customers WHERE city = 'New York';
The <> operator means not equal; NULL needs its own treatment.
SELECT full_name FROM customers WHERE tier <> 'Bronze';
IN is clearer than repeating many OR predicates.
SELECT full_name FROM customers WHERE city IN ('New York', 'Austin');
BETWEEN includes both endpoints.
SELECT id, total FROM orders WHERE total BETWEEN 50 AND 200;
LIKE searches according to database collation and pattern rules.
SELECT full_name FROM customers WHERE full_name LIKE 'A%';
Use IS NULL; ordinary equality with NULL does not give a true match.
SELECT full_name FROM customers WHERE city IS NULL;
ORDER BY makes output ordering explicit rather than accidental.
SELECT full_name, city FROM customers ORDER BY full_name ASC;
Sort before limiting so the chosen rows represent the intended ranking.
SELECT id, total FROM orders ORDER BY total DESC LIMIT 2;
DISTINCT removes duplicate output rows; it does not validate data accuracy.
SELECT DISTINCT city FROM customers WHERE city IS NOT NULL;
COUNT(*) includes rows even when individual fields contain NULL.
SELECT COUNT(*) AS customer_count FROM customers;
A business aggregate must state which order statuses are included.
SELECT SUM(total) AS paid_total FROM orders WHERE status = 'paid';
AVG ignores NULL values; define whether canceled/refunded orders belong in the metric.
SELECT ROUND(AVG(total), 2) AS average_order FROM orders;
Range inspection is a starting point for detecting outliers.
SELECT MIN(total) AS smallest, MAX(total) AS largest FROM orders;
GROUP BY produces a summary for each status.
SELECT status, COUNT(*) AS n FROM orders GROUP BY status;
HAVING filters aggregated groups, unlike WHERE, which filters source rows.
SELECT customer_id, SUM(total) AS spend FROM orders GROUP BY customer_id HAVING SUM(total) > 100;
An inner join includes only matching customer/order pairs.
SELECT c.full_name, o.total FROM customers AS c INNER JOIN orders AS o ON o.customer_id = c.id;
The left join retains customers without matching orders and yields NULL order fields.
SELECT c.full_name, o.id AS order_id FROM customers AS c LEFT JOIN orders AS o ON o.customer_id = c.id ORDER BY c.id, o.id;
COALESCE handles presentation of missing data without changing the source.
SELECT full_name, COALESCE(city, 'Unknown') AS city_label FROM customers;
CASE makes an explicit conditional output; threshold choice is an illustration.
SELECT id, CASE WHEN total >= 100 THEN 'large' ELSE 'small' END AS basket FROM orders;
Here ordered_on contains YYYY-MM-DD text; production systems need explicit timestamp/time-zone rules.
SELECT substr(ordered_on, 1, 7) AS month, COUNT(*) AS n FROM orders GROUP BY substr(ordered_on, 1, 7);
An IN subquery selects customers meeting an order condition.
SELECT full_name FROM customers WHERE id IN (SELECT customer_id FROM orders WHERE total >= 100);
EXISTS tests for at least one related row rather than counting all of them.
SELECT full_name FROM customers AS c WHERE EXISTS (SELECT 1 FROM orders AS o WHERE o.customer_id = c.id);
A CTE names an intermediate query and can make a multi-step report easier to audit.
WITH spend AS (SELECT customer_id, SUM(total) AS amount FROM orders GROUP BY customer_id) SELECT customer_id, amount FROM spend WHERE amount >= 100;
UNION removes duplicates. Both queries must return compatible column counts and types.
SELECT city AS label FROM customers WHERE city IS NOT NULL UNION SELECT status AS label FROM orders;
Transaction safety: before the remaining four examples, start a transaction using BEGIN;. Run one statement, inspect the result, then use ROLLBACK;; start another transaction for the next statement. This prevents a practice session from quietly altering shared data. In the local QA fixture each of the four statements was run inside a separate savepoint and then rolled back.
Use an explicit column list; do not insert real customer data in a practice exercise.
INSERT INTO customers (id, full_name, city, tier, email) VALUES (6, 'Fae Green', 'Boston', 'Bronze', 'fae@example.test');
Inspect the intended row before an UPDATE; missing WHERE can modify every row.
UPDATE customers SET tier = 'Gold' WHERE id = 2;
Preview the WHERE predicate first. In a real system, follow retention and recovery policies.
DELETE FROM orders WHERE id = 105;
An index can improve some lookups but adds storage and write overhead; validate plans and workload.
CREATE INDEX IF NOT EXISTS idx_orders_customer_date ON orders(customer_id, ordered_on);
Use explicit column names and reproducible test data. A successful query is not necessarily a correct business report: totals depend on refund rules, customer identities, date interpretation and who is authorized to see the records. Parameterize values from users instead of constructing SQL with string concatenation. For writes, review which rows a predicate affects, establish backup and recovery expectations, and record the change owner.
The core SQLite SELECT documentation explains retrieval, grouping and joins. The transaction reference describes BEGIN, COMMIT and ROLLBACK. These are technical references, not a claim that the same exact SQL runs unchanged on every database platform.
A good learning test is to add a sixth fictional order or change one tier and predict how the aggregate and join results will differ before running them. Understanding those consequences is more valuable than memorizing a long list of commands.