[{"data":1,"prerenderedAt":103},["ShallowReactive",2],{"qa-\u002Fsql\u002Fdml\u002Finsert-update-delete":3},{"page":4,"siblings":95,"blog":100},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":85,"related":86,"seo":87,"seoDescription":88,"stem":89,"subtopic":90,"topic":91,"topicSlug":92,"updated":93,"__hash__":94},"qa\u002Fsql\u002Fdml\u002Finsert-update-delete.md","Insert Update Delete",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","SQL","sql",{},true,1,"\u002Fsql\u002Fdml\u002Finsert-update-delete",[23,28,32,36,40,44,48,52,56,60,64,69,73,77,81],{"id":24,"difficulty":25,"q":26,"a":27},"basic-insert","easy","What is the syntax for inserting a row into a table?","`INSERT INTO` adds one or more rows to a table. You list the target columns\nexplicitly so the statement stays correct if the table schema changes later.\n\n```sql\n-- Single row, explicit column list (preferred)\nINSERT INTO users (name, email, created_at)\nVALUES ('Alice', 'alice@example.com', now());\n\n-- Multiple rows in one statement (more efficient than many single inserts)\nINSERT INTO users (name, email, created_at)\nVALUES\n  ('Bob',   'bob@example.com',   now()),\n  ('Carol', 'carol@example.com', now());\n```\n\nOmitting the column list makes the `VALUES` order dependent on the physical\ncolumn order, which breaks silently when a column is added.\n\n**Rule of thumb:** always list target columns explicitly in every `INSERT`\nstatement, even when inserting into all columns.\n",{"id":29,"difficulty":25,"q":30,"a":31},"insert-select","How do you insert rows from another table?","`INSERT INTO … SELECT` copies rows produced by a `SELECT` directly into the\ntarget table without materializing them in the application.\n\n```sql\n-- Copy active users to an archive table\nINSERT INTO users_archive (id, name, email, archived_at)\nSELECT id, name, email, now()\nFROM   users\nWHERE  deactivated_at IS NOT NULL;\n\n-- Create a staging table from a production table\nINSERT INTO orders_staging\nSELECT * FROM orders WHERE created_at >= '2026-01-01';\n```\n\nThe `SELECT` can be as complex as needed — it can join tables, apply\nfunctions, filter, and aggregate. The column count and types must match\nthe target column list.\n\n**Rule of thumb:** prefer `INSERT … SELECT` over fetching rows in\napplication code and re-inserting them — it keeps the data movement inside\nthe database and avoids round-trip latency.\n",{"id":33,"difficulty":14,"q":34,"a":35},"upsert-on-conflict","What is an UPSERT and how do you write one in SQL?","An **UPSERT** (update-or-insert) inserts a row if it does not already exist,\nor updates it if it does — determined by a unique\u002Fprimary key conflict.\n\n```sql\n-- Postgres: ON CONFLICT DO UPDATE (INSERT ... ON CONFLICT)\nINSERT INTO page_views (page_id, date, views)\nVALUES (42, '2026-06-20', 1)\nON CONFLICT (page_id, date)\nDO UPDATE SET views = page_views.views + EXCLUDED.views;\n\n-- MySQL: INSERT ... ON DUPLICATE KEY UPDATE\nINSERT INTO page_views (page_id, date, views)\nVALUES (42, '2026-06-20', 1)\nON DUPLICATE KEY UPDATE views = views + VALUES(views);\n\n-- SQL Server: MERGE statement\nMERGE page_views AS target\nUSING (VALUES (42, '2026-06-20', 1)) AS src (page_id, date, views)\n  ON target.page_id = src.page_id AND target.date = src.date\nWHEN MATCHED     THEN UPDATE SET views = target.views + src.views\nWHEN NOT MATCHED THEN INSERT (page_id, date, views) VALUES (src.page_id, src.date, src.views);\n```\n\n**Rule of thumb:** use UPSERT for idempotent writes — counters, caches,\nconfiguration, event deduplication. It avoids the race condition of a\nseparate SELECT-then-INSERT in application code.\n",{"id":37,"difficulty":25,"q":38,"a":39},"on-conflict-do-nothing","How do you silently ignore a duplicate row on INSERT?","Use `ON CONFLICT DO NOTHING` (Postgres) or `INSERT IGNORE` (MySQL) to\nskip rows that would violate a unique constraint rather than raising an error.\n\n```sql\n-- Postgres\nINSERT INTO tags (name)\nVALUES ('sql'), ('database'), ('sql')   -- duplicate 'sql'\nON CONFLICT (name) DO NOTHING;\n-- Inserts 'database'; skips the second 'sql' silently\n\n-- MySQL\nINSERT IGNORE INTO tags (name)\nVALUES ('sql'), ('database'), ('sql');\n```\n\nIn SQL Server, the closest equivalent is `MERGE … WHEN NOT MATCHED THEN INSERT`.\n\n**Rule of thumb:** use `DO NOTHING` for idempotent seed data or batch\nimports where duplicates are expected and harmless. Avoid it when you need\nto know how many rows were actually inserted.\n",{"id":41,"difficulty":25,"q":42,"a":43},"basic-update","What is the syntax for updating rows in SQL?","`UPDATE` modifies column values in rows that satisfy the `WHERE` condition.\n**Omitting `WHERE` updates every row in the table.**\n\n```sql\n-- Update a single row by PK\nUPDATE users\nSET    name = 'Alice Smith', updated_at = now()\nWHERE  id = 1;\n\n-- Update multiple rows matching a condition\nUPDATE orders\nSET    status = 'archived'\nWHERE  created_at \u003C now() - INTERVAL '2 years';\n\n-- Update using a value from the same row (relative update)\nUPDATE products\nSET    stock = stock - 1\nWHERE  id = 99 AND stock > 0;\n```\n\n**Rule of thumb:** always write and test the `WHERE` clause of an `UPDATE`\nas a `SELECT` first to confirm exactly which rows will be affected before\ncommitting the change.\n",{"id":45,"difficulty":14,"q":46,"a":47},"update-join","How do you update rows using values from another table?","Updating based on a related table requires different syntax per database.\n\n```sql\n-- Postgres: UPDATE ... FROM\nUPDATE orders o\nSET    discount = c.tier_discount\nFROM   customers c\nWHERE  c.id = o.customer_id\n  AND  c.tier = 'gold';\n\n-- MySQL: UPDATE with JOIN\nUPDATE orders o\nJOIN   customers c ON c.id = o.customer_id\nSET    o.discount = c.tier_discount\nWHERE  c.tier = 'gold';\n\n-- SQL Server: UPDATE with FROM\u002FJOIN\nUPDATE o\nSET    o.discount = c.tier_discount\nFROM   orders o\nJOIN   customers c ON c.id = o.customer_id\nWHERE  c.tier = 'gold';\n```\n\n**Rule of thumb:** always alias both tables and double-check the join\ncondition — a wrong `ON` clause can fan out rows and cause each target row\nto be updated multiple times (non-deterministically in Postgres).\n",{"id":49,"difficulty":25,"q":50,"a":51},"basic-delete","What is the DELETE statement and what happens if you omit WHERE?","`DELETE FROM` removes rows that match the `WHERE` predicate. Without\n`WHERE`, **all rows in the table are deleted** (the table structure remains).\n\n```sql\n-- Delete a single row\nDELETE FROM users WHERE id = 42;\n\n-- Delete with a condition\nDELETE FROM sessions WHERE expires_at \u003C now();\n\n-- Delete all rows (use TRUNCATE for large tables — it's faster)\nDELETE FROM staging_data;\n```\n\nUnlike `TRUNCATE`, `DELETE` fires `ON DELETE` triggers, respects FK\n`ON DELETE CASCADE` \u002F `RESTRICT` rules, and is logged row-by-row — making\nit slower but more controllable.\n\n**Rule of thumb:** run `SELECT * FROM … WHERE \u003Ccondition>` before any\n`DELETE` to preview what will be removed. Wrap destructive deletes in a\ntransaction and `ROLLBACK` first to verify the row count.\n",{"id":53,"difficulty":14,"q":54,"a":55},"delete-join","How do you delete rows based on a condition in a related table?","```sql\n-- Postgres: DELETE ... USING\nDELETE FROM order_items oi\nUSING  orders o\nWHERE  oi.order_id = o.id\n  AND  o.status = 'cancelled';\n\n-- MySQL: DELETE with JOIN\nDELETE oi\nFROM   order_items oi\nJOIN   orders o ON o.id = oi.order_id\nWHERE  o.status = 'cancelled';\n\n-- SQL Server: DELETE with FROM\u002FJOIN\nDELETE oi\nFROM   order_items oi\nJOIN   orders o ON o.id = oi.order_id\nWHERE  o.status = 'cancelled';\n\n-- ANSI alternative using a subquery (all databases)\nDELETE FROM order_items\nWHERE  order_id IN (\n  SELECT id FROM orders WHERE status = 'cancelled'\n);\n```\n\n**Rule of thumb:** the subquery form (`WHERE … IN (SELECT …)`) is the\nmost portable across databases; use the `JOIN`-based form when the subquery\nis slow (the optimizer may not push it down efficiently).\n",{"id":57,"difficulty":14,"q":58,"a":59},"returning-clause","What does the RETURNING clause do in Postgres?","`RETURNING` appends a `SELECT`-like clause to `INSERT`, `UPDATE`, or\n`DELETE` and returns the affected rows — without a separate query. This\neliminates a round-trip and avoids race conditions from a subsequent\n`SELECT`.\n\n```sql\n-- Get the generated ID after INSERT\nINSERT INTO orders (customer_id, total)\nVALUES (7, 149.99)\nRETURNING id, created_at;\n\n-- See the old values before UPDATE overwrites them\nUPDATE products\nSET    price = price * 0.9\nWHERE  category = 'clearance'\nRETURNING id, name, price AS new_price;\n\n-- Confirm which rows were deleted\nDELETE FROM sessions\nWHERE  expires_at \u003C now()\nRETURNING id, user_id;\n```\n\nMySQL uses `LAST_INSERT_ID()` for inserts only. SQL Server uses the\n`OUTPUT` clause (`OUTPUT INSERTED.id, DELETED.old_col`).\n\n**Rule of thumb:** use `RETURNING` (Postgres) or `OUTPUT` (SQL Server)\nto retrieve generated IDs and audit old\u002Fnew values atomically — never\nfollow a mutation with a separate `SELECT` when the database can return\nthe data in the same statement.\n",{"id":61,"difficulty":14,"q":62,"a":63},"soft-delete","What is soft delete and how is it implemented in SQL?","**Soft delete** marks rows as deleted without physically removing them,\npreserving history and enabling recovery. Instead of `DELETE`, you\n`UPDATE` a `deleted_at` timestamp (or a boolean flag).\n\n```sql\n-- Schema: nullable deleted_at column\nALTER TABLE users ADD COLUMN deleted_at TIMESTAMPTZ;\n\n-- Soft delete\nUPDATE users SET deleted_at = now() WHERE id = 42;\n\n-- Query active rows only\nSELECT * FROM users WHERE deleted_at IS NULL;\n\n-- Partial unique index so email stays unique among active users only\nCREATE UNIQUE INDEX uq_users_email_active\n  ON users (email)\n  WHERE deleted_at IS NULL;\n\n-- View to hide deleted rows from most queries\nCREATE VIEW active_users AS\n  SELECT * FROM users WHERE deleted_at IS NULL;\n```\n\n**Rule of thumb:** use soft delete when you need an audit trail, recoverability,\nor regulatory retention. Add a partial index on `(natural_key) WHERE deleted_at IS NULL`\nto keep uniqueness constraints working correctly.\n",{"id":65,"difficulty":66,"q":67,"a":68},"batch-delete-large-table","hard","How do you safely delete millions of rows from a large table?","Deleting millions of rows in one statement locks large portions of the\ntable, fills the transaction log, and may time out. The solution is to\n**delete in small batches** inside a loop.\n\n```sql\n-- Postgres: batch delete loop (run from application or a DO block)\nDO $$\nDECLARE deleted_count INT;\nBEGIN\n  LOOP\n    DELETE FROM events\n    WHERE  id IN (\n      SELECT id FROM events\n      WHERE  created_at \u003C now() - INTERVAL '1 year'\n      LIMIT  1000          -- batch size\n    );\n    GET DIAGNOSTICS deleted_count = ROW_COUNT;\n    EXIT WHEN deleted_count = 0;\n    PERFORM pg_sleep(0.05); -- brief pause to release locks\n  END LOOP;\nEND $$;\n```\n\nEach batch commits independently, so the table remains available for reads\nand writes between batches. The lock held per batch is small and short-lived.\n\n**Rule of thumb:** never delete more than ~5 000–10 000 rows per transaction\non a live table. Use batching with a pause between iterations to keep\nreplication lag and lock contention low.\n",{"id":70,"difficulty":25,"q":71,"a":72},"truncate-vs-delete","When should you use TRUNCATE instead of DELETE?","`TRUNCATE` removes all rows by deallocating data pages rather than\nlogging each deletion — it is orders of magnitude faster than\n`DELETE FROM table` with no `WHERE` clause.\n\n```sql\n-- Slow: logs every row deletion\nDELETE FROM staging_orders;\n\n-- Fast: drops and recreates the data pages\nTRUNCATE TABLE staging_orders;\n\n-- TRUNCATE also resets identity\u002Fsequence counters\nTRUNCATE TABLE events RESTART IDENTITY;\n\n-- TRUNCATE multiple tables atomically (Postgres)\nTRUNCATE TABLE staging_orders, staging_items RESTART IDENTITY CASCADE;\n```\n\nTrade-offs vs `DELETE`:\n- `TRUNCATE` does not fire row-level triggers.\n- `TRUNCATE` cannot have a `WHERE` clause — it always removes all rows.\n- In MySQL, `TRUNCATE` is DDL and auto-commits; in Postgres it is transactional.\n\n**Rule of thumb:** use `TRUNCATE` to reset staging, temp, or test-fixture\ntables between runs. Use `DELETE` when you need `WHERE` filtering, trigger\nfiring, or row-count reporting.\n",{"id":74,"difficulty":14,"q":75,"a":76},"update-vs-replace","What is the difference between UPDATE and REPLACE in MySQL?","In MySQL, `REPLACE INTO` works like an UPSERT but via a **delete-then-insert**\nstrategy rather than an in-place update. If a row with the same primary key\n(or unique key) exists, MySQL deletes it and inserts the new row. All columns\nnot listed in the `REPLACE` get their default values — not the existing values.\n\n```sql\n-- Suppose users(id PK, name, email, created_at DEFAULT now())\nREPLACE INTO users (id, name, email)\nVALUES (1, 'Alice Updated', 'alice@example.com');\n-- If id=1 existed: deletes old row, inserts new row.\n-- created_at will be reset to now(), NOT kept from the old row!\n\n-- Safer alternative that preserves untouched columns:\nINSERT INTO users (id, name, email)\nVALUES (1, 'Alice Updated', 'alice@example.com')\nON DUPLICATE KEY UPDATE\n  name  = VALUES(name),\n  email = VALUES(email);\n```\n\n**Rule of thumb:** avoid `REPLACE INTO` — it silently wipes columns you\ndidn't list and can cause unexpected data loss. Use `INSERT … ON DUPLICATE KEY UPDATE`\ninstead for in-place upserts.\n",{"id":78,"difficulty":14,"q":79,"a":80},"conditional-update","How do you update a column to different values based on a condition?","Use a `CASE` expression inside `SET` to apply different values depending\non each row's state — more efficient than multiple `UPDATE` statements.\n\n```sql\n-- Apply tiered discounts in a single pass\nUPDATE orders\nSET discount_pct = CASE\n  WHEN total >= 500  THEN 20\n  WHEN total >= 200  THEN 10\n  WHEN total >= 100  THEN 5\n  ELSE 0\nEND\nWHERE status = 'pending';\n\n-- Flip a boolean flag\nUPDATE tasks\nSET    is_done = CASE WHEN is_done THEN FALSE ELSE TRUE END\nWHERE  id = 7;\n```\n\n**Rule of thumb:** use `SET col = CASE … END` when multiple rows need\ndifferent values updated in one pass. It avoids multiple round-trips and\nis atomic — all rows are updated in the same transaction.\n",{"id":82,"difficulty":66,"q":83,"a":84},"cte-with-dml","How do you use a CTE with INSERT, UPDATE, or DELETE?","A **writeable CTE** (Postgres, SQL Server) lets you stage intermediate\nresults or chain mutations. The CTE body can be a `DELETE` or `UPDATE`\nwith `RETURNING`, whose output feeds the outer `INSERT`.\n\n```sql\n-- Postgres: move rows from one table to another atomically\nWITH deleted AS (\n  DELETE FROM job_queue\n  WHERE  status = 'pending'\n    AND  locked_by IS NULL\n  LIMIT  10\n  RETURNING *\n)\nINSERT INTO job_archive\nSELECT *, now() AS archived_at\nFROM   deleted;\n\n-- CTE as a data source for UPDATE\nWITH price_hike AS (\n  SELECT id, price * 1.05 AS new_price\n  FROM   products\n  WHERE  category = 'premium'\n)\nUPDATE products p\nSET    price = ph.new_price\nFROM   price_hike ph\nWHERE  p.id = ph.id;\n```\n\n**Rule of thumb:** use writeable CTEs to express complex multi-step\nmutations as a single atomic statement. They are far safer than chaining\nseparate DML statements across multiple round-trips where partial failure\ncan leave data in an inconsistent state.\n",15,null,{"description":11},"SQL INSERT, UPDATE, DELETE interview questions — syntax, bulk inserts, UPSERT, conditional updates, cascading deletes, RETURNING, and safe mutation patterns across Postgres, MySQL, and SQL Server.","sql\u002Fdml\u002Finsert-update-delete","INSERT, UPDATE & DELETE","Modifying Data","dml","2026-06-20","hEz-MQNAYp16Bxz5JgoePPGZKXw_OaszDPRiwd6FnSY",[96,97],{"subtopic":90,"path":21,"order":20},{"subtopic":98,"path":99,"order":12},"Views","\u002Fsql\u002Fdml\u002Fviews",{"path":101,"title":102},"\u002Fblog\u002Fsql-insert-update-delete-dml","SQL INSERT, UPDATE, DELETE — DML That Stays Safe at Scale",1782244107220]