29.1 CREATE VIEW: Encapsulating Complex Queries
Right, let’s talk about views. You’ve written a query. It’s a monster. It’s got four JOINs, three CASE statements, and a window function you’re particularly proud of. You need to use it in five different places. The thought of copying and pasting that behemoth makes your skin crawl—and it should. That’s how bugs are born. Enter the CREATE VIEW. Think of it as assigning a name to that query and saving it for later, like bookmarking a particularly complex thought.
The beauty of a view is its simplicity. You’re not storing data; you’re storing a query. Whenever you select from the view, the database engine behind the scenes goes, “Ah, right, that query,” and runs it on the fly against the current data. It’s a live projection. Change the data in the underlying tables, and the next time you query the view, you’ll see those changes. This makes views perfect for encapsulating complex logic, simplifying permissions (you can grant someone access to the view but not the underlying tables), and presenting a consistent, logical interface to applications.
Here’s the dead-simple syntax to bring one into the world:
CREATE VIEW customer_order_summary AS
SELECT
c.customer_id,
c.name,
c.email,
COUNT(o.order_id) AS total_orders,
SUM(o.amount) AS lifetime_value
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name, c.email;
Now, instead of that whole song and dance, any application or user with access can just run SELECT * FROM customer_order_summary WHERE lifetime_value > 1000;. Clean, right?
The Read-Only Reality (Mostly)
Here’s the first thing everyone trips over: can you INSERT/UPDATE/DELETE into a view? The answer is a firm “maybe, but probably not, and you likely shouldn’t.” The database must be able to unambiguously trace any modification back to a single underlying table. If your view has joins, aggregates, or DISTINCT clauses, it’s a hard no. Even on a simple view based on one table, if you have computed columns or omit a NOT NULL column without a default, it’ll fail. The rule of thumb is to treat views as read-only abstractions. The database designers made this tricky for a good reason: preventing you from making a complete hash of your data.
The Performance Trap Nobody Mentions
Remember when I said a view is just a saved query? I meant it. There’s zero performance benefit to using a view. None. Zip. If anything, you might hide a performance nightmare behind a deceptively simple name. SELECT * FROM that_nice_simple_view might be executing a seven-table join with a full table scan on each one. The optimizer still has to do all the work every single time. I’ve seen more than one production system grind to a halt because a developer thought a view was a magic performance fairy. It is not. It is an organizational tool.
Using the WITH CHECK OPTION Safeguard
This is a brilliantly useful feature that most people don’t know about. Let’s say you create a view to show only active users:
CREATE VIEW active_users AS
SELECT user_id, name, email
FROM users
WHERE is_active = true;
What happens if someone inserts through this view? Without WITH CHECK OPTION, they could insert a user with is_active = false. The insert would succeed on the underlying table, but the new row would immediately be invisible to the view that was just used to create it. It’s absurd. It’s a logical nightmare waiting to happen.
CREATE VIEW active_users AS
SELECT user_id, name, email
FROM users
WHERE is_active = true
WITH CHECK OPTION;
Now, the database will reject any INSERT or UPDATE through this view that would result in a row that doesn’t satisfy the view’s own WHERE clause. It enforces consistency. Use it. It’s like a bouncer for your view’s logic.
The ORDER BY That Doesn’t Belong
Here’s a classic rookie mistake that I’ve made myself:
CREATE VIEW my_sorted_view AS
SELECT * FROM products ORDER BY price DESC;
You create this, run SELECT * FROM my_sorted_view;, and lo and behold, it’s sorted! You high-five yourself. The next day, you run SELECT * FROM my_sorted_view WHERE category = 'books'; and the results are in some random order. What gives? The ORDER BY in the view definition is not a permanent fixture. It’s only used if you select from the view without any other ordering. The second you add your own WHERE or ORDER BY, the database tosses the view’s sort out the window. Sorting is a presentation-layer operation, and the SQL standard rightly argues it shouldn’t be locked into the view definition. If you need a sorted result, you must apply ORDER BY when you select from the view. Don’t pretend otherwise.