CTE with INSERT, UPDATE, DELETE (writable CTEs) in PostgreSQL - Time & Space Complexity
When using writable CTEs in PostgreSQL, we want to know how the time to run the query changes as the data grows.
We ask: How does the number of rows affect the work done by INSERT, UPDATE, or DELETE inside a CTE?
Analyze the time complexity of this writable CTE example.
WITH updated_rows AS (
UPDATE employees
SET salary = salary * 1.1
WHERE department = 'Sales'
RETURNING *
)
DELETE FROM employees
WHERE id IN (SELECT id FROM updated_rows WHERE salary > 100000);
This code first increases salaries for Sales employees, then deletes those whose salary exceeds 100,000.
Look for repeated work inside the query.
- Primary operation: Scanning and updating rows in the employees table matching the department condition.
- How many times: Each matching row is processed once during UPDATE, then some rows are checked again for DELETE.
As the number of employees grows, the work grows roughly in proportion.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 rows updated and checked |
| 100 | About 100 rows updated and checked |
| 1000 | About 1000 rows updated and checked |
Pattern observation: The operations grow linearly with the number of rows affected.
Time Complexity: O(n)
This means the time to run the query grows roughly in direct proportion to the number of rows involved.
[X] Wrong: "The DELETE inside the CTE runs separately and doubles the work for all rows."
[OK] Correct: The DELETE only processes rows returned by the UPDATE, so it works on a smaller subset, not the whole table again.
Understanding how writable CTEs scale helps you explain query performance clearly and shows you can reason about real database operations.
"What if the DELETE condition was changed to remove rows based on a different column not related to the UPDATE? How would that affect time complexity?"
Practice
Solution
Step 1: Understand writable CTEs
Writable CTEs allow data modification commands like INSERT, UPDATE, or DELETE inside a WITH clause.Step 2: Compare options
Options B, C, and D describe other features unrelated to writable CTEs.Final Answer:
To perform INSERT, UPDATE, or DELETE operations inside a WITH clause -> Option CQuick Check:
Writable CTEs = Data modification in WITH [OK]
- Confusing writable CTEs with temporary tables
- Thinking writable CTEs speed up SELECT queries
- Mixing writable CTEs with function definitions
Solution
Step 1: Review writable CTE update syntax
The correct syntax uses WITH updated AS (UPDATE ... RETURNING *) followed by a SELECT from updated.Step 2: Analyze options
WITH updated AS (UPDATE employees SET salary = salary * 1.1 RETURNING *) SELECT * FROM updated; matches the correct syntax. Options A, C, and D have syntax errors or misuse RETURNING.Final Answer:
WITH updated AS (UPDATE employees SET salary = salary * 1.1 RETURNING *) SELECT * FROM updated; -> Option DQuick Check:
Writable CTE update = WITH + UPDATE + RETURNING + SELECT [OK]
- Omitting RETURNING clause in writable CTE
- Trying to UPDATE inside WITH without SELECT
- Misplacing RETURNING outside the CTE
products(id, name, stock) with rows: (1, 'Pen', 10), (2, 'Pencil', 5), what will be the result of this query?WITH updated AS (UPDATE products SET stock = stock + 10 WHERE name = 'Pen' RETURNING *) SELECT * FROM updated;
Solution
Step 1: Identify updated rows
The UPDATE increases stock by 10 only for product with name 'Pen' (id=1), changing stock from 10 to 20.Step 2: Understand RETURNING and SELECT
The RETURNING * returns the updated row, and the SELECT outputs it.Final Answer:
[{id:1, name:'Pen', stock:20}] -> Option AQuick Check:
Updated Pen stock = 10 + 10 = 20 [OK]
- Assuming all rows are updated
- Ignoring RETURNING clause output
- Confusing stock before and after update
WITH deleted AS (DELETE FROM orders WHERE status = 'cancelled') SELECT * FROM deleted;
Solution
Step 1: Check DELETE in writable CTE
DELETE inside WITH must include RETURNING to produce rows for the outer SELECT.Step 2: Analyze the query
The query lacks RETURNING, so 'deleted' has no rows to select from, resulting in an empty result set.Final Answer:
Missing RETURNING clause in DELETE statement -> Option BQuick Check:
Writable CTE DELETE needs RETURNING [OK]
- Omitting RETURNING after DELETE in CTE
- Thinking DELETE can't be in WITH clause
- Misunderstanding SELECT after writable CTE
Solution
Step 1: Insert and capture new rows
Use a writable CTE to insert and RETURNING id, name to identify new users.Step 2: Update using CTE results
Update users joining with inserted rows on id, setting status to 'active', and RETURNING updated rows.Step 3: Analyze options
WITH ins AS (INSERT INTO users(name) VALUES ('Alice'), ('Bob') RETURNING id, name) UPDATE users SET status = 'active' FROM ins WHERE users.id = ins.id RETURNING users.*; correctly chains insert and update in one statement with proper RETURNING and join. INSERT INTO users(name) VALUES ('Alice'), ('Bob'); WITH upd AS (UPDATE users SET status = 'active' WHERE name IN ('Alice', 'Bob') RETURNING *) SELECT * FROM upd; separates insert and update into multiple statements. WITH ins AS (INSERT INTO users(name) VALUES ('Alice'), ('Bob')) UPDATE users SET status = 'active' WHERE id IN (SELECT id FROM ins); lacks RETURNING so CTE returns no rows and subquery updates nothing. WITH ins AS (INSERT INTO users(name) VALUES ('Alice'), ('Bob')) UPDATE ins SET status = 'active' WHERE name IN ('Alice', 'Bob'); incorrectly attempts to UPDATE the CTE which is invalid.Final Answer:
WITH ins AS (INSERT INTO users(name) VALUES ('Alice'), ('Bob') RETURNING id, name) UPDATE users SET status = 'active' FROM ins WHERE users.id = ins.id RETURNING users.*; -> Option AQuick Check:
Writable CTE insert + update with join = WITH ins AS (INSERT INTO users(name) VALUES ('Alice'), ('Bob') RETURNING id, name) UPDATE users SET status = 'active' FROM ins WHERE users.id = ins.id RETURNING users.*; [OK]
- Omitting RETURNING clause before using CTE in subquery
- Splitting insert and update into separate statements
- Omitting RETURNING to get updated rows
Ready to prove it?
Pass the test and this pattern moves unknown
