Skip to main content

🗃️ SQL Syntax and Queries

SQL is the language you use to ask a relational database questions — and it has quietly powered the web for over four decades. In this lesson you'll learn how a query is built clause by clause, how to select and filter exactly the rows you want, and how to sort, de-duplicate, and page through results in a way that works across PostgreSQL, MySQL, and SQLite.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Describe the five sublanguages of SQL (DDL, DML, DQL, DCL, TCL) and where SELECT fits
  • Write SELECT queries that project columns, apply aliases, and filter rows with WHERE
  • Use the special operators BETWEEN, IN, and LIKE, and handle NULL correctly with IS NULL
  • Sort results with ORDER BY, remove duplicates with DISTINCT, and page results with LIMIT/OFFSET
  • Recognize the logical order of evaluation that explains why aliases and clauses behave the way they do

Estimated Time: 35–45 minutes  •  Difficulty: Beginner

Hands-on: Build and query a small books table in your own database, then answer five realistic questions with SQL.

In This Lesson

What SQL Is (and Isn't)

SQL (Structured Query Language, often pronounced "sequel") is the standard language for talking to relational databases — databases that store data in tables of rows and columns, like a collection of very strict spreadsheets that know how to reference each other. Born at IBM in the 1970s from Edgar Codd's relational model, SQL is now an ISO standard implemented by PostgreSQL, MySQL/MariaDB, SQL Server, Oracle, and the tiny-but-mighty SQLite.

The single most important idea about SQL is that it is declarative. You describe what data you want, not how to fetch it. You never write a loop to walk rows one at a time; you state a condition and the database's query planner figures out the most efficient way to satisfy it.

💡 Analogy — the library assistant: Imagine a library with millions of books across many floors. You don't wander the stacks yourself. You hand a precise request to an expert assistant — "every science-fiction title published since 2020, newest first" — and they return exactly that. SQL is how you phrase the request; the database is the assistant who knows where everything lives and how to get it fastest.

📖 Key Terms

Table (relation): a named collection of rows, all sharing the same columns.

Row (record / tuple): one entry — a single book, customer, or order.

Column (field / attribute): one named piece of data with a fixed type, e.g. price DECIMAL.

Query: a statement (usually a SELECT) that asks the database a question and returns a result set.

The Five Sublanguages of SQL

People say "SQL" as if it were one thing, but the commands split into five functional groups. Knowing which group a keyword belongs to helps you reason about what it does — and how dangerous it is.

graph TD SQL[SQL] --> DDL[DDL — Data Definition] SQL --> DML[DML — Data Manipulation] SQL --> DQL[DQL — Data Query] SQL --> DCL[DCL — Data Control] SQL --> TCL[TCL — Transaction Control] DDL --> DDLx[CREATE · ALTER · DROP · TRUNCATE] DML --> DMLx[INSERT · UPDATE · DELETE] DQL --> DQLx[SELECT] DCL --> DCLx[GRANT · REVOKE] TCL --> TCLx[COMMIT · ROLLBACK · SAVEPOINT]
GroupPurposeKey statements
DDL — DefinitionDefine the shape of the database (schema)CREATE, ALTER, DROP, TRUNCATE
DML — ManipulationChange the data inside tablesINSERT, UPDATE, DELETE
DQL — QueryRead data back outSELECT
DCL — ControlManage permissionsGRANT, REVOKE
TCL — TransactionsGroup changes so they succeed or fail togetherCOMMIT, ROLLBACK, SAVEPOINT

This lesson lives almost entirely in DQL — the humble SELECT. It's the command you'll write most often in your career by a wide margin, so it's worth learning deeply. The next lesson covers the DML trio (INSERT/UPDATE/DELETE) that make up CRUD.

Anatomy of a Query

A SELECT query is built from clauses that always appear in the same written order. Not every clause is required, but when present they must follow this sequence:

SELECT   column_a, column_b        -- which columns to return
FROM     table_name                -- where the data lives
WHERE    condition                 -- which rows to keep
ORDER BY column_a DESC             -- how to sort them
LIMIT    10;                        -- how many to return
Written order versus logical execution order of a SELECT query A query is written SELECT, FROM, WHERE, ORDER BY, LIMIT, but the database evaluates FROM first, then WHERE, then SELECT, then ORDER BY, then LIMIT. How you WRITE it How the DB RUNS it 1. SELECT columns 2. FROM table 3. WHERE filter 4. ORDER BY sort 5. LIMIT count 1. FROM table 2. WHERE filter 3. SELECT columns 4. ORDER BY sort 5. LIMIT count
Figure 1 — You write SELECT first, but the database evaluates FROM and WHERE first and SELECT only third. This is why you generally can't reference a SELECT alias inside a WHERE clause.

Syntax rules that trip people up

  • Keywords are case-insensitive (SELECT = select), but by convention keywords are uppercased. Table and column names may be case-sensitive depending on the database and operating system.
  • Whitespace is ignored, so indent generously for readability.
  • String literals use single quotes: 'USA'. Double quotes mean something different — a quoted identifier (a column or table name).
  • Statements end with a semicolon ; — required when running several statements together.
  • Comments: -- to end of line, or /* block */.

SELECT: Projecting Columns

Choosing which columns to return is called projection. Start with everything, then learn to be specific.

-- Every column of every row (fine for exploring, avoid in production)
SELECT * FROM customers;

-- Only the columns you actually need (faster, clearer, safer)
SELECT customer_name, email
FROM customers;

⚠️ Avoid SELECT * in application code

It's great for poking around interactively, but naming your columns explicitly is better in real code: queries stay fast, they don't silently break when someone adds a column, and readers can see exactly what your feature depends on.

Column aliases

Aliases rename a column in the result set — useful for readable headers or for labeling computed values.

SELECT
    first_name AS "First Name",
    last_name  AS "Last Name",
    unit_price * units_in_stock AS inventory_value   -- a computed column needs a name
FROM products;

The AS keyword is optional — unit_price "Price" works too — but including it makes your intent obvious. Note that because SELECT runs after WHERE (Figure 1), you usually cannot filter on inventory_value in the same query's WHERE clause; you'd repeat the expression or wrap the query.

Filtering with WHERE

The WHERE clause keeps only the rows that make its condition true. This is where SQL earns its keep.

Comparison operators

OperatorMeaningExample
=Equal toWHERE price = 9.99
< / >Less / greater thanWHERE quantity > 100
<= / >=Less / greater than or equalWHERE salary >= 50000
<> or !=Not equal toWHERE status <> 'Cancelled'

Combining conditions with AND, OR, NOT

-- Both must be true
SELECT product_name, unit_price, category
FROM products
WHERE unit_price > 50 AND category = 'Electronics';

-- Either may be true
SELECT first_name, last_name
FROM employees
WHERE department = 'Sales' OR department = 'Marketing';

-- Negate a condition
SELECT order_id, status
FROM orders
WHERE NOT status = 'Delivered';

⚠️ Parenthesize mixed AND/OR

AND binds tighter than OR, so a OR b AND c means a OR (b AND c) — rarely what you intend. Always group explicitly:

SELECT product_id, product_name, unit_price
FROM products
WHERE (category = 'Beverages' OR category = 'Condiments')
  AND unit_price > 10
  AND units_in_stock > 0;

BETWEEN, IN, LIKE & NULL

Four tools that make filters shorter and more expressive.

BETWEEN — an inclusive range

SELECT product_name, unit_price
FROM products
WHERE unit_price BETWEEN 10 AND 20;   -- same as >= 10 AND <= 20

-- Works for dates too
SELECT order_id, order_date
FROM orders
WHERE order_date BETWEEN '2024-01-01' AND '2024-03-31';

IN — match any value in a list

SELECT customer_name, country
FROM customers
WHERE country IN ('USA', 'Canada', 'Mexico');   -- shorthand for three ORs

-- IN also accepts a subquery
SELECT product_name
FROM products
WHERE category_id IN (
    SELECT category_id FROM categories WHERE category_name = 'Beverages'
);

LIKE — pattern matching on text

Two wildcards: % matches any run of characters (including none), and _ matches exactly one character.

SELECT customer_name FROM customers WHERE customer_name LIKE 'A%';      -- starts with A
SELECT product_name  FROM products  WHERE product_name  LIKE '%coffee%'; -- contains coffee
SELECT last_name     FROM employees WHERE last_name     LIKE '_____';    -- exactly 5 chars

💡 Case-insensitive matching

Plain LIKE is case-sensitive on some engines and not on others. PostgreSQL offers ILIKE for guaranteed case-insensitive matching; on MySQL, the default collation is usually case-insensitive already. When it matters, normalize with LOWER(column) LIKE LOWER('a%').

NULL — the absence of a value

NULL means "unknown," not "zero" or "empty string." Because unknown compared to anything is still unknown, = NULL never matches. Use IS NULL / IS NOT NULL.

-- Employees with no assigned manager
SELECT employee_id, first_name, last_name
FROM employees
WHERE manager_id IS NULL;

-- Customers who DO have a phone on file
SELECT customer_name, phone
FROM customers
WHERE phone IS NOT NULL;

Sorting, DISTINCT & Paging

ORDER BY

Result order is not guaranteed unless you ask for it. ASC (ascending) is the default; DESC reverses it. You can sort by several columns — the first is primary, the next breaks ties.

-- Alphabetical by name
SELECT customer_name, city FROM customers ORDER BY customer_name;

-- Group by department, highest paid first within each
SELECT first_name, last_name, department, salary
FROM employees
ORDER BY department ASC, salary DESC;

DISTINCT — remove duplicate rows

DISTINCT collapses identical result rows into one. It applies to the whole selected row, not a single column.

SELECT DISTINCT country FROM customers ORDER BY country;
SELECT DISTINCT city, country FROM customers ORDER BY country, city;
SELECT DISTINCT collapsing duplicate country values A five-row country column containing USA, Canada, USA, Mexico, Canada becomes a three-row result of USA, Canada, Mexico. Source rows DISTINCT result USA Canada USA Mexico Canada DISTINCT USA Canada Mexico
Figure 2 — SELECT DISTINCT country keeps one row per unique value; the duplicate USA and Canada rows are folded away.

Limiting and paging results

The standard evolved unevenly, so the syntax differs by engine. PostgreSQL, MySQL, and SQLite all use LIMIT; SQL Server uses TOP; strict ANSI SQL and Oracle 12c+ use FETCH.

-- PostgreSQL / MySQL / SQLite
SELECT product_name, unit_price FROM products
ORDER BY unit_price DESC
LIMIT 10;

-- Pagination: page 2 of 10 rows (skip the first 10)
SELECT product_name FROM products
ORDER BY product_name
LIMIT 10 OFFSET 10;

-- SQL Server
SELECT TOP 10 product_name, unit_price FROM products ORDER BY unit_price DESC;

-- ANSI SQL / Oracle 12c+
SELECT product_name FROM products ORDER BY unit_price DESC
FETCH FIRST 10 ROWS ONLY;

⚠️ Always pair LIMIT with ORDER BY

Without an ORDER BY, "the first 10 rows" is meaningless — the database may return any 10. Sort first, then limit.

Hands-on Exercise

🏋️ Query a real bookstore table

Objective: Create a small table, load a few rows, and answer five questions with SELECT. Any of PostgreSQL, MySQL, or SQLite works — the code below is standard across all three.

Step 1 — Create and seed the table

CREATE TABLE books (
    book_id      INTEGER PRIMARY KEY,
    title        VARCHAR(120) NOT NULL,
    author       VARCHAR(80)  NOT NULL,
    genre        VARCHAR(40),
    price        DECIMAL(6,2) NOT NULL,
    published    DATE,
    copies_left  INTEGER      NOT NULL
);

INSERT INTO books (book_id, title, author, genre, price, published, copies_left) VALUES
(1, 'The Silent Orbit',    'A. Vega',    'Sci-Fi',  14.99, '2023-04-10', 3),
(2, 'Garden of Bytes',     'M. Cruz',    'Sci-Fi',  22.50, '2021-09-01', 0),
(3, 'Quiet Mornings',      'A. Vega',    'Poetry',   9.95, '2024-01-15', 12),
(4, 'The Last Ledger',     'R. Santos',  'Mystery', 18.00, '2020-06-30', 4),
(5, 'Debugging the Heart', 'M. Cruz',    'Romance', 12.99, '2024-03-05', 7);

Step 2 — Answer these questions in SQL

  1. List the title and price of every book, most expensive first.
  2. Find all Sci-Fi books priced under $20.
  3. List every distinct genre in the table, alphabetically.
  4. Find books that are low on stock (fewer than 5 copies) but not sold out.
  5. Find every book whose title contains the word "the", case-insensitively.
💡 Hint

Q1 uses ORDER BY price DESC. Q2 combines two conditions with AND. Q3 is SELECT DISTINCT genre ... ORDER BY genre. Q4 needs copies_left > 0 AND copies_left < 5 (or BETWEEN 1 AND 4). Q5 uses LIKE with % on both sides — and LOWER() or ILIKE for case-insensitivity.

✅ Solution
-- 1
SELECT title, price FROM books ORDER BY price DESC;

-- 2
SELECT title, price FROM books
WHERE genre = 'Sci-Fi' AND price < 20;

-- 3
SELECT DISTINCT genre FROM books ORDER BY genre;

-- 4
SELECT title, copies_left FROM books
WHERE copies_left BETWEEN 1 AND 4;

-- 5  (PostgreSQL)
SELECT title FROM books WHERE title ILIKE '%the%';
-- Portable form:
SELECT title FROM books WHERE LOWER(title) LIKE '%the%';

Best Practices

✅ Do

  • Name your columns instead of SELECT * in application code.
  • Uppercase keywords and indent clauses so queries read like prose.
  • Always add ORDER BY when you use LIMIT, or the "top N" is arbitrary.
  • Use IS NULL for missing values — never = NULL.
  • Parenthesize any mix of AND and OR.

❌ Don't

  • Don't assume result order without ORDER BY — it can change between runs.
  • Don't use double quotes for string literals; that's for identifiers. Strings use single quotes.
  • Don't forget that LIKE '%term%' can't use a normal index and may scan the whole table on large datasets.

Summary & Quiz

🎉 Key Takeaways

  • SQL is a declarative language split into five groups; SELECT (DQL) is the one you'll use most.
  • A query is written SELECT → FROM → WHERE → ORDER BY → LIMIT, but evaluated FROM → WHERE → SELECT → ORDER BY → LIMIT.
  • WHERE filters rows; BETWEEN, IN, and LIKE make common filters concise.
  • NULL means "unknown" — test it with IS NULL, never =.
  • ORDER BY sorts, DISTINCT de-duplicates, and LIMIT/OFFSET pages results (syntax varies by engine).

🎯 Quick Quiz

Question 1: Which query correctly finds employees who have no manager assigned?

Question 2: Why should you pair LIMIT with an ORDER BY?

Question 3: In WHERE unit_price BETWEEN 10 AND 20, which prices match?

📚 Further Reading

🚀 What's Next?

You can now read data fluently. Next up is CRUD Operations in SQL, where you'll learn to create, update, and delete rows with INSERT, UPDATE, and DELETE — and how to avoid the classic mistake that wipes an entire table.