SQL: Null
Welcome to the Null lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
Like most programming languages, SQL supports "null", which indicates the absence of a value. For example, a user's
login_countmight be null if they've never logged in.When a column is allowed to be null, we say that it's "nullable". In SQLite and most other database systems, all columns are nullable unless we specify otherwise. Every column that we've seen up to this point has been nullable!
Our queries return JavaScript data, so null is represented as the standard JavaScript
null.>
exec(`CREATE TABLE users (name TEXT, login_count INTEGER)`);exec(`INSERT INTO users (name, login_count) VALUES (NULL, NULL)`);exec(`SELECT * FROM users`);Result:
This isn't a quirk specific to SQLite. Most database systems make columns nullable by default, including PostgreSQL and MySQL.
For the rest of this lesson, we'll explicitly mark columns as either
NULLorNOT NULL. A column likelogin_count INTEGER NULLmeans that the column is nullable. That's the default, so theNULLisn't strictly required, but it's nice to make it explicit.We can tell the database to disallow null values by adding
NOT NULLto a column declaration. Trying to insert a null value into aNOT NULLcolumn is an error. You can typeerrorat these prompts if you think that a query will produce an error.>
exec(`CREATE TABLE users (name TEXT NOT NULL, login_count INTEGER NULL)`);exec(`INSERT INTO users (name, login_count) VALUES ('Amir', NULL)`);exec(`SELECT * FROM users`);Result:
[{name: 'Amir', login_count: null}]>
exec(`CREATE TABLE users (name TEXT NOT NULL, login_count INTEGER NULL)`);exec(`INSERT INTO users (name, login_count) VALUES (NULL, NULL)`);exec(`SELECT * FROM users`);Result:
Error: NOT NULL constraint failed: users.name
Like many things in SQL,
NOT NULLis more important than it might seem at first. Let's imagine a production bug that shows why. (This story is fictional, but stories like it have happened in many real systems.)We have an
orderstable with aphone_numbercolumn. We accidentally introduce a bug where we sometimes insert aNULLphone_number.Here's a code problem:
Write code that inserts a
NULLphone_number.exec(`CREATE TABLE orders (phone_number TEXT NULL)`);exec(`INSERT INTO orders (phone_number) VALUES (NULL)`);exec(`SELECT * FROM orders`);- Goal:
[{phone_number: null}]- Yours:
[{phone_number: null}]
We fix the bug after 12 hours. The system stops inserting nulls. Everything is fine again, right?
Unfortunately, everything is not fine! We'd like to change the column to be
NOT NULLto prevent this bug from recurring in the future. But the production database now contains some rows wherephone_numberisNULL. SQL databases won't let us change a column to beNOT NULLif it already contains someNULLs.We have a couple of relatively simple options for trying to clean this up once it's happened:
First, we can leave our
phone_numbercolumn nullable forever. But that means that we can make this mistake again, so it's not a good solution.Second, we can make up fake phone numbers and put those in the
phone_numbercolumns for the affected orders. Then there are no nulls left, so we can change the column to beNOT NULL. But now some of our customer data is intentionally made-up and therefore incorrect, so it's not a good solution.There are other, more creative solutions that we could use, but they involve permanently complicating the database schema. Once we have unexpected
NULLs in the database, there's no good way to fix them.The best solution is to make the bug impossible in the first place. If we'd made
phone_numbercolumnNOT NULLthen none of this would've happened. Instead, the database would've errored at runtime when we tried to insert theNULL. We may have lost a few sales due to those errors, but we wouldn't be saddled with invalid database data!The takeaway here is: it's best to make columns
NOT NULLunless you have a very good reason not to. When you do make a column nullable, it's best to explicitly mark it asNULLby defining it asphone_number TEXT NULL. That way, anyone who reads your schema later can clearly see which columns are intentionally nullable and non-nullable.Here's a code problem:
Create a table "cats" with a
TEXT NOT NULLname and anINTEGER NULLage.exec(`CREATE TABLE cats (name TEXT NOT NULL, age INTEGER NULL)`);exec(`INSERT INTO cats (name, age) VALUES (NULL, 3)`);- Goal:
Error: NOT NULL constraint failed: cats.name
- Yours:
Error: NOT NULL constraint failed: cats.name