A view is a stored query that behaves like a table. You name a SELECT once, then read from that name anywhere — the query runs fresh each time, so a view always reflects the current base tables. A materialized view instead caches the result on disk, trading freshness for speed.
The seed is a small storefront: five customers (one marked inactive) and their orders.
SELECT * FROM customers ORDER BY id;
A view names a query
Say you keep filtering to active customers. Instead of repeating the WHERE clause everywhere, wrap the query in a view once:
CREATE VIEW active_customers AS
SELECT id, name, email, country
FROM customers
WHERE is_active;
Now query the view exactly like a table — no data was copied; active_customers is a virtual table backed by the query above:
SELECT name, country FROM active_customers ORDER BY name;
Margaret is gone because she is inactive. A view gives you three things at once: readability (a name for a gnarly query), a stable interface (callers depend on the view, not the underlying columns), and a cheap way to hide columns — notice the view never exposes is_active, and you could just as easily omit an email or a salary.
Views are always fresh
A view stores the query, not the rows, so it re-runs every time. Change a base table and the view reflects it instantly — no refresh step:
UPDATE customers SET is_active = false WHERE name = 'Linus Torvalds';
SELECT name, country FROM active_customers ORDER BY name;
Linus vanished the moment his row changed. Let's put him back so later steps line up:
UPDATE customers SET is_active = true WHERE name = 'Linus Torvalds';
Views join like tables
Because a view is just a query with a name, you can join it, filter it, and aggregate over it like any table. Here we join active_customers to orders:
SELECT c.name, count(o.id) AS orders
FROM active_customers c
JOIN orders o ON o.customer_id = c.id
GROUP BY c.name
ORDER BY c.name;