SQLab Hub
Lesson 31 · Views
Lesson 31 · Advanced

SQL Views: CREATE VIEW Syntax, Examples and Use Cases

A view is a saved query that you can use like a table. You write a SELECT once, give it a name with CREATE VIEW, and from then on SELECT * FROM the_view runs that query. A normal view stores no data; it always shows the current data of the tables underneath.

👁 Virtual tables Materialized views ▶ Runnable Playground examples 🧪 4 practice exercises 📝 40-question quiz ⏱ ~25 min

What Is a View?

Many reports need the same thing: each order with its customer's name and city. That takes a join every time. A view stores the join under a name:

SQL · run in a local database
CREATE VIEW order_summary AS
SELECT o.id AS order_id,
       o.order_date,
       u.first_name || ' ' || u.last_name AS customer,
       u.city,
       o.status,
       o.total
FROM orders o
JOIN users u ON u.id = o.user_id;

After that, anyone can write SELECT * FROM order_summary WHERE status = 'delivered' without knowing how the tables are joined. Querying the view runs the stored SELECT, so the result is always current. Here is what the view returns, produced by running its query directly:

SQLite · runs in the Playground
SELECT o.id AS order_id,
       o.order_date,
       u.first_name || ' ' || u.last_name AS customer,
       u.city,
       o.status,
       o.total
FROM orders o
JOIN users u ON u.id = o.user_id
ORDER BY o.id
LIMIT 6;

▶ Run this query in the SQL Playground

What order_summary would return (first rows)
order_idorder_datecustomercitystatustotal
12024-04-24Amanda MitchellFort Worthcancelled2638.11
22024-03-15Timothy CampbellColumbusdelivered2947
32023-02-15Sarah HernandezChicagodelivered683.24
42022-01-10Linda AllenSan Antonioprocessing3920.44
52023-06-16Robert LewisPhoenixdelivered1120.99
62024-02-16Michelle LeeColumbusshipped1327.01
✓ 6 rows · real output from the Playground database
The SQLabHub Playground only runs SELECT and WITH queries, so CREATE VIEW and DROP VIEW have to be tried in a local SQLite, PostgreSQL or MySQL database. The runnable examples on this page show the output of each view's query, which is exactly what selecting from the view would give.

CREATE, Replace and DROP a View

SQL · syntax
-- create
CREATE VIEW view_name AS
SELECT ...;

-- change the definition
CREATE OR REPLACE VIEW view_name AS SELECT ...;   -- PostgreSQL, MySQL, Oracle
ALTER VIEW view_name AS SELECT ...;               -- SQL Server, MySQL
-- SQLite: DROP VIEW, then CREATE VIEW again

-- remove
DROP VIEW view_name;
DROP VIEW IF EXISTS view_name;
StatementEffect on the data
CREATE VIEWNone. Only the query is saved
SELECT FROM viewRuns the saved query against the current tables
DROP VIEWRemoves the saved query. The tables and their data are untouched
DELETE FROM viewDeletes rows from the underlying table, if the view is updatable. It does not drop the view

What Views Are Used For

Hiding complex joins

A view over four tables turns "revenue by product category per month" into a simple query for everyone else:

SQL · run in a local database
CREATE VIEW category_monthly_sales AS
SELECT strftime('%Y-%m', o.order_date) AS month,
       p.category,
       SUM(oi.quantity) AS units,
       ROUND(SUM(oi.subtotal), 2) AS revenue
FROM order_items oi
JOIN orders o   ON o.id = oi.order_id
JOIN products p ON p.id = oi.product_id
GROUP BY month, p.category;
SQLite · runs in the Playground
SELECT strftime('%Y-%m', o.order_date) AS month,
       p.category,
       SUM(oi.quantity) AS units,
       ROUND(SUM(oi.subtotal), 2) AS revenue
FROM order_items oi
JOIN orders o   ON o.id = oi.order_id
JOIN products p ON p.id = oi.product_id
WHERE o.order_date >= '2024-12-01'
GROUP BY month, p.category
ORDER BY revenue DESC;

▶ Run this query in the SQL Playground

The view's query, filtered to December 2024
monthcategoryunitsrevenue
2024-12Office Supplies113650.33
2024-12Automotive132940.41
2024-12Clothing72364.79
2024-12Books82259.28
2024-12Food & Beverage6957.27
2024-12Health & Beauty4762.72
2024-12Home & Garden3512.88
✓ 7 rows · real output from the Playground database

