SQL (Structured Query Language) is the standard language for storing, manipulating and retrieving data in databases. This tutorial teaches the essentials, one statement at a time, each with a runnable example. When you are ready to practise, open any case and try the commands in its investigation console.
A database holds one or more tables. A table is a grid of
columns (fields) and rows (records). The
examples below use this sample Customers table:
| CustomerID | CustomerName | City | Country |
|---|---|---|---|
| 1 | Alfreds Futterkiste | Berlin | Germany |
| 2 | Ana Trujillo | Mexico City | Mexico |
| 3 | Around the Horn | London | UK |
| 4 | Berglunds snabbkop | Lulea | Sweden |
With SQL you can ask the database questions ("queries") and get back exactly the rows and columns you asked for.
SQL keywords are not case sensitive (select works like
SELECT), but writing keywords in UPPERCASE is the convention.
Statements end with a semicolon ; — required here, and good
practice everywhere. Text values go in single quotes: 'Berlin'.
Numbers do not: 42.
The SELECT statement retrieves data from a table. Use * for all columns, or name the columns you want:
SELECT * FROM Customers; SELECT CustomerName, City FROM Customers;
SELECT DISTINCT returns only different (unique) values, removing duplicates:
SELECT DISTINCT Country FROM Customers;
The WHERE clause filters rows, keeping only those that match a condition:
SELECT * FROM Customers WHERE Country = 'Mexico';
Comparison operators: = equal, <> (or !=) not equal, >, <, >=, <=.
SELECT * FROM Customers WHERE CustomerID > 2;
Combine conditions: AND requires all to be true, OR requires at least one, NOT negates. Use parentheses to group:
SELECT * FROM Customers WHERE Country = 'Germany' AND City = 'Berlin'; SELECT * FROM Customers WHERE City = 'Berlin' OR City = 'London'; SELECT * FROM Customers WHERE NOT Country = 'Sweden'; SELECT * FROM Customers WHERE Country = 'Germany' AND (City = 'Berlin' OR City = 'Munich');
Sorts the result. Ascending (ASC) is the default; DESC sorts descending:
SELECT * FROM Customers ORDER BY Country; SELECT * FROM Customers ORDER BY Country DESC; -- sort by several columns SELECT * FROM Customers ORDER BY Country, CustomerName;
LIMIT caps how many rows come back — handy for peeking at a big table, or combined with ORDER BY to get "the top one":
SELECT * FROM Customers LIMIT 3; -- the customer with the highest ID SELECT * FROM Customers ORDER BY CustomerID DESC LIMIT 1;
NULL means "no value". You cannot test it with =;
use IS NULL or IS NOT NULL:
SELECT * FROM Customers WHERE City IS NULL; SELECT * FROM Customers WHERE City IS NOT NULL;
LIKE searches for a pattern. % matches any number of
characters (including none); _ matches exactly one character:
-- starts with 'A' SELECT * FROM Customers WHERE CustomerName LIKE 'A%'; -- ends with 'n' SELECT * FROM Customers WHERE City LIKE '%n'; -- contains 'or' SELECT * FROM Customers WHERE CustomerName LIKE '%or%'; -- 'L' then any two characters then 'ea' SELECT * FROM Customers WHERE City LIKE 'L__ea';
IN is shorthand for several OR conditions on the same column:
SELECT * FROM Customers
WHERE Country IN ('Germany', 'UK', 'Mexico');
SELECT * FROM Customers
WHERE Country NOT IN ('Sweden');
BETWEEN selects values in a range, inclusive of both ends. Works for numbers, text and dates:
SELECT * FROM Customers WHERE CustomerID BETWEEN 2 AND 4;
An alias gives a column or a table a temporary name, to make output readable
or queries shorter. The AS keyword is optional for tables:
SELECT CustomerName AS Name, City AS Town FROM Customers; SELECT c.CustomerName FROM Customers c;
A JOIN combines rows from two tables using a related column —
usually an id. Given an Orders table with a
CustomerID column, this lists each order with its customer's name:
SELECT Orders.OrderID, Customers.CustomerName FROM Orders JOIN Customers ON Orders.CustomerID = Customers.CustomerID;
JOIN (inner join) returns only rows with a match in both tables.
LEFT JOIN returns all rows from the left table, with
NULLs where the right table has no match:
SELECT Customers.CustomerName, Orders.OrderID FROM Customers LEFT JOIN Orders ON Customers.CustomerID = Orders.CustomerID;
Aggregate functions compute one value from many rows:
SELECT COUNT(*) FROM Customers; -- how many rows SELECT MIN(CustomerID) FROM Customers; -- smallest value SELECT MAX(CustomerID) FROM Customers; -- largest value SELECT AVG(CustomerID) FROM Customers; -- average SELECT SUM(CustomerID) FROM Customers; -- total
GROUP BY collects rows that share a value into groups, usually to
aggregate per group — "how many customers in each country":
SELECT Country, COUNT(*) AS customers FROM Customers GROUP BY Country ORDER BY customers DESC;
WHERE filters rows before grouping; HAVING
filters the groups after aggregation:
SELECT Country, COUNT(*) AS customers FROM Customers GROUP BY Country HAVING COUNT(*) > 5;
Full SQL also creates tables and edits rows. You will meet these when you build your own database (or submit a case as a SQL dump), though the investigation console here is read-only:
CREATE TABLE Persons (ID INTEGER PRIMARY KEY, Name TEXT, City TEXT);
INSERT INTO Persons (Name, City) VALUES ('Tom Hardy', 'London');
UPDATE Persons SET City = 'Oslo' WHERE Name = 'Tom Hardy';
DELETE FROM Persons WHERE Name = 'Tom Hardy';
The fastest way to make SQL stick is to use it on a problem. Every case on
this site is a small database; explore it with
SELECT name FROM sqlite_master WHERE type = 'table';, peek at
tables with SELECT * FROM some_table LIMIT 3;, and follow the
clues with the statements above.