SELECT, INSERT, UPDATE, DELETE and RETURNING
corebeginnerSELECT reads rows, INSERT adds them, UPDATE changes them, DELETE removes them. RETURNING attaches to a write to hand back the affected rows in the same round trip — no second query needed to see what happened.
Think of it as
Four verbs and one modifier. SELECT never changes data — it only looks. INSERT, UPDATE and DELETE all change data and, by default, tell you nothing about what changed beyond a row count. RETURNING is the one word that makes a write also answer "and what did that actually produce?" in the same statement.
What we're doing: Run all four statements against one table, using RETURNING each time to see the effect without a follow-up SELECT.
- 1
- id fills itself; done defaults to false for every new row.
- 3–4
- Two rows in one INSERT. RETURNING hands back both, in the order they were inserted.
- 6
- A plain SELECT, just to see the table state — not required by RETURNING, done here to show it.
- 8–9
- UPDATE changes one row; RETURNING confirms exactly what changed, without a second query.
- 11–12
- DELETE removes the row that is now done; RETURNING hands back what was deleted, since the row no longer exists to SELECT afterward.
id | title
----+-------------------
1 | Ship the feature
2 | Write docs
(2 rows)
id | title | done
----+-------------------+------
1 | Ship the feature | f
2 | Write docs | f
(2 rows)
id | done
----+------
1 | t
(1 row)
id | title
----+-------------------
1 | Ship the feature
(1 row)Why this works: RETURNING attaches directly to the write statement, so PostgreSQL hands back the affected rows as part of executing it — there is no window where the client has committed a write but not yet seen its result. This matters most for DELETE: once a row is gone, an ordinary SELECT can never see it again, so RETURNING is the only way to get the deleted row's data back in the same transaction. UPDATE's RETURNING reflects the row AFTER the change, which is why done reads 't', not the pre-update 'f'.
Running UPDATE or DELETE with no WHERE clause
Wrong
Better
What you see: The wrong version reports "UPDATE 2" (or however many rows the table has) when the intent was to change one row — every task is now marked done, silently, with no error raised.
Why: UPDATE and DELETE have no implicit scope — omitting WHERE means "every row in the table," not "no rows" or an error. PostgreSQL has no confirmation prompt for this the way an interactive tool might; the statement simply runs. Always write the WHERE clause first, or run the equivalent SELECT first to see exactly which rows would be affected before switching the SELECT to an UPDATE or DELETE.
- INSERT/UPDATE/DELETE — changes rows
- RETURNING * — hands back the affected rows
- result set — no second SELECT needed
The four statements, and what RETURNING adds to each
Together
Remember: RETURNING hands back affected rows in the same round trip — the only way to see a deleted row's data, since a later SELECT never can.
See also: filtering and sorting · postgresql as rdbms