Users of the view would write SELECT * FROM category_monthly_sales WHERE month = '2024-12'. Most databases push that filter into the view's query, so it is not slower than writing the full query by hand.

Restricting what people can see

A view can leave out sensitive columns, such as salary, and you can grant access to the view without granting access to the table:

SQL · run in a local database
CREATE VIEW employee_directory AS
SELECT first_name, last_name, email, department, title, city
FROM employees
WHERE status = 'active';

GRANT SELECT ON employee_directory TO reporting_role;   -- PostgreSQL, MySQL, SQL Server
SQLite · runs in the Playground
SELECT first_name, last_name, email, department, title, city
FROM employees
WHERE status = 'active'
ORDER BY last_name
LIMIT 5;

▶ Run this query in the SQL Playground

What employee_directory would show: no salary column, active staff only
first_namelast_nameemaildepartmenttitlecity
RebeccaAdamsradams68@corp.ioSalesChief Revenue OfficerPhoenix
StevenAdamssadams85@corp.ioProductGroup PMCharlotte
RobertAdamsradams42@corp.ioLegalLegal CounselPhiladelphia
CharlesAllencallen32@corp.ioCustomer SuccessCS DirectorHouston
JamesAllenjallen15@corp.ioMarketingMarketing CoordinatorAustin
✓ 5 rows · real output from the Playground database

SQLite has no user accounts or GRANT, so this use applies to server databases.

One definition of a business rule

If "revenue" means "orders that were not cancelled or refunded", a view can hold that rule so every report uses the same definition:

SQLite · runs in the Playground
SELECT COUNT(*) AS counted_orders,
       ROUND(SUM(total), 2) AS revenue
FROM orders
WHERE status NOT IN ('cancelled', 'refunded');

▶ Run this query in the SQL Playground

The query a revenue view would hold
counted_ordersrevenue
154394998.46
✓ 1 row · real output from the Playground database

View vs Table vs CTE vs Temporary Table

ViewTableCTETemporary tableMaterialized view
Stores dataNoYesNoYesYes
Lives forUntil droppedUntil droppedOne statementThe sessionUntil dropped
Always currentYesn/aYesNo, a snapshotNo, until refreshed
Can be indexedNoYesNoYesYes
Shared with other usersYesYesNoNoYes

A view is like a CTE that is saved in the database and can be used by any query and any user. A temporary table is a copy of data taken at one moment; a view is not.

Materialized Views

A materialized view stores the result of its query on disk. Reading it is as fast as reading a table, and it can be indexed, but the data is only as fresh as the last refresh:

SQL · PostgreSQL
-- PostgreSQL
CREATE MATERIALIZED VIEW category_revenue AS
SELECT p.category, SUM(oi.subtotal) AS revenue
FROM order_items oi
JOIN products p ON p.id = oi.product_id
GROUP BY p.category;

REFRESH MATERIALIZED VIEW category_revenue;
DatabaseMaterialized views
PostgreSQLCREATE MATERIALIZED VIEW, refreshed with REFRESH MATERIALIZED VIEW
OracleMaterialized views with scheduled or on-commit refresh
SQL ServerIndexed views: kept up to date automatically, with many restrictions
MySQL, SQLiteNot supported; use a table filled by a scheduled job

Use one when the query is expensive, read often, and a little staleness is acceptable, such as a dashboard updated every hour.

Updating Data Through a View

Simple views, over one table without GROUP BY, DISTINCT, aggregates or UNION, are usually updatable: INSERT, UPDATE and DELETE on the view change the underlying table.

SQL · PostgreSQL, MySQL, SQL Server, Oracle
CREATE VIEW pending_orders AS
SELECT id, user_id, status, total
FROM orders
WHERE status = 'pending'
WITH CHECK OPTION;

UPDATE pending_orders SET total = total * 0.9 WHERE id = 70;   -- changes orders
UPDATE pending_orders SET status = 'shipped' WHERE id = 70;    -- rejected by CHECK OPTION

WITH CHECK OPTION rejects changes that would move a row out of the view, here a status other than pending. SQLite views are read-only; an INSTEAD OF trigger can make them accept changes.

Views and Performance

A normal view is not faster than its query, because it is the query. The database merges the view definition into your statement and optimizes the whole thing. Things to watch:

SituationAdvice
Views built on other views, several levels deepHard to read and can produce poor plans; flatten them
SELECT * from a wide view when you need two columnsSelect only what you need; some joins can then be skipped
An expensive aggregate queried constantlyConsider a materialized view or a summary table
Slow view queriesIndex the base tables on the columns the view joins and filters on

