All 40 lessons

Lesson 35 of 40 · Advanced SQL

SQL Views

By Skillancy Editorial Team

Every example runs on the ShopKart dataset, already loaded in the free SQL Compiler. Click Try it in the SQL Compiler under a query to open it there, then press Run.

A view is a saved query that you can select from like a table. Instead of copying the same 15-line revenue query into every report, you save it once as a view and everyone queries SELECT * FROM order_revenue. A materialized view goes further and stores the result for speed.

Syntax

CREATE VIEW view_name AS
SELECT ...;

CREATE OR REPLACE VIEW view_name AS
SELECT ...;

DROP VIEW view_name;

Examples

1. Create a view

One row per order line with the business logic built in:

CREATE VIEW order_lines AS
SELECT o.order_id,
       o.order_date,
       o.status,
       c.customer_name,
       c.city,
       p.product_name,
       cat.category_name,
       oi.quantity,
       ROUND(oi.quantity * oi.unit_price * (1 - oi.discount_pct / 100), 2) AS net_amount
FROM orders o
JOIN customers c    ON c.customer_id = o.customer_id
JOIN order_items oi ON oi.order_id = o.order_id
JOIN products p     ON p.product_id = oi.product_id
JOIN categories cat ON cat.category_id = p.category_id;
Try it in the SQL Compiler

Result:

Query OK, 0 row(s) affected.

2. Query the view like a table

SELECT customer_name, product_name, net_amount
FROM order_lines
WHERE status = 'Delivered'
ORDER BY net_amount DESC
LIMIT 4;

Result:

customer_nameproduct_namenet_amount
Rohan GuptaSamsung 43" Smart TV27191.50
Farhan KhanRedmi Note 13 Pro22799.05
Aarav SharmaRedmi Note 13 Pro21599.10
Priya NairWomen's Silk Saree8074.05

(4 rows)

Revenue by city, now a two-line query:

SELECT city, SUM(net_amount) AS revenue
FROM order_lines
WHERE status = 'Delivered'
GROUP BY city
ORDER BY revenue DESC
LIMIT 3;

Result:

cityrevenue
Delhi33183.50
Bengaluru28591.30
Lucknow24098.05

(3 rows)

3. Views are always current

A view re-runs its query each time, so new data shows up immediately:

UPDATE orders SET status = 'Delivered', delivery_date = '2026-02-14' WHERE order_id = 1024;

SELECT order_id, status, product_name
FROM order_lines
WHERE order_id = 1024;

Result:

order_idstatusproduct_name
1024DeliveredMen's Cotton Kurta
1024DeliveredNon-stick Tawa

(2 rows)

4. Replace a view

Add a column with CREATE OR REPLACE VIEW. In PostgreSQL, new columns must go at the end:

CREATE OR REPLACE VIEW order_lines AS
SELECT o.order_id, o.order_date, o.status, c.customer_name, c.city,
       p.product_name, cat.category_name, oi.quantity,
       ROUND(oi.quantity * oi.unit_price * (1 - oi.discount_pct / 100), 2) AS net_amount,
       o.payment_method
FROM orders o
JOIN customers c    ON c.customer_id = o.customer_id
JOIN order_items oi ON oi.order_id = o.order_id
JOIN products p     ON p.product_id = oi.product_id
JOIN categories cat ON cat.category_id = p.category_id;

SELECT payment_method, COUNT(*) AS lines FROM order_lines GROUP BY payment_method ORDER BY lines DESC;
Try it in the SQL Compiler

Result:

payment_methodlines
UPI19
Card10
COD5
NetBanking4

(4 rows)

5. Views for access control

Share data without exposing sensitive columns. Here, the marketing team sees customers without phone or email:

CREATE VIEW customer_directory AS
SELECT customer_id, customer_name, city, state, is_prime
FROM customers;

SELECT * FROM customer_directory LIMIT 3;
Try it in the SQL Compiler

Result:

