Skip to content
Changing data

SQL INSERT, UPDATE, DELETE: Changing Data Safely

6 min readUpdated September 24, 2026Every example verified
On this page 8 sections ▾
  1. INSERT — adding rows
  2. UPDATE — changing rows that already exist
  3. DELETE — removing rows
  4. The habit that saves careers: SELECT first
  5. Where transactions fit
  6. DELETE vs TRUNCATE vs DROP
  7. Frequently asked questions
  8. Related reading

Everything on this site so far has read data. This page is about the other half of SQL: putting rows in, changing them, and taking them out. Three statements — INSERT, UPDATE, DELETE — and one habit that separates people who have lost production data from people who have not. The playground is the ideal place to practise these, because its database lives only in your browser tab: reload the page and the original data is back, no harm done.

INSERT — adding rows

INSERT INTO Products (product_id, name, category, unit_price, in_stock)
VALUES (209, 'Drift Mouse', 'Accessories', 45.50, 300);

Name the columns, then supply values in the same order. You can omit the column list and rely on the table's own column order — it is shorter, and it is also how INSERTs silently break the day someone adds a column to the table. Writing the list makes the statement self-documenting and future-proof, which is why it is the professional default.

Prove it worked with an ordinary SELECT. One thing to know about the playground: it starts from the original data every time it opens, so a statement that depends on an earlier one has to run in the same tab, after it. The blocks below therefore repeat the INSERT they rely on:

INSERT INTO Products (product_id, name, category, unit_price, in_stock)
VALUES (209, 'Drift Mouse', 'Accessories', 45.50, 300);

SELECT * FROM Products WHERE product_id = 209;
product_idnamecategoryunit_pricein_stock
209Drift MouseAccessories45.5300

● 1 row · produced by running this query on the sample database

UPDATE — changing rows that already exist

INSERT INTO Products (product_id, name, category, unit_price, in_stock)
VALUES (209, 'Drift Mouse', 'Accessories', 45.50, 300);

UPDATE Products
SET unit_price = 39.00
WHERE product_id = 209;

SELECT * FROM Products WHERE product_id = 209;
product_idnamecategoryunit_pricein_stock
209Drift MouseAccessories39300

● 1 row · produced by running this query on the sample database

SET says what changes; WHERE says which rows it happens to. The value can be computed from the row's own current values, which is how stock adjustments and price increases are really written:

UPDATE Products
SET in_stock = in_stock - 10
WHERE product_id = 209;

An UPDATE with no WHERE updates every row in the table. No confirmation, no warning — the database does exactly what you asked. This is not a rare mistake; it is the classic one.

Sometimes you do not know whether the row is there yet: a nightly price feed, say, that includes products you already sell and some you do not. A plain INSERT is refused as soon as it meets a product that already exists, and a plain UPDATE silently skips the new ones. An upsert does both in one statement. SQLite and PostgreSQL spell it ON CONFLICT ... DO UPDATE, where excluded means the row you tried to insert:

INSERT INTO Products (product_id, name, category, unit_price, in_stock)
VALUES (201, 'Aurora Laptop 14"', 'Hardware',    1249,  40),
       (209, 'Drift Mouse',       'Accessories', 45.50, 300)
ON CONFLICT (product_id) DO UPDATE
  SET unit_price = excluded.unit_price,
      in_stock   = excluded.in_stock;

SELECT * FROM Products WHERE product_id IN (201, 209) ORDER BY product_id;
product_idnamecategoryunit_pricein_stock
201Aurora Laptop 14"Hardware124940
209Drift MouseAccessories45.5300

● 2 rows · produced by running this query on the sample database

Product 201 already existed at 1299 with 42 in stock, so its price and stock were updated in place; 209 did not exist, so it was inserted. MySQL has its own spelling, and SQL Server uses MERGE, which the dialect differences guide shows in full:

-- MySQL
INSERT INTO Products (product_id, name, category, unit_price, in_stock)
VALUES (201, 'Aurora Laptop 14"', 'Hardware',    1249,  40),
       (209, 'Drift Mouse',       'Accessories', 45.50, 300)
ON DUPLICATE KEY UPDATE
  unit_price = VALUES(unit_price),
  in_stock   = VALUES(in_stock);

DELETE — removing rows

DELETE FROM Products
WHERE product_id = 209;

Same shape, same rule: the WHERE decides which rows die, and a DELETE without a WHERE empties the whole table. There is no undo in SQL itself — recovery means restoring a backup or, if the DELETE ran inside a transaction you opened first, rolling that transaction back, which the playground lets you try below.

