[{"data":1,"prerenderedAt":102},["ShallowReactive",2],{"qa-\u002Fsql\u002Fdml\u002Fviews":3},{"page":4,"siblings":93,"blog":99},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":12,"path":20,"questions":21,"questionsCount":84,"related":85,"seo":86,"seoDescription":87,"stem":88,"subtopic":6,"topic":89,"topicSlug":90,"updated":91,"__hash__":92},"qa\u002Fsql\u002Fdml\u002Fviews.md","Views",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","SQL","sql",{},true,"\u002Fsql\u002Fdml\u002Fviews",[22,27,31,35,39,43,47,51,55,59,64,68,72,76,80],{"id":23,"difficulty":24,"q":25,"a":26},"what-is-view","easy","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":28,"difficulty":24,"q":29,"a":30},"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":32,"difficulty":24,"q":33,"a":34},"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":36,"difficulty":14,"q":37,"a":38},"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":40,"difficulty":14,"q":41,"a":42},"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":44,"difficulty":14,"q":45,"a":46},"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":48,"difficulty":14,"q":49,"a":50},"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":52,"difficulty":14,"q":53,"a":54},"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":56,"difficulty":14,"q":57,"a":58},"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":60,"difficulty":61,"q":62,"a":63},"schema-changes-view","hard","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":65,"difficulty":61,"q":66,"a":67},"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":69,"difficulty":61,"q":70,"a":71},"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":73,"difficulty":61,"q":74,"a":75},"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":77,"difficulty":14,"q":78,"a":79},"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":81,"difficulty":14,"q":82,"a":83},"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",15,null,{"description":11},"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","Modifying Data","dml","2026-06-20","4EK1oP4CMIjb70HVC-sAiNvcBsBI4ZONVgWiuz2oQ-k",[94,98],{"subtopic":95,"path":96,"order":97},"INSERT, UPDATE & DELETE","\u002Fsql\u002Fdml\u002Finsert-update-delete",1,{"subtopic":6,"path":20,"order":12},{"path":100,"title":101},"\u002Fblog\u002Fsql-views-virtual-tables","SQL Views — Simplifying Queries, Controlling Access, and Materialized Views",1782244107247]