customer_idcustomer_namecitystateis_prime
1Aarav SharmaBengaluruKarnatakatrue
2Priya NairKochiKeralafalse
3Rohan GuptaDelhiDelhitrue

(3 rows)

You can then grant access to the view but not the table.

Materialized views

A materialized view runs the query once and stores the rows. Reads are fast, but data only updates when you refresh it.

CREATE MATERIALIZED VIEW monthly_revenue AS
SELECT DATE_TRUNC('month', order_date)::date AS month,
       SUM(net_amount) AS revenue
FROM order_lines
WHERE status = 'Delivered'
GROUP BY 1;

SELECT * FROM monthly_revenue WHERE month >= '2026-01-01' ORDER BY month;

Result:

monthrevenue
2026-02-012703.05

(1 row)

Order 1024 is in February 2026. Now another order is delivered:

UPDATE orders SET status = 'Delivered', delivery_date = '2026-01-30' WHERE order_id = 1023;

SELECT * FROM monthly_revenue WHERE month >= '2026-01-01' ORDER BY month;

Result:

monthrevenue
2026-02-012703.05

(1 row)

January 2026 still isn't there: the stored result is stale. Refresh it:

REFRESH MATERIALIZED VIEW monthly_revenue;

SELECT * FROM monthly_revenue WHERE month >= '2026-01-01' ORDER BY month;

Result:

monthrevenue
2026-01-012196.00
2026-02-012703.05

(2 rows)

ViewMaterialized viewTable
Stores datanoyesyes
Always currentyesafter REFRESHwhen you update it
Read speedsame as the queryfastfast
Typical usereusable logic, access controlheavy dashboardssource data

Common mistakes

  • Stacking views on views on views. Each layer hides logic and can make queries slow and hard to debug.
  • Forgetting to refresh materialized views. Schedule REFRESH (for example nightly) or reports go stale.
  • Using SELECT * in a view definition. The column list is fixed when the view is created; new table columns won't appear.

Practice question

Create a view product_performance showing each product's name, category name, units sold and net revenue (delivered orders only, including products with no sales). Query the top 3.

Show solution
CREATE VIEW product_performance AS
SELECT p.product_name,
       cat.category_name,
       COALESCE(SUM(oi.quantity) FILTER (WHERE o.status = 'Delivered'), 0) AS units_sold,
       COALESCE(SUM(oi.quantity * oi.unit_price * (1 - oi.discount_pct / 100))
                FILTER (WHERE o.status = 'Delivered'), 0) AS net_revenue
FROM products p
JOIN categories cat      ON cat.category_id = p.category_id
LEFT JOIN order_items oi ON oi.product_id = p.product_id
LEFT JOIN orders o       ON o.order_id = oi.order_id
GROUP BY p.product_name, cat.category_name;

SELECT product_name, units_sold, ROUND(net_revenue, 0) AS net_revenue
FROM product_performance
ORDER BY net_revenue DESC
LIMIT 3;
Try it in the SQL Compiler

Result:

product_nameunits_soldnet_revenue
Redmi Note 13 Pro244398
Samsung 43" Smart TV127192
Women's Silk Saree215723

(3 rows)

Key takeaways

  • A view is a saved query; selecting from it runs the query.
  • Views centralise business logic and can hide sensitive columns.
  • CREATE OR REPLACE VIEW updates a view's definition.
  • Materialized views store results for speed and need REFRESH.

Frequently asked questions

Does a view store data?

A normal view does not. It stores the query, which runs every time you select from the view, so results are always current. A materialized view stores the result and must be refreshed.

Are views slower than tables?

Selecting from a normal view costs the same as running its query. If the query is heavy and read often, a materialized view is faster, at the cost of slightly stale data.

Can I insert into a view?

Simple views over a single table without GROUP BY, DISTINCT or joins are updatable in PostgreSQL. Views with aggregates or joins are read-only unless you add INSTEAD OF triggers.

Newsletter

Keep learning SQL

One email a week with SQL and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.

Prefer a one-page summary? Download the free SQL cheat sheet (PDF)