Most SQL jobs are not glamorous architecture. They are four verbs: read rows, add rows, change rows, remove rows. People get fancy with windows and warehouses later. The ones who sleep well still treat those four verbs with respect, especially the three that write.
This is Part 2 of the SQL series on Analytics Made Simple. You should have a practice database from Part 1 with customers and orders. Today we walk SELECT, INSERT, UPDATE, and DELETE on that toy shop, with safety habits you will reuse forever. Series hub: SQL series.
What you will learn
- What each core command does in plain English
- How SELECT is your default tool and your safety net
- How INSERT adds rows without inventing fake “full dumps”
- How UPDATE changes existing values, carefully
- How DELETE removes rows, carefully
- The non-negotiable rule: never UPDATE or DELETE without a WHERE (unless you truly mean every row)
- Preview-then-write workflows you can defend in a code review
CRUD without the buzzword fog
People summarize these verbs as CRUD: Create, Read, Update, Delete. In SQL that maps roughly to INSERT, SELECT, UPDATE, DELETE. The acronym is fine. The habit is better: know which statements only read, and which change durable state.

| Command | Intent | Touches stored data? |
|---|---|---|
SELECT | Read and shape a result | No (read-only) |
INSERT | Add new rows | Yes |
UPDATE | Change values in existing rows | Yes |
DELETE | Remove existing rows | Yes |
Analysts live in SELECT most of the time. Engineers and operators live in all four. Even if your role is “read only,” you should understand writes so you can review someone else’s migration, or so you never run a dangerous snippet you found on the internet “to clean the table real quick.”
SELECT: read is the main job
SELECT returns a result set. It does not change the table. That is why it is safe to explore with, and why every write should start as a SELECT that shows the rows you plan to touch.
Basic shape:
SELECT
order_id,
customer_id,
amount,
status
FROM orders
ORDER BY order_id;On the Part 1 seed data you get the full toy order list. Keep this query nearby; it is your “what does the table look like right now?” button.
Example output:

Parts 3 through 5 go deep on columns, filters, and aggregates. For this part, remember three SELECT jobs:
- Explore a table
- Answer a question
- Preview the exact rows a later UPDATE or DELETE will hit
INSERT: add rows
INSERT creates new rows. You list columns (recommended) and values that match.
INSERT INTO customers (customer_id, name, region, email)
VALUES (6, 'Fran Wu', 'East', 'fran@example.com');Prove it landed:
SELECT
customer_id,
name,
region
FROM customers
WHERE customer_id = 6;Multiple rows in one statement:
INSERT INTO orders (order_id, customer_id, order_date, amount, status)
VALUES
(109, 6, '2026-02-01', 55.00, 'pending'),
(110, 6, '2026-02-03', 80.00, 'paid');Workplace notes:
- Name your columns. Relying on table order is fragile when someone adds a column later.
- Respect unique keys. Inserting a second
customer_id = 6should fail if the primary key is enforced. - Prefer application paths or controlled pipelines for production inserts. Hand SQL inserts are for practice, fixes, and admin work with review.
UPDATE: change existing rows
UPDATE sets new values on rows that already exist. The danger is scope. If you omit the filter, many engines will happily rewrite every row in the table.
Hard rule: Never run
UPDATEwithout aWHEREclause unless you truly intend to update every row, and even then write the intention in a comment and get a second pair of eyes.
Safe pattern: SELECT the target rows first.
SELECT
order_id,
status,
amount
FROM orders
WHERE order_id = 103;Then update only that order, for example when a pending payment clears:
UPDATE orders
SET status = 'paid'
WHERE order_id = 103;Verify:
SELECT
order_id,
status
FROM orders
WHERE order_id = 103;Updating several columns at once:
UPDATE customers
SET
region = 'Central',
email = 'ben.ortiz@example.com'
WHERE customer_id = 2;Still one WHERE. Still preview first if the filter is more complex than a primary key.
The preview trick for multi-row updates
Suppose you want to cancel all pending orders older than a cutoff in a real system. In practice you would write:
SELECT
order_id,
order_date,
status
FROM orders
WHERE status = 'pending'
AND order_date < '2026-01-15';Stare at the result. Check the row count. Only then mirror the same WHERE into UPDATE:
UPDATE orders
SET status = 'cancelled'
WHERE status = 'pending'
AND order_date < '2026-01-15';If the SELECT returned zero rows, stop. Do not “run the update anyway.” Something about your assumption was wrong.
DELETE: remove rows
DELETE removes entire rows. Same scope danger as UPDATE. Same hard rule.
Hard rule: Never run
DELETEwithout aWHEREclause unless you intend to empty the table, and that should be rare, explicit, and usually replaced by a safer bulk process.
Preview:
SELECT
order_id,
status
FROM orders
WHERE order_id = 105;Delete that cancelled demo row if you want it gone from the practice file:
DELETE FROM orders
WHERE order_id = 105;Confirm absence:
SELECT
order_id
FROM orders
WHERE order_id = 105;Empty result is success here. In production, prefer soft-delete patterns (a status or deleted flag) when history matters for audits. Hard deletes are for data you truly should not keep.
Worked example: fix a status, end to end
Scenario: Ops says order 109 should be paid, not pending. You are in the practice database, not production, but you still rehearse the safe sequence.
Step 1: show the orders table shape you care about.
SELECT
order_id,
customer_id,
order_date,
amount,
status
FROM orders
ORDER BY order_id;Example output:

