skip to content

Data Modification (DML)

The statements that change rows: INSERT, UPDATE, DELETE, TRUNCATE and MERGE, plus getting modified rows back. This is the half of SQL that backend interviews drill hardest, because mistakes here corrupt data instead of just returning a wrong result.

part ofSQLoverview, primer and where to startread it →
on this pageshow

questions

30

What does DELETE FROM orders WHERE status = 'DRAFT' remove, and what happens if you omit WHERE?

level: juniorimportance: must knowfreq 80%

answer

  1. Rows, never columns
  2. WHERE is optional here
  3. No column list in the syntax
  4. Missing WHERE means the whole table
  5. Clearing one field is UPDATE ... SET NULL

basics

~10 s

It removes every whole row of orders whose status is 'DRAFT'; the table and all other rows stay. With no WHERE clause, DELETE FROM orders removes every row in the table.

solid answer

~50 s

`DELETE` operates on **whole rows of exactly one table**. The `WHERE` search condition is a row filter: each row of `orders` is tested, and only rows for which the condition is TRUE are removed, so `status = 'DRAFT'` removes the draft orders and leaves everything else untouched. Drop the `WHERE` clause and the filter is gone — `DELETE FROM orders` removes every row, one row at a time, and the statement is still ordinary DML, so it can be rolled back if it runs inside an open transaction that you have not committed. What `DELETE` never does is remove *part* of a row: there is no column list, so `DELETE status FROM orders` is not valid SQL. Clearing one column is `UPDATE orders SET status = NULL`. And the table itself, with its columns, indexes and constraints, survives a `DELETE` — removing the table is `DROP TABLE`.

code

sql · 8 lines
sql
-- preview first: identical FROM/WHERE, different head
SELECT COUNT(*) FROM orders WHERE status = 'DRAFT';

-- then the destructive statement
DELETE FROM orders WHERE status = 'DRAFT';

-- no WHERE clause: removes every row, table stays
DELETE FROM orders;

go deeper

for a junior

Be ready to write a correct DELETE with a WHERE clause on the spot and to say plainly that DELETE removes whole rows, never one column, and never the table itself.

for a middle

Explain that WHERE is an ordinary search condition evaluated per row, that omitting it is legal and removes everything, and that the statement reports an affected-row count you should check.

for a senior

Show the habit as well as the syntax: preview with the twin SELECT, run destructive statements inside an explicit transaction, verify the row count, and only then commit.

for a principal

Own the guard rails around destructive DML in a team: who may run ad-hoc deletes on production, whether such statements go through reviewed migrations, and what the recovery path is when someone commits an unqualified DELETE.

