A CTE, covered earlier in this module, is a named query that only exists for one statement. A view is the permanent version of that idea: a query saved in the database under a name, that can be queried again and again afterward, in any number of separate statements, exactly like a regular table.
Learning Objectives
- Create a view with CREATE VIEW, and query it like a table.
- Explain what makes a view different from an actual table.
- Know when a view can be updated directly, and when it can't.
CREATE VIEW
A query, saved under a name
Stored in the Schema
Alongside real tables
Queried Like a Table
SELECT * FROM view_name
Always Current
Runs the underlying query fresh each time
Creating and Using a View
Once created, high_earners behaves like a table in every query that follows, you can SELECT from it, filter it further with WHERE, join it to other tables. Under the hood, there's no actual copy of the data, every time the view is queried, the database re-runs the underlying SELECT fresh, so the view's results are always current.
CREATE VIEW high_earners AS SELECT first_name, department, salary FROM employees WHERE salary > 90000;
SELECT * FROM high_earners WHERE department = 'Engineering';
Why Use a View
- Hide a complicated join or calculation behind a simple name, so other queries (or less experienced teammates) don't need to repeat it.
- Give a restricted or simplified slice of a table to certain users, for example a view that leaves out a salary column entirely.
- Keep a frequently-used query consistent and defined in exactly one place, instead of copy-pasted across many application queries.
Updatable Views
A simple view, built from a single table with no aggregation, grouping, or DISTINCT, can usually be updated directly, an UPDATE or INSERT against the view actually modifies the underlying table. A complex view, one with a JOIN, GROUP BY, or an aggregate function, generally can't be, there's no unambiguous way to translate a change back to the original rows.
UPDATE high_earners SET department = 'Research' WHERE first_name = 'Ada'; -- Works, because high_earners is a simple, single-table view, -- this actually updates the underlying employees table.
All four databases support WITH CHECK OPTION on a view, which blocks an UPDATE or INSERT through the view from creating a row that the view's own WHERE clause wouldn't actually include, for example, stopping someone from using the high_earners view to set a salary below 90000.
Materialized Views
A regular view re-runs its query every single time it's queried, which can be slow if the underlying query is expensive. A materialized view instead actually stores the computed result physically, like a real table, and has to be explicitly refreshed to pick up new data. It trades perfect freshness for speed, useful for an expensive report or dashboard query that doesn't need to be up-to-the-second.
| Database | Materialized view support |
|---|---|
| PostgreSQL | Native, CREATE MATERIALIZED VIEW, manually refreshed with REFRESH MATERIALIZED VIEW |
| SQL Server | Indexed views, a view with a unique clustered index, kept automatically in sync |
| MySQL | No native support, commonly simulated with a real table populated on a schedule |
| SQLite | No native support, same manual-table workaround as MySQL |
Frequently Asked Questions
Does a view take up storage space?
A regular view itself barely does, it's just the saved query definition, not a copy of the data. A materialized view is different, it does store the actual computed result, taking up real space, since that's the whole point of it.
Can a view reference another view?
Yes, on every database covered in this course, though nesting views many layers deep can make performance and debugging harder to reason about, it's rarely worth going more than one or two levels.
How do I remove a view?
DROP VIEW IF EXISTS view_name; works identically across MySQL, PostgreSQL, SQLite, and SQL Server, the same IF EXISTS pattern covered for DROP TABLE earlier in this course.
Common Beginner Mistakes
Expecting a regular view to be fast on an expensive underlying query
A regular view is just a saved query, it re-runs the full underlying SELECT every time, with all the same cost that query would normally have. It doesn't cache anything on its own, a materialized view is the tool for that.
Trying to UPDATE through a view that joins multiple tables or uses GROUP BY
Most databases reject this, there's no single unambiguous way to translate a change on a joined or aggregated view back into the right underlying rows. Only simple, single-table views are reliably updatable.
Assuming MySQL or SQLite have materialized views, because other databases do
They don't, neither has native materialized view support. The common workaround is a real table that's populated and refreshed on a schedule, manually simulating what PostgreSQL and SQL Server offer natively.
Best Practices
- Use a view to hide a complex, frequently-repeated query behind a simple, readable name.
- Add WITH CHECK OPTION on an updatable view that's meant to enforce a filtering rule, so updates through it can't silently violate that rule.
- Reach for a materialized view (or a manually refreshed table, on MySQL/SQLite) only when a regular view's query is genuinely too slow to re-run on every read.
- Keep view nesting shallow, one or two levels at most, for the sake of anyone debugging a slow or confusing query later.
Interview Questions
What's the fundamental difference between a view and a table?
A table physically stores data. A view stores only a query definition, and re-executes that query, against the real underlying tables, every time it's selected from. A view has no data of its own, a materialized view is the exception, since it does store a physical, periodically refreshed copy.
Why can't you UPDATE through a view built with a GROUP BY?
A grouped view's rows are summaries derived from many underlying rows, there's no single, unambiguous underlying row for the database to apply the update to. Updatable views require a direct, traceable mapping back to individual rows in one underlying table, which an aggregated view doesn't have.
A dashboard query is too slow to run on every page load. What are two ways a view-related feature could help, and how do they differ?
A materialized view (native on PostgreSQL and SQL Server) stores the computed result and serves it instantly, refreshed on a schedule rather than live. On MySQL or SQLite, the equivalent is a real table populated by a scheduled job. Both trade perfect real-time freshness for speed, the difference is PostgreSQL/SQL Server manage the refresh natively, while MySQL/SQLite require manually built refresh logic.
Summary
This lesson covered creating and querying a view like a regular table, why a simple single-table view is usually updatable while a complex one generally isn't, WITH CHECK OPTION for enforcing a view's filter on writes, and materialized views as a way to trade real-time freshness for speed.
That completes Module 4, Advanced Queries: window functions, CTEs, set operations, and views, rounding out the intermediate-to-advanced SQL a working developer uses day to day.
What's Next?
More modules are planned for this course, check the course page for progress as new lessons go live.
Ready to Practice SQL?
Write and run real SQL queries online, right in your browser, no install or signup required. Pick a playground based on which database you want to practice on.
Both open a third-party SQL playground in a new tab. External site terms and privacy policy apply.