Common View Mistakes

Five errors and how to avoid them.

1. Expecting a view to store a snapshot

❌ Results change with the tables
SQL
-- "the view shows last month's numbers"
CREATE VIEW month_end AS SELECT ... FROM orders;
✅ Use a table for a snapshot
SQL
CREATE TABLE month_end_2024_12 AS
SELECT ... FROM orders WHERE order_date < '2025-01-01';

A view re-runs its query every time. To freeze numbers, copy them into a table.

2. SELECT * in a view definition

❌ Column list frozen or broken
SQL
CREATE VIEW v AS SELECT * FROM employees;
✅ List the columns
SQL
CREATE VIEW v AS
SELECT id, first_name, last_name, department
FROM employees;

Many databases expand * when the view is created, so new table columns do not appear and dropped ones break the view. Explicit columns also stop sensitive ones leaking in.

3. Thinking DROP VIEW deletes data

❌ Only the definition goes
SQL
DROP VIEW order_summary;  -- "deletes the orders"
✅ Know what each statement does
SQL
-- DROP VIEW removes the saved query only;
-- DELETE FROM a_table removes rows

The tables underneath are not affected by dropping a view.

4. Deep stacks of views

❌ Hard to debug, slow plans
SQL
v_report -> v_sales -> v_orders_clean -> v_orders_base -> orders
✅ One or two levels
SQL
v_report -> orders (and the tables it joins)

Each layer hides logic from the next reader and can stop the optimizer from simplifying the query.

5. Updating through a view that cannot be updated

❌ Error
SQL
UPDATE category_monthly_sales
SET revenue = 0
WHERE category = 'Toys';
✅ Update the base table
SQL
UPDATE order_items SET ...
WHERE product_id IN (SELECT id FROM products WHERE category = 'Toys');

An aggregated value has no single source row to change.

Views Cheat Sheet

TaskStatement
CreateCREATE VIEW v AS SELECT ...;
UseSELECT * FROM v WHERE ...;
ReplaceCREATE OR REPLACE VIEW v AS ...; (SQL Server: ALTER VIEW)
DropDROP VIEW IF EXISTS v;
Stored resultCREATE MATERIALIZED VIEW mv AS ...; + REFRESH MATERIALIZED VIEW mv; (PostgreSQL)
Guard updates... WITH CHECK OPTION
List views (SQLite)SELECT name FROM sqlite_master WHERE type = 'view';

Practice Views Queries

4 exercises on the real Playground tables.

Write each query yourself first. Every solution has been run against the Playground database.

  1. 1Easy Write the SELECT for a view called active_catalog that lists name, category and price of products whose status is active. Show the first 6 by name. Table: products

    Show solution
    SQLite · runs in the Playground
    SELECT name, category, price
    FROM products
    WHERE status = 'active'
    ORDER BY name
    LIMIT 6;

    ▶ Run this query in the SQL Playground

    In a local database, CREATE VIEW active_catalog AS followed by this SELECT (without ORDER BY and LIMIT) would save it.

  2. 2Medium Write the query behind a customer_spend view: each customer's name, number of orders and total spent. Show the top 5. Table: orders, users

    Show solution
    SQLite · runs in the Playground
    SELECT u.id, u.first_name || ' ' || u.last_name AS customer,
           COUNT(o.id) AS orders,
           ROUND(SUM(o.total), 2) AS spent
    FROM users u
    JOIN orders o ON o.user_id = u.id
    GROUP BY u.id
    ORDER BY spent DESC
    LIMIT 5;

    ▶ Run this query in the SQL Playground

    An aggregated view like this is read-only: there is no single row to update.

  3. 3Medium Write the query for a view that shows headcount and average salary per department without exposing individual salaries. Table: employees

    Show solution
    SQLite · runs in the Playground
    SELECT department,
           COUNT(*) AS employees,
           ROUND(AVG(salary)) AS avg_salary
    FROM employees
    GROUP BY department
    ORDER BY department;

    ▶ Run this query in the SQL Playground

    Granting access to this view shares the averages but not anyone's pay.

  4. 4Medium Check how many views and tables the Playground database has. Table: sqlite_master

    Show solution
    SQLite · runs in the Playground
    SELECT type, COUNT(*) AS objects
    FROM sqlite_master
    WHERE type IN ('table', 'view')
    GROUP BY type;

    ▶ Run this query in the SQL Playground

    The Playground has tables only. In your own SQLite database, views you create appear here with type = 'view'.