SQLite and PostgreSQL can also hand back the rows a statement changed, if you add RETURNING. On a DELETE that is a record of exactly what was removed:

DELETE FROM Orders
WHERE status = 'Cancelled'
RETURNING order_id, status, order_total;
order_idstatusorder_total
5005Cancelled2842.5
5014Cancelled1050

● 2 rows · produced by running this query on the sample database

Both cancelled orders are listed as they were at the moment they were deleted. RETURNING works on INSERT and UPDATE too, where it is a common way to learn the id a new row was given. SQL Server’s version is the OUTPUT clause; MySQL has no equivalent.

The habit that saves careers: SELECT first

Before running an UPDATE or DELETE, run a SELECT with the exact same WHERE clause and look at what comes back:

SELECT product_id, name, unit_price
FROM Products
WHERE category = 'Accessories' AND unit_price < 200;

Those are the rows your UPDATE or DELETE will touch — count them, read them, and only then swap the SELECT line for the destructive statement. Thirty seconds of checking against a mistake that can take a day to undo. Every experienced database person does this, which tells you how they got to be experienced.

Try it yourself

The sample database here is the perfect crash-test dummy: INSERT, UPDATE and DELETE all you like, then reload the page and everything is back.

Open the Playground

Where transactions fit

Production systems wrap risky changes in a transaction: BEGIN, run the statements, then COMMIT to keep the changes or ROLLBACK to undo them all. Either everything lands or nothing does — that all-or-nothing guarantee is the A in the ACID properties databases advertise. SQLite, the engine the playground runs, supports transactions, so you can watch a DELETE being taken back:

BEGIN TRANSACTION;
DELETE FROM Products WHERE category = 'Accessories';
ROLLBACK;

SELECT name, category FROM Products WHERE category = 'Accessories';
namecategory
Nimbus Docking HubAccessories
Quill KeyboardAccessories

● 2 rows · produced by running this query on the sample database

Both accessories are still there: the DELETE removed them, and the ROLLBACK put them back. Replace ROLLBACK with COMMIT and run it again, and the query comes back empty. On data that matters the habit is to open the transaction, make the change, check it with a SELECT, and only then commit.

DELETE vs TRUNCATE vs DROP

Three levels of destruction, regularly confused in interviews: DELETE removes rows (some or all, depending on WHERE) and can be rolled back in a transaction. TRUNCATE removes every row at once, faster, typically without firing per-row triggers. DROP removes the table itself — data, structure, everything. If the question is "how do I remove some rows", the answer is always DELETE with a WHERE. SQLite, and so the playground, has no TRUNCATE at all: a DELETE with no WHERE is its equivalent.

Frequently asked questions

What happens if I run UPDATE without a WHERE clause?

Every row in the table is updated, immediately and without confirmation. Outside a transaction there is no undo. This is the most common serious SQL mistake, and the SELECT-first habit exists specifically to prevent it.

How do I undo a DELETE in SQL?

Inside a transaction, ROLLBACK undoes it. After a COMMIT - or where no transaction was used - SQL itself has no undo; recovery means restoring from a backup. Which is why you SELECT with the same WHERE first.

What is the difference between DELETE and TRUNCATE?

DELETE removes rows matched by its WHERE clause (or all rows without one) and can be rolled back in a transaction. TRUNCATE removes all rows at once, is faster, and cannot be filtered. To remove specific rows, DELETE is the only choice. SQLite has no TRUNCATE; there, DELETE with no WHERE clause is the equivalent.

Should I list the columns in an INSERT statement?

Yes. INSERT INTO t (a, b, c) VALUES (...) keeps working when someone adds a column to the table later, and documents which value lands where. Omitting the list couples your statement to the table's current column order.

Can I use expressions in an UPDATE?

Yes - SET in_stock = in_stock - 10 computes the new value from the current one, per row. Any expression that works in a SELECT list works on the right-hand side of SET.

Is it safe to practise INSERT, UPDATE and DELETE here?

Completely. The sample database lives in your browser tab and nowhere else. Change anything, delete everything - reloading the page rebuilds the original data from scratch.

About the author

SQL Practice

SQL Practice publishes free SQL tutorials, an in-browser playground and 42 practice exercises, built so you can run what you read. Every runnable example in our tutorials is executed against the site’s sample database before it is published. Read how we keep tutorials accurate, or report a mistake.