[{"data":1,"prerenderedAt":186},["ShallowReactive",2],{"topic-sql-dml":3},{"framework":4,"topic":16,"subtopics":25},{"id":5,"description":6,"extension":7,"icon":8,"meta":9,"name":10,"order":11,"slug":12,"stem":13,"tier":14,"__hash__":15},"frameworks\u002Fframeworks\u002Fsql.yml","SQL interview questions on queries, joins and aggregation — essential for every backend, data and analytics interview.","yml","database",{},"SQL",4,"sql","frameworks\u002Fsql",1,"lpzsOj2p9p9W0Tctwc61nP-ulZAA80R5gJiyaZS6ZeI",{"id":17,"description":18,"extension":7,"frameworkSlug":12,"meta":19,"name":20,"order":21,"slug":22,"stem":23,"__hash__":24},"topics\u002Ftopics\u002Fsql-dml.yml","INSERT, UPDATE, DELETE, UPSERT and views — changing the data and exposing it through saved queries.",{},"Modifying Data",5,"dml","topics\u002Fsql-dml","D7PgAya6q-kLOA4xB2C59Qc1jHN38F-54RXtgnWB_C8",[26,112],{"id":27,"title":28,"body":29,"description":33,"difficulty":36,"extension":37,"framework":10,"frameworkSlug":12,"meta":38,"navigation":39,"order":14,"path":40,"questions":41,"questionsCount":104,"related":105,"seo":106,"seoDescription":107,"stem":108,"subtopic":109,"topic":20,"topicSlug":22,"updated":110,"__hash__":111},"qa\u002Fsql\u002Fdml\u002Finsert-update-delete.md","Insert Update Delete",{"type":30,"value":31,"toc":32},"minimark",[],{"title":33,"searchDepth":34,"depth":34,"links":35},"",2,[],"medium","md",{},true,"\u002Fsql\u002Fdml\u002Finsert-update-delete",[42,47,51,55,59,63,67,71,75,79,83,88,92,96,100],{"id":43,"difficulty":44,"q":45,"a":46},"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":48,"difficulty":44,"q":49,"a":50},"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":52,"difficulty":36,"q":53,"a":54},"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":56,"difficulty":44,"q":57,"a":58},"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":60,"difficulty":44,"q":61,"a":62},"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":64,"difficulty":36,"q":65,"a":66},"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":68,"difficulty":44,"q":69,"a":70},"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":72,"difficulty":36,"q":73,"a":74},"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":76,"difficulty":36,"q":77,"a":78},"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":80,"difficulty":36,"q":81,"a":82},"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":84,"difficulty":85,"q":86,"a":87},"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":89,"difficulty":44,"q":90,"a":91},"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":93,"difficulty":36,"q":94,"a":95},"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":97,"difficulty":36,"q":98,"a":99},"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":101,"difficulty":85,"q":102,"a":103},"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":33},"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","2026-06-20","hEz-MQNAYp16Bxz5JgoePPGZKXw_OaszDPRiwd6FnSY",{"id":113,"title":114,"body":115,"description":33,"difficulty":36,"extension":37,"framework":10,"frameworkSlug":12,"meta":119,"navigation":39,"order":34,"path":120,"questions":121,"questionsCount":104,"related":105,"seo":182,"seoDescription":183,"stem":184,"subtopic":114,"topic":20,"topicSlug":22,"updated":110,"__hash__":185},"qa\u002Fsql\u002Fdml\u002Fviews.md","Views",{"type":30,"value":116,"toc":117},[],{"title":33,"searchDepth":34,"depth":34,"links":118},[],{},"\u002Fsql\u002Fdml\u002Fviews",[122,126,130,134,138,142,146,150,154,158,162,166,170,174,178],{"id":123,"difficulty":44,"q":124,"a":125},"what-is-view","What is a view in SQL?","A **view** is a named, stored `SELECT` statement. Querying a view runs the\nunderlying query at that moment — the result is not stored (contrast with a\nmaterialized view). Views look and act like tables from the caller's\nperspective but contain no data of their own.\n\n```sql\n-- Define the view\nCREATE VIEW active_users AS\n  SELECT id, name, email\n  FROM   users\n  WHERE  deleted_at IS NULL;\n\n-- Query it exactly like a table\nSELECT * FROM active_users WHERE name ILIKE 'alice%';\n\n-- The database executes the underlying query at query time:\n-- SELECT id, name, email FROM users\n-- WHERE deleted_at IS NULL AND name ILIKE 'alice%'\n```\n\n**Rule of thumb:** use views to give a stable, simple name to a complex\nor frequently repeated query — and to hide columns or rows that callers\nshould not access.\n",{"id":127,"difficulty":44,"q":128,"a":129},"create-replace-view","How do you create, replace, and drop a view?","```sql\n-- Create (fails if the view already exists)\nCREATE VIEW recent_orders AS\n  SELECT id, customer_id, total, created_at\n  FROM   orders\n  WHERE  created_at >= now() - INTERVAL '30 days';\n\n-- Replace (redefines in place — dependent GRANTs are preserved in Postgres)\nCREATE OR REPLACE VIEW recent_orders AS\n  SELECT id, customer_id, total, created_at, status\n  FROM   orders\n  WHERE  created_at >= now() - INTERVAL '30 days';\n\n-- Drop\nDROP VIEW recent_orders;\n\n-- Drop even if other views depend on it (Postgres)\nDROP VIEW recent_orders CASCADE;\n```\n\n`CREATE OR REPLACE` in Postgres requires the new definition to have the\nsame columns in the same order (you can add columns at the end, but not\nremove or reorder existing ones). SQL Server has no `OR REPLACE`; use\n`ALTER VIEW` instead.\n\n**Rule of thumb:** use `CREATE OR REPLACE VIEW` in migrations to avoid\ndropping dependent objects. Only use `DROP VIEW` when removing the view\nentirely.\n",{"id":131,"difficulty":44,"q":132,"a":133},"view-use-cases","What are the main use cases for views?","1. **Simplify complex queries** — encapsulate multi-table joins so callers\n   write `SELECT * FROM order_summary` instead of a 10-line join.\n2. **Row and column security** — expose only the columns and rows a role\n   should see, without granting access to the base tables.\n3. **Stable interface over a changing schema** — rename or restructure tables\n   while keeping the view's column names unchanged for backward compatibility.\n4. **Soft-delete \u002F active-record filter** — `WHERE deleted_at IS NULL`\n   applied once in the view, not in every query.\n\n```sql\n-- Security: analysts can see revenue but not PII\nCREATE VIEW sales_summary AS\n  SELECT date_trunc('day', created_at) AS day,\n         SUM(total)                   AS revenue,\n         COUNT(*)                     AS order_count\n  FROM   orders\n  GROUP  BY 1;\n\nGRANT SELECT ON sales_summary TO analyst_role;\n-- analysts cannot SELECT from the orders table directly\n```\n\n**Rule of thumb:** a view is the right tool when multiple queries share\nthe same complex join or filter logic — it is a DRY principle applied to SQL.\n",{"id":135,"difficulty":36,"q":136,"a":137},"updatable-views","Can you INSERT, UPDATE, or DELETE through a view?","Yes — a view is **updatable** if it satisfies all of these conditions:\n- Maps to exactly **one base table**.\n- Does not use `DISTINCT`, `GROUP BY`, `HAVING`, aggregates, window\n  functions, `UNION`, or set operations.\n- Does not use subqueries in the `SELECT` list.\n\n```sql\nCREATE VIEW active_products AS\n  SELECT id, name, price\n  FROM   products\n  WHERE  archived = FALSE;\n\n-- This INSERT goes through to the products table\nINSERT INTO active_products (name, price) VALUES ('Widget', 9.99);\n\n-- This UPDATE modifies the underlying products row\nUPDATE active_products SET price = 8.99 WHERE id = 1;\n\n-- Caveat: you could insert a row that then disappears from the view\n-- if archived is not set to FALSE by default. Use WITH CHECK OPTION to prevent this.\n```\n\n**Rule of thumb:** rely on updatable views only for simple single-table\nviews with a clear filter. For complex views, use `INSTEAD OF` triggers\nor handle mutations in application code on the base table.\n",{"id":139,"difficulty":36,"q":140,"a":141},"with-check-option","What does WITH CHECK OPTION do on a view?","`WITH CHECK OPTION` prevents `INSERT` and `UPDATE` through the view from\nproducing rows that would no longer be visible through that view. Without it,\nyou can insert a row that immediately \"disappears\" from the view.\n\n```sql\nCREATE VIEW active_products AS\n  SELECT id, name, price, archived\n  FROM   products\n  WHERE  archived = FALSE\nWITH CHECK OPTION;\n\n-- This fails: the new row would have archived = TRUE,\n-- so it would not be visible through the view\nUPDATE active_products\nSET    archived = TRUE\nWHERE  id = 1;\n-- ERROR: new row violates check option for view \"active_products\"\n\n-- Without WITH CHECK OPTION the UPDATE would succeed and the row\n-- would silently vanish from the view's result set.\n```\n\n**Rule of thumb:** add `WITH CHECK OPTION` to every updatable view that\nfilters rows — it prevents mutations that produce invisible rows and makes\nthe view behave as a consistent, self-contained interface.\n",{"id":143,"difficulty":36,"q":144,"a":145},"view-vs-cte","When should you use a view vs a CTE?","| | View | CTE |\n|---|---|---|\n| **Scope** | Persistent, reusable across sessions | Single-query, local |\n| **Access control** | Can `GRANT SELECT` on it | Cannot grant independently |\n| **Performance** | Optimizer can inline; no extra cost in Postgres | Same — inlined by default |\n| **Parameterization** | Cannot accept parameters | Cannot either (use functions) |\n| **Discoverability** | Visible in `information_schema` | Invisible outside the query |\n\n```sql\n-- Use a VIEW: reused in many places, needs access control\nCREATE VIEW daily_revenue AS\n  SELECT date_trunc('day', created_at) AS day, SUM(total) AS revenue\n  FROM orders GROUP BY 1;\n\n-- Use a CTE: decompose a single complex query for readability\nWITH base AS (\n  SELECT *, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY created_at) AS rn\n  FROM orders\n)\nSELECT * FROM base WHERE rn = 1;\n```\n\n**Rule of thumb:** use a view when you need to share, reuse, or restrict\naccess to a query across the codebase. Use a CTE when you need to\ndecompose a single query for readability — it disappears after the query runs.\n",{"id":147,"difficulty":36,"q":148,"a":149},"materialized-view-vs-view","What is the difference between a view and a materialized view?","A **regular view** executes its underlying query every time it is queried —\nthe data is always current but the query cost is paid on every access.\n\nA **materialized view** stores the query result on disk like a table. Reads\nare instant (no recomputation), but the data is **stale** until explicitly\nrefreshed.\n\n```sql\n-- Regular view: always fresh, always recomputes\nCREATE VIEW monthly_sales AS\n  SELECT date_trunc('month', created_at) AS month, SUM(total) AS revenue\n  FROM orders GROUP BY 1;\n\n-- Materialized view (Postgres): fast reads, manual refresh\nCREATE MATERIALIZED VIEW monthly_sales_mv AS\n  SELECT date_trunc('month', created_at) AS month, SUM(total) AS revenue\n  FROM orders GROUP BY 1;\n\n-- Refresh (blocks reads unless CONCURRENTLY is used)\nREFRESH MATERIALIZED VIEW monthly_sales_mv;\n\n-- Non-blocking refresh (requires a unique index)\nCREATE UNIQUE INDEX ON monthly_sales_mv (month);\nREFRESH MATERIALIZED VIEW CONCURRENTLY monthly_sales_mv;\n```\n\n**Rule of thumb:** use a regular view when data must be current; use a\nmaterialized view for expensive aggregations where slightly stale data is\nacceptable (dashboards, reports). Schedule the refresh as a cron job.\n",{"id":151,"difficulty":36,"q":152,"a":153},"view-performance","Do views have a performance cost compared to writing the query inline?","In **Postgres and SQL Server**, views are **inlined** by the optimizer —\nthe query planner substitutes the view definition into the outer query\nand optimizes the whole thing as a single query. There is typically no\nperformance difference between querying through a view and writing the\nequivalent SQL directly.\n\n```sql\n-- These two are optimized identically in Postgres:\nSELECT * FROM active_users WHERE name = 'Alice';\n\nSELECT * FROM (\n  SELECT id, name, email FROM users WHERE deleted_at IS NULL\n) sub WHERE name = 'Alice';\n-- Both produce the same plan: index scan on (name) with deleted_at IS NULL pushed down.\n```\n\n**Exception**: views with `DISTINCT`, `LIMIT`, subqueries in `SELECT`, or\nwindow functions can prevent full predicate pushdown — check with `EXPLAIN`.\n\n**Rule of thumb:** view indirection is free in most cases. Always use\n`EXPLAIN ANALYZE` to confirm that predicates from the outer query are\npushed inside the view's definition; if they are not, the view may force\na full scan.\n",{"id":155,"difficulty":36,"q":156,"a":157},"security-view","How do views implement row-level security?","By granting `SELECT` on a view (not the base table), you restrict what\ndata each role can see. The view acts as a **security boundary**: callers\nonly see rows the view exposes.\n\n```sql\n-- Base table: orders (all customers)\n-- View: each salesperson sees only their own customer's orders\n\nCREATE VIEW my_orders AS\n  SELECT o.*\n  FROM   orders o\n  JOIN   salespeople s ON s.customer_id = o.customer_id\n  WHERE  s.username = current_user;   -- session-level user\n\n-- Grant only the view, not the table\nGRANT SELECT ON my_orders TO salesperson_role;\nREVOKE ALL ON orders FROM salesperson_role;\n```\n\nPostgres also offers **Row-Level Security (RLS)** as a more flexible\nalternative that enforces policies at the table level.\n\n**Rule of thumb:** views-as-security-boundaries work well for simple,\nrole-based access patterns. For fine-grained, data-driven access control\n(multi-tenant apps), prefer Postgres RLS — it applies even when queries\nbypass the view and hit the table directly.\n",{"id":159,"difficulty":85,"q":160,"a":161},"schema-changes-view","What happens to a view when the underlying table schema changes?","Views store the query text, not the resolved columns. The behavior on\nschema change differs by database:\n\n- **Postgres**: if you `SELECT *` in the view and add a column to the\n  base table, the view does **not** automatically include the new column —\n  the `*` is expanded at view creation time. To pick up the new column,\n  you must `CREATE OR REPLACE VIEW`. If you drop a column referenced in\n  the view, querying the view raises an error.\n- **MySQL**: views are re-parsed on each access, so dropping a referenced\n  column makes the view fail at query time (not at drop time).\n- **SQL Server**: views can be refreshed with `sp_refreshview` to\n  re-resolve `*` expansions after schema changes.\n\n```sql\n-- Check for broken views in Postgres\nSELECT schemaname, viewname\nFROM   pg_views v\nWHERE  NOT EXISTS (\n  SELECT 1 FROM information_schema.view_table_usage\n  WHERE  view_name = v.viewname\n);\n\n-- Or simply try:\nSELECT * FROM problematic_view LIMIT 0;\n-- Will surface a dependency error immediately.\n```\n\n**Rule of thumb:** avoid `SELECT *` in view definitions — always list\ncolumns explicitly. Run a migration smoke-test against all views after any\ntable schema change.\n",{"id":163,"difficulty":85,"q":164,"a":165},"recursive-view","Can a view be recursive?","Yes — in Postgres and SQL Server you can create a view that wraps a\nrecursive CTE, enabling callers to query hierarchical data (trees, graphs)\nwithout writing the recursion each time.\n\n```sql\n-- Postgres: recursive view for an employee org chart\nCREATE RECURSIVE VIEW org_chart (id, name, manager_id, depth, path) AS (\n  -- Anchor: top-level employees\n  SELECT id, name, manager_id, 0, ARRAY[id]\n  FROM   employees\n  WHERE  manager_id IS NULL\n\n  UNION ALL\n\n  -- Recursive: employees whose manager is already in the result\n  SELECT e.id, e.name, e.manager_id, oc.depth + 1, oc.path || e.id\n  FROM   employees e\n  JOIN   org_chart oc ON oc.id = e.manager_id\n);\n\n-- Callers query it without knowing it's recursive\nSELECT * FROM org_chart WHERE depth \u003C= 3 ORDER BY path;\n```\n\n**Rule of thumb:** wrap recursive CTEs in a view when the hierarchy query\nis reused across multiple callers. Set a depth limit inside the CTE to\nguard against infinite loops from cyclic data.\n",{"id":167,"difficulty":85,"q":168,"a":169},"indexed-view-sql-server","What is an indexed view in SQL Server?","An **indexed view** (SQL Server's term for a materialized view) is a view\nwith a **clustered unique index** created on it. SQL Server physically stores\nthe result set and updates it as the underlying data changes (unlike Postgres,\nwhich requires a manual `REFRESH`).\n\n```sql\n-- Must use SCHEMABINDING to prevent base table changes from breaking the view\nCREATE VIEW dbo.daily_revenue\nWITH SCHEMABINDING AS\n  SELECT\n    CONVERT(DATE, created_at) AS day,\n    SUM(total)                AS revenue,\n    COUNT_BIG(*)              AS order_count   -- COUNT_BIG required for indexed views\n  FROM dbo.orders\n  GROUP BY CONVERT(DATE, created_at);\nGO\n\n-- Create the clustered index to materialize it\nCREATE UNIQUE CLUSTERED INDEX ix_daily_revenue_day\n  ON dbo.daily_revenue (day);\n```\n\nThe optimizer can automatically use the indexed view to satisfy matching\nqueries against the base table — even if the query doesn't mention the view.\n\n**Rule of thumb:** use indexed views in SQL Server for expensive, frequently\nqueried aggregations that can tolerate the write overhead of keeping the\nmaterialized result up to date automatically.\n",{"id":171,"difficulty":85,"q":172,"a":173},"view-vs-table-function","When would you use a table-valued function instead of a view?","A **view** is a fixed query — it cannot accept parameters. A\n**table-valued function (TVF)** looks like a table to callers but accepts\narguments, making it a parameterizable view.\n\n```sql\n-- Postgres: table-valued function\nCREATE FUNCTION orders_for_customer(p_customer_id INT)\nRETURNS TABLE (id INT, total NUMERIC, created_at TIMESTAMPTZ)\nLANGUAGE SQL STABLE AS $$\n  SELECT id, total, created_at\n  FROM   orders\n  WHERE  customer_id = p_customer_id;\n$$;\n\n-- Call it like a table\nSELECT * FROM orders_for_customer(42) WHERE total > 100;\n\n-- SQL Server equivalent (inline TVF)\nCREATE FUNCTION dbo.OrdersForCustomer(@CustomerId INT)\nRETURNS TABLE AS\nRETURN (\n  SELECT id, total, created_at FROM orders WHERE customer_id = @CustomerId\n);\nSELECT * FROM dbo.OrdersForCustomer(42) WHERE total > 100;\n```\n\nThe optimizer inlines simple TVFs just like views, so they are as fast\nas writing the query directly.\n\n**Rule of thumb:** use a view when the query is the same for all callers;\nuse a table-valued function when callers need to pass a filter parameter\nthat determines which rows are returned.\n",{"id":175,"difficulty":36,"q":176,"a":177},"view-dependencies","How do you find which tables and columns a view depends on?","```sql\n-- Postgres: view dependencies via information_schema\nSELECT vtu.view_name,\n       vtu.table_name,\n       vtu.column_name\nFROM   information_schema.view_column_usage vtu\nWHERE  vtu.view_name = 'active_users'\nORDER  BY vtu.table_name, vtu.column_name;\n\n-- Postgres: all objects that depend on a table (to check before DROP)\nSELECT dependent_ns.nspname AS schema,\n       dependent_view.relname AS dependent_view\nFROM   pg_depend\nJOIN   pg_rewrite ON pg_depend.objid = pg_rewrite.oid\nJOIN   pg_class dependent_view ON pg_rewrite.ev_class = dependent_view.oid\nJOIN   pg_class source_table   ON pg_depend.refobjid  = source_table.oid\nJOIN   pg_namespace dependent_ns ON dependent_ns.oid = dependent_view.relnamespace\nWHERE  source_table.relname = 'users'\n  AND  dependent_view.relname \u003C> 'users';\n\n-- SQL Server\nSELECT referencing_entity_name\nFROM   sys.dm_sql_referencing_entities('dbo.users', 'OBJECT');\n```\n\n**Rule of thumb:** always check view dependencies before dropping or\naltering a table. In Postgres, `DROP TABLE … CASCADE` will silently drop\nall dependent views — use it only when that is intentional.\n",{"id":179,"difficulty":36,"q":180,"a":181},"view-schemabinding","What does SCHEMABINDING do on a view in SQL Server?","`WITH SCHEMABINDING` binds the view to the schema of the referenced base\ntables. While bound, the database prevents any change to the base tables\nthat would break the view — you cannot `DROP` a referenced column or the\ntable itself without first dropping or unbinding the view.\n\n```sql\n-- SQL Server: create a schema-bound view\nCREATE VIEW dbo.product_summary\nWITH SCHEMABINDING AS\n  SELECT p.id,\n         p.name,\n         c.name AS category\n  FROM   dbo.products p          -- must use two-part names (schema.table)\n  JOIN   dbo.categories c ON c.id = p.category_id;\n\n-- Attempt to drop a referenced column will now fail:\n-- ALTER TABLE dbo.products DROP COLUMN name;\n-- ERROR: cannot drop column because it is referenced by object 'product_summary'\n```\n\n`SCHEMABINDING` is also a **prerequisite** for creating an indexed view\n(materialized view) in SQL Server — without it, the clustered index\ncreation will be rejected.\n\n**Rule of thumb:** use `WITH SCHEMABINDING` on production views in SQL\nServer to prevent accidental schema changes from silently breaking them,\nand always use it when you plan to add a clustered index to the view.\n",{"description":33},"SQL views interview questions — creating and replacing views, updatable views, materialized views, security uses, WITH CHECK OPTION, and views vs CTEs across Postgres, MySQL, and SQL Server.","sql\u002Fdml\u002Fviews","4EK1oP4CMIjb70HVC-sAiNvcBsBI4ZONVgWiuz2oQ-k",1782244097987]