Vocabulary for this course

The terms the chapters assume, in plain words. You can open this from any chapter.

alias
A short second name given to a table or column in a query, so the rest of the query can refer to it briefly.
Cartesian product
Every row of one table paired with every row of another, which happens when a join has no matching rule.
column
One named field in a table, with the same kind of value in every row, such as a title or a year.
constraint
A rule a table enforces on its data, such as requiring a value or making one column unique.
distinct
A word in a query that removes repeated rows from the result, so each one appears once.
foreign key
A column that refers to the primary key of another table, so each row can be tied to one row there.
index
A stored lookup that helps the database find rows quickly without reading the whole table.
inner join
The default way of combining two tables: only pairs of rows present on both sides appear in the result.
join
A query operation that combines rows from two tables by matching values they share, usually a shared identifier.
junction table
A bridge between two tables that lets a row on either side pair with many on the other; each of its rows holds one pair.
left join
A combination that keeps every row of the first table named, filling in nulls where the second has no match.
many-to-many
A relationship where one row on each side can pair with many rows on the other, usually held in a junction table.
null
A marker for a value that is missing or unknown, which is not the same as zero or an empty string.
outer join
A join that keeps rows from one or both tables even when no row on the other side matches.
primary key
The column or columns that together identify each row uniquely, and which can never be missing.
query
A request written in SQL that asks the database for an answer, usually a table of rows.
row
One record in a table, with one value for each column that table defines.
schema
The stored plan of a database: its tables, the columns in each, and the rules those columns must follow.
table
A named set of rows that all share the same columns, such as one holding every book in a library.

SQL from the ground up

Choosing columns, filtering rows

The two halves of every query: which columns come back, and which rows make the cut — comparisons, AND, OR, and the parentheses that keep them honest.

Part 2 of 12

Beginner15 min readBack to Courses

Name the columns

SELECT takes a list, not just one column: SELECT title, year FROM books; comes back with those two, in that order, however the table happens to be laid out. Naming what you need keeps the answer readable and keeps the query from breaking when the table grows a column.

A column can be renamed on the way out with AS: SELECT title AS book, year AS published FROM books;. The name is only for the result — nothing in the table changes.

WHERE keeps the rows that match

The WHERE clause runs a test on every row and keeps the ones that pass. The tests are the comparisons you would expect: =, <> for not-equal, <, >, <=, >=. Text goes in single quotes; numbers stand bare.

Conditions combine with AND, OR and NOT. They are not equal partners — AND binds tighter than OR — so when the intent is "either of these, and also that", write the parentheses: WHERE (year < 1950 OR year > 2000) AND author = 'Ursula K. Le Guin'.

The test is applied to one row at a time and knows nothing about the other rows. That is why WHERE cannot ask about a total or a count: those exist only after the rows have been folded together, which is what HAVING is for later in the course.

The order the database thinks in

SQL is written SELECT ... FROM ... WHERE ..., but it is evaluated the other way round: the database first finds the table, then tests each row with WHERE, and only then builds the columns the SELECT names. That is why a WHERE clause cannot normally use an alias defined in the SELECT list — the alias does not exist yet at that point.

It also explains why filtering is the cheap place to be precise. A row rejected by WHERE never reaches the sorting, grouping or output stages, so a good filter does less work than a good SELECT list.

Comparing values of different types

SQLite is relaxed about types: a column declared INTEGER can hold text, and year = '1965' may match because the comparison converts one side. That convenience hides two problems. The query may succeed here and fail on a stricter database, and a conversion on the column side — WHERE CAST(year AS TEXT) = '1965' — throws away any index built on year, because the database has to compute the expression for every row before it can compare anything.

Compare a column with a value of its own type, and let the literal do the converting rather than the column. When the stored data really is mixed, typeof(year) will show you which rows are the odd ones.

A filter is only as good as its shape

An index is a sorted structure keyed on a column, and a condition can use it only when the column appears bare on one side of the comparison. WHERE year > 1960 can seek straight to the right place. WHERE year + 10 > 1970 cannot, because the database would have to compute a new value for every row first.

The same is true of WHERE upper(author) = 'DUNE', of a pattern that begins with %, and of an OR that names two different columns. None of these is wrong; they are simply full scans. On a hundred rows that is invisible, and on a million it is the difference between a page that loads and one that does not.

NULL is not a value you can compare with =. NULL = NULL is unknown, not true, so a column that may be empty needs IS NULL or IS NOT NULL. The same three-valued logic makes WHERE year <> 1965 silently drop rows whose year is NULL.

One filter, two conditions

SELECT title, year
FROM books
WHERE year > 1960
  AND year < 1980;

SELECT title, author
FROM books
WHERE author = 'Octavia E. Butler'
   OR author = 'Frank Herbert';

Patterns, lists and case

LIKE matches a pattern: % stands for any run of characters and _ for exactly one. title LIKE 'The %' matches every title that begins with "The ". IN is a set of alternatives, and BETWEEN is an inclusive range — both are shorthand for a chain of OR/comparisons.

CASE turns a condition into a value, so it can sit in a SELECT list or an ORDER BY. It is an expression, not a control-flow statement: every branch has to answer a value of the same kind.

Try these next

  • Find every title that contains the word "the" anywhere, using LIKE '%the%'.
  • Use IN to match three authors and compare it with the same query written with OR.
  • Add a CASE column that labels books before 1970 as "classic" and the rest as "modern".

Filter the shelves

Ask for the title and year of every book published from 1970 onwards, and nothing else.

Live Preview

Between, IN, and the empty year

Ask for books published between 1960 and 1980 inclusive, then ask which books have no year recorded. Read the two answers separately.

Live Preview

Text is case-sensitive in a plain comparison, so = 'Dune' will not match 'DUNE'. The next chapter has LIKE, which compares case-insensitively for ASCII and can match a pattern rather than an exact word.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does SELECT title AS book FROM books; change?