## What a DELETE statement actually does `DELETE` is the DML statement that removes rows. Its standard form is: ```sql DELETE FROM orders WHERE status = 'DRAFT'; ``` The unit of deletion is the **row**, and the target is **exactly one table**. There is no column list anywhere in the syntax, because "delete half a row" is not a thing a relational table can represent — a row either exists or it does not. Beginners frequently try `DELETE status FROM orders` or `DELETE * FROM orders`, borrowing the shape of `SELECT *`; both are syntax errors. The standard also requires the `FROM` keyword, though some dialects (T-SQL, for example) accept `DELETE orders WHERE ...` without it — writing `DELETE FROM` always works and always reads unambiguously. ## WHERE is a row filter, not a safety feature The `WHERE` clause of a `DELETE` uses the same search-condition grammar as a `SELECT`: comparisons, `AND`/`OR`/`NOT`, `IN`, `BETWEEN`, `LIKE`, `EXISTS`, subqueries. Each row of the target table is evaluated against it, and a row is removed only when the condition evaluates to TRUE. That is why the safest way to understand a `DELETE` you are about to run is to run its twin `SELECT` first — keep the `FROM` and `WHERE` byte-for-byte identical and only swap the head of the statement: ```sql SELECT COUNT(*) FROM orders WHERE status = 'DRAFT'; ``` Whatever count that returns is exactly the number of rows the `DELETE` will remove. ## Omitting WHERE `WHERE` is optional. `DELETE FROM orders;` is legal SQL and removes **every** row of `orders`. The engine does not warn you, does not require a confirmation, and does not treat it as a special statement — it is the same statement with the filter set to "all rows". Interactive clients sometimes add their own guard rails (a "safe update mode", a required key predicate), but those are client or session features, not SQL semantics, and you should never rely on one being switched on. An unqualified `DELETE` is still DML: it removes rows one by one, fires whatever row triggers the table has, respects constraints, and participates in the surrounding transaction. If you are inside an explicit transaction that has not committed, `ROLLBACK` puts the rows back. If your session is in autocommit mode — the default in most client tools and drivers — the statement commits the moment it succeeds and there is nothing to roll back. ## DELETE removes rows; other statements remove other things It helps to hold three statements apart: - `DELETE FROM orders WHERE ...` — removes rows, keeps the table. - `UPDATE orders SET note = NULL WHERE ...` — keeps the rows, clears a column value. - `DROP TABLE orders` — removes the table definition itself along with its data, indexes, constraints and privileges. So "delete the customer's phone number" is an `UPDATE`, not a `DELETE`, and "delete the orders table" is a `DROP`. ## How many rows did it remove? A `DELETE` reports the number of rows it affected. SQL's diagnostics area exposes it as `ROW_COUNT` (retrieved with `GET DIAGNOSTICS`), and every client API surfaces the same number as an "update count" — JDBC's `executeUpdate` return value, for instance. A count of zero is not an error: a `DELETE` that matches nothing succeeds quietly. When you expect a statement to remove exactly one row and it reports zero, that is a signal to check the predicate rather than assume success. Conversely, a count much larger than expected is the moment to `ROLLBACK` if you were prudent enough to open a transaction first. ## Practical habits Write the `SELECT` first and convert it to a `DELETE`. Prefer deleting by primary key when you mean one row. Open an explicit transaction for anything destructive on a production table, inspect the affected-row count, and only then `COMMIT`. And read your own predicate out loud before running it — the difference between `WHERE status = 'DRAFT'` and `WHERE status <> 'DRAFT'` is one character and the entire table.

  • Does DELETE FROM orders remove the table's columns, indexes or constraints?
    No. `DELETE` only removes rows; the table definition, its columns, indexes, constraints, triggers and privileges all remain, and the table is simply left empty. Removing the object itself is `DROP TABLE orders`, which is DDL and takes no `WHERE` clause.
  • How do you find out how many rows a DELETE actually removed?
    The statement reports an affected-row count: SQL exposes it as `ROW_COUNT` in the diagnostics area, and client APIs return it as an update count (for example JDBC's `executeUpdate`). Zero is a valid, successful result — it just means nothing matched the predicate.
  • Is a DELETE reversible?
    Only through the transaction. Inside an explicit transaction, `ROLLBACK` undoes the removal completely. Under autocommit — the default in most drivers and consoles — the statement commits immediately and the rows are gone as far as SQL is concerned; recovery then becomes a backup or point-in-time-restore question, not a SQL one.

DELETE is the shredder for whole pages in a filing cabinet: it can throw away any page you point at, but it cannot erase one line from a page, and it never removes the cabinet.

saying these in an interview costs you the question

  • Thinks DELETE can remove a single column's value
  • Writes DELETE * FROM orders, copying SELECT syntax
  • Claims the engine rejects a DELETE with no WHERE
  • Says DELETE removes the table like DROP TABLE does
  • Assumes a DELETE can never be rolled back

context

open as a page

Why should an INSERT statement name its target columns instead of relying on column order?

level: juniorimportance: must knowfreq 82%

basics

~20 s

An INSERT without a column list binds values to columns by position, so adding, dropping or reordering a column silently shifts every value. Naming the columns pins each value to its column and lets omitted columns take their declared defaults.

open as a page

How does MERGE INTO ... USING ... ON perform an upsert in a single statement?

level: juniorimportance: must knowfreq 60%

basics

~20 s

MERGE joins a target table to a source rowset through an ON condition, then applies one action per source row: WHEN MATCHED THEN UPDATE for keys that already exist in the target, WHEN NOT MATCHED THEN INSERT for the rest.

open as a page

What does adding RETURNING order_id to an INSERT statement produce?

level: juniorimportance: must knowfreq 55%

basics

~10 s

RETURNING makes the INSERT also yield a result set: one row per inserted row, containing the listed columns with their final stored values — generated identity keys, applied defaults and computed columns included.

open as a page

What is the difference between TRUNCATE TABLE and DELETE FROM with no WHERE clause?

level: juniorimportance: must knowfreq 88%

basics

~20 s

TRUNCATE TABLE empties a table in one bulk operation: no WHERE clause, no per-row DELETE triggers, usually an identity reset, and far less logging. DELETE removes rows one at a time as ordinary DML and can be filtered.

open as a page

How do you set several columns in one UPDATE, and why does SET a = 1 AND b = 2 fail?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Use one SET keyword with comma-separated assignments: SET price = 10, qty = 0. AND is a boolean operator, not a separator, so SET price = 10 AND qty = 0 assigns price the result of an expression and never touches qty.

open as a page

How do you delete duplicate rows from a table while keeping one row per duplicate group?

level: middleimportance: must knowfreq 70%

basics

~20 s

Define which columns make rows duplicates and which row survives, then delete rows that have a surviving twin: DELETE FROM contacts c WHERE EXISTS (SELECT 1 FROM contacts k WHERE k.email = c.email AND k.id < c.id) keeps the lowest id per email.

open as a page

How does INSERT ... SELECT match the query's result columns to the target table's columns?

level: middleimportance: must knowfreq 68%

basics

~20 s

Strictly by position: the first select-list expression fills the first column of the INSERT's column list, and so on. Names and aliases are ignored, so two type-compatible columns in the wrong order are inserted swapped without any error.

open as a page

What happens when a MERGE source contains two rows matching the same target row?

level: middleimportance: must knowfreq 50%

basics

~20 s

It is a cardinality violation: the standard forbids acting on the same target row twice in one MERGE, so the statement fails with an error instead of silently letting the last source row win. Collapse the source to one row per key first.

open as a page

Why does an UPDATE whose SET reads a correlated subquery set unmatched rows to NULL?

level: seniorimportance: must knowfreq 62%

basics

~20 s

A subquery in SET returns NULL when it finds no matching source row, and the WHERE clause of the UPDATE decides which rows are touched, not whether a match exists. Every row without a match is therefore overwritten with NULL.

open as a page

How does a multi-row INSERT ... VALUES (1,2),(3,4) differ from the same rows inserted by separate statements?

level: juniorimportance: should knowfreq 60%

basics

~20 s

A 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.

open as a page

How do you write a DELETE whose target rows are chosen by a match in another table?

level: middleimportance: should knowfreq 62%

basics

~20 s

Keep one target table in DELETE FROM and put the other table inside a correlated EXISTS or an IN subquery in the WHERE clause. Only the target table's rows are removed; the second table is read, never modified.

open as a page

Why does deleting a parent row fail with a foreign-key error, and in what order must you delete?

level: middleimportance: should knowfreq 65%

basics

~20 s

A foreign key requires every child row to reference an existing parent, so removing a still-referenced parent would break that promise and the statement is rejected. Delete from the leaves inward: children first, parents last.

open as a page

What value lands in a column that an INSERT's column list omits?

level: middleimportance: should knowfreq 55%

basics

~20 s

An omitted column takes its declared DEFAULT; with no default it takes NULL if nullable, and the statement fails if it is NOT NULL with no default. Writing NULL explicitly is different — it overrides the default.

open as a page

How does a MERGE evaluate multiple WHEN MATCHED arms carrying AND conditions?

level: middleimportance: should knowfreq 30%

basics

~20 s

Arms are tested in the order written, and the first one whose condition holds fires — at most one action per row. That ordering lets a single MERGE delete tombstoned rows, update changed ones, and leave unchanged rows alone.

open as a page

How does ANSI MERGE differ from a vendor upsert that keys on a unique-constraint conflict?

level: middleimportance: should knowfreq 40%

basics

~20 s

MERGE decides matched-versus-new with an arbitrary ON predicate against a source rowset and can update, insert or delete. Conflict-style upserts hang off an INSERT, trigger only when a unique or primary key collides, and cannot delete.

open as a page

For a multi-row UPDATE ... RETURNING, which rows come back and in what order?

level: middleimportance: should knowfreq 40%

basics

~20 s

One result row comes back per row the statement actually modified — none if the WHERE matched nothing — and the order is unspecified. RETURNING takes no ORDER BY of its own; wrap the statement and sort outside it.

open as a page

In UPDATE ... RETURNING, are the returned values the pre-update or post-update ones?

level: middleimportance: should knowfreq 35%

basics

~20 s

Post-update. RETURNING reports the row as finally stored, including defaults, generated columns and trigger effects. Engines with an OUTPUT-style clause also expose the prior image through a deleted pseudo-table; plain RETURNING gives only the new one.

open as a page

What does TRUNCATE TABLE do to a table's identity or sequence counter?

level: middleimportance: should knowfreq 52%

basics

~10 s

TRUNCATE TABLE can restart the counter, unlike DELETE which never touches it. Standard SQL spells the choice as TRUNCATE TABLE t RESTART IDENTITY or CONTINUE IDENTITY, and engines differ on which is the default.

open as a page

Can TRUNCATE TABLE be rolled back if it runs inside an open transaction?

level: middleimportance: should knowfreq 60%

basics

~10 s

It depends on the engine. PostgreSQL and SQL Server treat TRUNCATE as transactional, so ROLLBACK restores the rows. MySQL and Oracle commit implicitly when TRUNCATE runs, so there is nothing left to roll back.

open as a page

In UPDATE employees SET salary = bonus, bonus = salary, which values do the right-hand sides read?

level: middleimportance: should knowfreq 52%

basics

~20 s

Standard SQL evaluates every right-hand side against the row as it was before the statement, so the two columns swap. Assignments are simultaneous, not sequential. Some engines instead evaluate assignments left to right, so this form is not portable.

open as a page

How do you make sure an UPDATE's WHERE clause targets exactly the rows you intend?

level: middleimportance: should knowfreq 54%

basics

~20 s

Run the predicate as a SELECT with the same FROM and WHERE first, count and inspect the rows, then paste the identical predicate into the UPDATE and reconcile the affected-row count against that expected number.

open as a page

A DELETE with an IN-subquery emptied the whole table instead of a few rows — what went wrong?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Almost always an unqualified column inside the subquery that does not exist in the subquery's table: name resolution reaches outward and binds it to the table being deleted, so the predicate compares each row with itself and is true for every row.

open as a page

Why does putting a filter predicate in a MERGE ON clause cause unwanted INSERTs?

level: seniorimportance: should knowfreq 33%

basics

~20 s

ON only classifies source rows as matched or not matched. An existing target row excluded by an extra predicate in ON is reported as not matched, so the insert arm adds a second row for the same key — a duplicate or a unique-constraint failure.

open as a page

How can DELETE ... RETURNING move rows into an archive table in one statement?

level: seniorimportance: should knowfreq 30%

basics

~20 s

DELETE ... RETURNING emits the removed rows as a result set, so a single statement can feed them straight into an INSERT — via a data-modifying CTE in PostgreSQL, or DELETE ... OUTPUT deleted.* INTO archive in T-SQL — with no intervening SELECT.

open as a page

Your audit trigger stopped logging deletions after a job switched from DELETE to TRUNCATE — why?

level: seniorimportance: should knowfreq 35%

basics

~20 s

TRUNCATE removes rows in bulk instead of one at a time, so row-level DELETE triggers never fire and the audit log records nothing. Only an explicit statement-level TRUNCATE trigger, where the engine offers one, sees the event.

open as a page

Why does TRUNCATE TABLE fail on a table referenced by another table's foreign key?

level: middleimportance: nice to knowfreq 38%

basics

~20 s

TRUNCATE removes every row at once instead of row by row, so it cannot run the per-row referential actions a foreign key requires. Engines refuse it rather than leave orphaned children, so you truncate the child first or use CASCADE.

open as a page

What does INSERT INTO events (id, kind) SELECT id + 1000, kind FROM events do to the table it reads?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

It duplicates the table exactly once. The source query is defined to see the table as it was when the statement started, so the rows being inserted are not re-read; three rows become six, not an endless loop.

open as a page

Which equivalents of RETURNING exist in engines that lack the clause?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

RETURNING is not core ISO SQL. PostgreSQL and SQLite spell it RETURNING, SQL Server uses OUTPUT with inserted and deleted pseudo-tables, Oracle uses RETURNING ... INTO bind variables, and MySQL has none — LAST_INSERT_ID() is its fallback.

open as a page

What does the row-assignment form UPDATE t SET (a, b) = (SELECT x, y ...) give you?

level: seniorimportance: nice to knowfreq 32%

basics

~20 s

Row assignment sets several columns from a single subquery: one lookup supplies all the targets, instead of repeating the same correlated subquery once per column. Engine support varies, so confirm it before relying on it.

open as a page