Topic 21

INSERT

SQL

A regular walks up to the Marquee counter. Priya has been coming to the Thursday shows for months, and tonight she finally joins the mailing list — which means her name and email have to become a permanent fact in the customers table. Every query in the last two chapters read data and left it exactly as it found it. This one changes the database. The verb for creating a row is INSERT.

INSERT is the simplest of the three writing verbs, and it carries the chapter's big idea in miniature: you do not just put data in, you submit it. The database checks every new row against the rules the table declares — the types from Chapter 2, the keys, the constraints — and only a row that passes all of them gets in. A refused row is not a malfunction. It is the entire point.

The Full Form

Here is Priya's row being born. Read the statement in three moves: INSERT INTO customers names the table. The list in parentheses names exactly which columns you are supplying — name and email, nothing else. And VALUES provides one value for each named column, in the same order you named them.

One new row for the customers table
INSERT INTO customers (name, email)
VALUES ('Priya N', 'priya@example.com');

The pairing between columns and values is positional against your list: the first value goes to the first column you named, the second to the second. The order the columns sit in inside the table is irrelevant. Name them in any order you like, as long as VALUES follows the same order — the database matches your list to your values, not the table's layout to your values.

Two columns are conspicuously missing. Nobody supplied customer_id, and nobody supplied joined_on. That is deliberate. Chapter 2 set the id column up so the engine mints a fresh, never-repeated number for every new row, and joined_on carries a declared default of today's date. Leave those columns out of your list and the database fills them in. Supply your own id and you are volunteering for exactly the collision problems the engine exists to prevent — two rows fighting over the same number, which the engine, minting alone, can never produce.

What Gets Checked at the Door

Think of a club with a door policy: the bouncer checks every guest once, at the door, and because of that nobody inside ever needs checking again. Constraints work the same way. Every rule the table declares is checked at the moment a row tries to enter, so everything already inside the table is known to be clean. That is the whole mapping — from here on we will say constraints, not bouncers, though the image will make one deliberate return when constraints get their own page in Chapter 6.

The checks are the ones you already met when the tables were built. Does each value fit its column's type — is the price a number, is the timestamp a real date? Is the primary key value unique? Does every foreign key point at a row that actually exists? Is every required, NOT NULL column present? One failure of any check and the whole row is refused.

Watch a refusal happen. The customers table declares email UNIQUE — Chapter 2's guarantee that no two customers share an address. A week later, a volunteer who does not know Priya already signed up types her in again:

The mistake — the same email a second time, which UNIQUE refuses
INSERT INTO customers (name, email)
VALUES ('Priya', 'priya@example.com');

The database answers with an error instead of a row. In PostgreSQL it reads: duplicate key value violates unique constraint "customers_email_key". Read that aloud and it says exactly what happened: a value that must be unique arrived twice, and the rule with that name refused it. Database errors look alarming and are usually this literal — naming the table, the rule, and the offense.

Here is the part that matters most, because it sets up Chapter 7: nothing half-happened. The refused row did not partially arrive; no name-without-email sits in the table. After the error, the customers table is exactly what it was before the attempt. A refused INSERT leaves no trace — and transactions, later in the book, extend that all-or-nothing promise from single rows to whole groups of changes.

Every new row is auditioned once, at the door
A new row arrivesname + email submitted
The checks runtypes · keys · NOT NULL
Admittedid minted, defaults filled
Or refused wholeerror named, table unchanged

Several Rows at Once

Rows often arrive in small batches — tonight's screenings go on sale together, not one by one. INSERT handles that without ceremony: after VALUES, each parenthesized group is one row, and commas separate the rows.

Three rows in one statement — tonight's screenings
INSERT INTO screenings (film_id, screen, starts_at, price)
VALUES (1, 1, '2026-07-10 18:00', 9.50),
       (2, 2, '2026-07-10 18:30', 9.50),
       (3, 1, '2026-07-10 21:00', 11.00);

Film 1 is The Long Rain, film 2 is Paper Lanterns, film 3 is Night Bus — the foreign key column carries ids, and the films table holds the titles, exactly as Chapter 2 arranged. Every row in the batch is checked individually against the same rules as a lone row; a film_id pointing at a film that does not exist would be refused just as firmly here.

Where Rows Really Come From

One honest note before the verb feels too manual. In production systems, almost no INSERT is typed by a human. When the Marquee's website gets its signup form in Chapter 9, the form's submit button will fire an INSERT shaped exactly like Priya's — written once by a developer, run thousands of times by customers. People type INSERTs for setup, repairs, and small one-off tasks; software types the rest. The statement is identical either way, which is why learning it by hand is learning the real thing.

Common Confusions
  • "If the INSERT fails, something half-happened." Nothing happened. A refused row leaves the table exactly as it was — no partial row, no leftover values. Chapter 7's transactions extend this all-or-nothing promise to groups of changes.
  • "I should supply the id myself." Let the engine mint ids. It hands out a fresh number per row and never repeats one; supplying your own invites exactly the collisions it exists to prevent.
  • "My column list must match the table's column order." No — you name the columns, in any order, and VALUES pairs positionally with your list. The table's own layout does not enter into it.
  • "Every column must appear in the INSERT." Only the required ones. Columns you leave out take their declared default (like joined_on) or NULL where the table allows it — and a missing NOT NULL column is simply refused.
Why It Matters
  • Every signup, sale, and booking in the world is an INSERT. Recognizing the statement — and reading its error messages calmly — is practical literacy you will use in the first week of any data job.
  • "The rules audition every row at the door" is the reason databases stay trustworthy for years while shared spreadsheets rot in months — Chapter 1's thesis, now with teeth.

Knowledge Check

An INSERT is refused because the email already exists in the table. What is in the customers table afterwards?

  • A partial row holding the name but not the email
  • Exactly what was there before the attempt
  • The new row, stored but flagged as a duplicate
  • The old row, overwritten with the new name

Who should normally supply the value for customer_id when a new customer row is inserted?

  • Whoever types the INSERT, counting up by hand from the last row
  • Nobody — the engine mints it when the column is left out
  • The customer, when they fill in the signup form
  • It stays empty until an administrator assigns one later

In INSERT INTO customers (email, name) VALUES (...), what does the first value pair with?

  • The first column of the table as it was created
  • email — because your column list names it first
  • Whichever column the value looks like it belongs to
  • customer_id, since ids always come first

Which of these is one of the checks a new row must pass before it is admitted?

  • The name is spelled correctly when checked against a dictionary
  • A database administrator approves the row by hand
  • Every foreign key points at a row that actually exists
  • The table still has free space for one more row

You got correct