Step 2: preview the single row.
SELECT
order_id,
status
FROM orders
WHERE order_id = 109;Step 3: update with the same filter.
UPDATE orders
SET status = 'paid'
WHERE order_id = 109;Step 4: verify, then optionally count paid orders for a sanity check.
SELECT
status,
COUNT(*) AS n
FROM orders
GROUP BY status
ORDER BY status;That last query is a light aggregate; Part 5 will explain GROUP BY properly. Here it is only a dashboard-style check that your write did not create chaos.
Transactions: the seatbelt (concept)
Many engines let you wrap changes in a transaction: begin, run statements, then COMMIT to keep them or ROLLBACK to undo. In a serious workplace update, that is how you avoid half-applied messes. SQLite supports transactions too.
BEGIN;
UPDATE orders
SET status = 'paid'
WHERE order_id = 103;
-- look with SELECT in the same session if your client allows
-- COMMIT; -- keep
-- ROLLBACK; -- undoExact client behavior varies. The idea does not: practice the muscle of “I can undo this batch” before you touch shared data. Part 8 returns to modification discipline with more depth.
Permissions and roles (why SELECT-only jobs exist)
In real companies, analysts often receive read-only warehouse roles. That is not an insult. It is blast-radius control. INSERT/UPDATE/DELETE may live in app services, controlled ETL, or admin paths. If your role is read-only, master SELECT deeply. Still learn write syntax so you understand pipelines and can review change scripts when asked.
If someone hands you write access on production “for convenience,” push for a sandbox. Convenience is how tables get emptied on a Friday afternoon.
A short incident story (fictional, familiar)
An analyst needed to fix a handful of pending orders that finance confirmed as paid. They drafted an UPDATE, got interrupted, and ran a statement that was missing the WHERE clause. Every order in the practice schema flipped to paid. In practice that was only a toy file. In production it would have been a very long night.
The recovery pattern, even on a toy database, is worth rehearsing: stop, do not run more writes, restore from backup or reseed from your script, then rewrite the change with a preview SELECT and a tight filter. If your workplace has point-in-time recovery, great. If not, prevention is the whole job. That is why this part sounds repetitive about WHERE. Repetition is cheaper than restore drills.
When you review someone else’s change script, look for the same things you want in your own: preview queries, key-based filters, explicit column lists on INSERT, and a stated row-count expectation (“should touch 12 rows”). If the script cannot say how many rows it should affect, it is not ready.
Common mistakes
- UPDATE without WHERE. The classic foot-gun. Entire tables set to the same email. Entire amounts zeroed. Always filter.
- DELETE without WHERE. Same story, worse ending.
- Skipping the preview SELECT. If you cannot show the rows first, you are guessing.
- Wrong table, right WHERE. Double-check the table name. GUIs with many tabs make this easy to mess up.
- Inserting orphan orders. An
orders.customer_idthat does not exist incustomersbreaks relational sense even if the engine allows it. - Treating SELECT * dumps as a backup strategy. Learn real backup tools for production. SELECT is not a recovery plan.
- Practicing writes only in your head. Break the toy database on purpose, then reseed. Fear shrinks with reps.
How to practice
- Insert a new customer and two orders. Select them back by id.
- Update one order status with the preview pattern. Update one customer email the same way.
- Delete one order you created for practice. Confirm it is gone.
- Intentionally write an UPDATE with a WHERE that matches zero rows. Observe that nothing changes. That is a useful failure mode.
- Write a one-page personal rule: “I preview with SELECT; I filter every write; I do not use production for learning.”
- When your data issues are about trust rather than syntax, keep data quality nearby. For broader paths, see Learn, Python, and spreadsheets to data.
Quick recap
- SELECT reads. INSERT adds. UPDATE changes. DELETE removes.
- Most analytics work is SELECT; write literacy still matters.
- Name columns on INSERT. Prefer primary-key filters for single-row fixes.
- Never UPDATE or DELETE without WHERE unless you truly mean every row.
- Preview with SELECT, then reuse the same filter on the write.
- Transactions and least-privilege roles reduce blast radius at work.
Next: make SELECT precise. Columns, aliases, DISTINCT, and LIMIT are how you stop hauling whole tables into meetings. That is Part 3.
Sources
Language references for the core commands:
- SQLite: SELECT
- SQLite: INSERT
- SQLite: UPDATE
- SQLite: DELETE
- PostgreSQL: Data Manipulation (INSERT, UPDATE, DELETE overview)
- Analytics Made Simple: SQL series
- Analytics Made Simple: Learn
- Analytics Made Simple: Data quality series
- Analytics Made Simple: Python series
- Analytics Made Simple: Spreadsheets to data