📝 Quiz — Views

Test your understanding · 0/40 answered

Question 1 of 40

What is a view in SQL?

Question 2 of 40

Does a normal view store data?

Question 3 of 40

Which statement creates a view?

Question 4 of 40

How do you query a view?

Question 5 of 40

How do you change a view's definition?

Question 6 of 40

Which statement removes a view?

Question 7 of 40

Why are views called virtual tables?

Question 8 of 40

What is the main difference between a view and a table?

Question 9 of 40

What is the main difference between a view and a CTE?

Question 10 of 40

What is a materialized view?

Question 11 of 40

Can a view join several tables?

Question 12 of 40

Can a view contain aggregates?

Question 13 of 40

What is an updatable view?

Question 14 of 40

What usually makes a view not updatable?

Question 15 of 40

How do analysts mostly use views?

Question 16 of 40

How can a view help with security?

Question 17 of 40

What is the difference between a view and a subquery?

Question 18 of 40

What is the difference between a view and a temporary table?

Question 19 of 40

Can a view be built on another view?

Question 20 of 40

Does a view make a query faster?

Question 21 of 40

Which command changes an existing view without dropping it?

Question 22 of 40

What happens to the table data when you DROP a view?

Question 23 of 40

Which BI tools commonly read from views?

Question 24 of 40

Can a view use CASE WHEN?

Question 25 of 40

Can a view use a window function such as ROW_NUMBER()?

Question 26 of 40

Can a view definition contain a CTE?

Question 27 of 40

What is generally required for INSERT, UPDATE or DELETE through a view?

Question 28 of 40

Which is PostgreSQL materialized view syntax?

Question 29 of 40

How do you refresh a materialized view in PostgreSQL?

Question 30 of 40

Which database does not support materialized views natively?

Question 31 of 40

What is the safest way to share employee data without salaries?

Question 32 of 40

What is a nested view?

Question 33 of 40

Why does a normal view not guarantee speed?

Question 34 of 40

How should you think of a view in a schema design?

Question 35 of 40

Does DELETE FROM my_view drop the view?

Question 36 of 40

Why use a view instead of repeating a join in every report?

Question 37 of 40

What do you use for a view-like object that must read fast and may be slightly stale?

Question 38 of 40

Are views a common interview topic?

Question 39 of 40

What does WITH CHECK OPTION do?

Question 40 of 40

What is a view, in one sentence?

Views Interview Questions

Q1. What is the difference between a view and a materialized view?

A view stores only its query and runs it on every use, so it is always current. A materialized view stores the result, so it is fast to read and can be indexed, but it is only as fresh as its last refresh.

Q2. When can you update data through a view?

When the view is simple: one base table, no aggregates, GROUP BY, DISTINCT or UNION, so each view row maps to one table row. WITH CHECK OPTION stops changes that would move rows out of the view.

Q3. Do views improve performance?

Normal views do not; they are expanded into the query. Materialized views (or SQL Server indexed views) can, by storing precomputed results.

Q4. How do views help with security?

You can grant access to a view that exposes only certain columns or rows, without granting access to the underlying table.

Q5. What problems do deeply nested views cause?

Logic is spread over many layers, so the query is hard to read and debug, and the optimizer may not simplify the combined query well.

Frequently Asked Questions

What is a view in SQL?

A view is a virtual table: a named, saved SQL query that returns its result when you select from it. A normal view stores no data; it reads the current data of its tables each time.

Why do we use views?

To hide repeated join logic behind a simple name, to give everyone the same definition of a business figure, and to expose only selected columns or rows to people who should not see the whole table.

What is a virtual table?

Another name for a view. It behaves like a table when you query it, with SELECT, WHERE, JOIN and aggregates, but stores no data of its own.

What is the difference between a view and a table?

A table stores data. A view stores only a query; its rows come from the tables it reads, at the moment you query it.

What is the difference between a view and a CTE?

A view is saved in the database and can be used by any query and any user until it is dropped. A CTE (WITH clause) exists only inside the single statement that defines it.

What is a materialized view?

A view whose result is stored on disk. It reads like a table and can be indexed, but it must be refreshed to show new data. PostgreSQL and Oracle support them; MySQL and SQLite do not.

Can views improve security?

Yes. A view can expose only non-sensitive columns and chosen rows, and access can be granted on the view rather than on the underlying table.

Are views important in SQL interviews?

Yes. View versus table, versus CTE and versus materialized view, updatable views and using views for security are common interview questions.