How does a multi-row INSERT ... VALUES (1,2),(3,4) differ from the same rows inserted by separate statements?
answer
- count the statements, not the rows
- one affected-row count comes back
- every row shares the same column list
- one bad row takes the others down with it
basics
~20 sA multi-row VALUES clause is one statement: every row shares the same column list, the whole statement reports one row count, and in standard SQL it either inserts all its rows or none. Separate statements succeed or fail independently.
solid answer
~40 s`INSERT INTO t (a, b) VALUES (1, 2), (3, 4)` uses the standard's table value constructor: the VALUES clause builds a two-row table and the statement inserts it. Because it is a single statement, every row must supply the same columns in the same order, the database reports one affected-row count, and a constraint violation in any row aborts the whole statement so none of its rows land — with separate statements the earlier ones would already have succeeded. Individual entries in a row can still be expressions or the `DEFAULT` keyword, so rows may differ in what they let the table fill in. It is also one statement to parse and one round trip rather than *n*, which is why it is the usual way to load a small known set of rows.
go deeper
Recall the syntax exactly: one column list, then comma-separated parenthesised rows, and a single semicolon. Be able to say it is one statement inserting several rows, not several statements.
Explain the consequences of it being one statement — a single affected-row count, one shared column list, and all-or-nothing behaviour when any row violates a constraint — rather than stopping at "fewer round trips".
Show that you design around the failure semantics: an atomic statement means a rejected batch tells you nothing about which row was bad, so you decide batch size, error reporting and retry granularity deliberately.
Be ready to set the house standard for literal loads: where multi-row VALUES stops being appropriate, what replaces it, and how statement-size and parameter limits shape the data-loading interfaces teams build.
## The construct ```sql INSERT INTO order_items (order_id, sku, qty) VALUES (101, 'A-1', 2), (101, 'B-7', 1), (102, 'A-1', 5); ``` Standard SQL calls the `VALUES` clause a *table value constructor*: it builds an anonymous table of literal rows, and `INSERT` inserts that table into the target. One row constructor per parenthesised group, commas between them. ## What is genuinely different from three statements **It is one statement.** That single fact produces every difference worth naming in an interview: - **One column list governs every row.** You cannot give row 1 three columns and row 2 four. The list is written once, before the `VALUES` keyword, and every row constructor must have the same number of values, positionally compatible with it. - **One row count comes back.** The client sees "3 rows affected", not three separate results. Anything in your code that inspects per-statement counts sees one number. - **All-or-nothing at statement level.** In standard SQL a data-change statement is atomic: if the third row violates a unique or NOT NULL constraint, the statement fails and *no* row from it is inserted, even though the first two were fine. Run the same three rows as three statements and rows 1 and 2 are in the table when row 3 fails — you are left with a partially applied load unless you wrapped them in a transaction yourself. Engines with non-transactional table types, or an explicit ignore-errors option, can deviate from this; check the engine you are on before relying on it. - **One parse, one round trip.** The statement is planned once instead of *n* times, and the client sends one message instead of *n*. This is why multi-row VALUES is the default shape for inserting a small known set of rows. ## What is *not* different The rows themselves are ordinary rows. Defaults, generated columns, constraints and triggers apply to each row exactly as they would from a single-row INSERT. The rows have no order once inserted — a table is not a sequence, and the order you wrote them in confers nothing on later queries. ## Per-row expressions and DEFAULT A value in a row constructor does not have to be a literal. Expressions and the `DEFAULT` keyword are allowed, and they can vary between rows: ```sql INSERT INTO audit_log (event, created_at) VALUES ('login', CURRENT_TIMESTAMP), ('logout', DEFAULT); ``` Here the second row asks the table for the column's declared default while the first supplies a value. That per-row flexibility is a real advantage over shapes that force one uniform template. ## Practical limits A multi-row VALUES list is bounded in practice: engines cap the number of placeholders or the statement size, and a single enormous statement is one long-running unit of work whose failure discards everything. For a handful to a few hundred rows it is the right tool; for bulk loading a large data set you would reach for a different mechanism, and for copying rows that already exist in the database you would use `INSERT ... SELECT` instead of shipping values out to the client and back. ## Portability note Multi-row `VALUES` in an `INSERT` is standard SQL and widely available, but it is not universal across every engine and version — some dialects historically required a different construct to insert several literal rows in one statement. If you are writing SQL that must run on more than one engine, verify the form there rather than assuming. ## The interview answer Say "it is one statement" first and then derive the consequences: one column list, one row count, all-or-nothing, one round trip. Candidates who only say "it is faster" have the least interesting half of the answer, and they miss the failure semantics that actually change how you write the code around it.
- Can different rows in one VALUES clause supply different columns?No. The column list is written once, before `VALUES`, and it governs every row constructor; each must supply the same number of positionally compatible values. What can differ per row is the *value*: any entry may be an expression, NULL, or the `DEFAULT` keyword, so one row can hand the column a value while another lets the table fill in its declared default.
- If the fourth row of a five-row INSERT violates a unique constraint, what is in the table afterwards?In standard SQL, nothing from that statement: the statement is atomic, so all five rows are rejected together and rows one to three are not inserted either. The same five rows as five separate statements would leave the first three in the table. Some engines with non-transactional tables or an explicit ignore option behave differently, so confirm on your engine.
- Is there a limit to how many rows one VALUES clause can carry?Not in the language, but there is in practice: engines cap statement size and the number of bind parameters, and one huge statement is a single long unit of work whose failure discards everything. A few hundred rows per statement is a common, comfortable shape; beyond that, split the load into several statements.
saying these in an interview costs you the question
- Says the only difference is that it is faster
- Thinks each row in the VALUES list can name its own columns
- Assumes a failing row leaves the earlier rows of the statement inserted
- Believes rows keep the order they were written in the table
- Claims a VALUES list can hold unlimited rows