Views
Create reusable virtual tables with views to simplify complex queries.
Introduction to Views
You've written complex queries with JOINs, subqueries, and aggregations. But imagine writing the same complex query over and over - tedious and error-prone! What if you could save that query and reuse it like a table?
That's exactly what views do! A view is a saved query that acts like a virtual table. You query it just like a regular table, but behind the scenes it runs your saved query.
A virtual table based on a saved SELECT query. It doesn't store data itself - it dynamically executes the underlying query whenever you access the view. Views simplify complex queries, improve security, and maintain consistent business logic.
Why use views?
- Simplify complexity: Turn complex queries into simple SELECT statements
- Reusability: Write once, use many times
- Security: Hide sensitive columns from certain users
- Consistency: Ensure everyone uses the same business logic
- Abstraction: Hide database structure changes from applications
Think of a view as a "named query" or a "query shortcut."
Creating Your First View
Let's work with our familiar tables:
id | name | dept_id | salary | hire_date |
|---|---|---|---|---|
| 1 | Alice Smith | 10 | 95000 | 2020-01-15 |
| 2 | Bob Johnson | 20 | 75000 | 2021-03-20 |
| 3 | Carol White | 10 | 105000 | 2019-06-10 |
| 4 | David Brown | 30 | 65000 | 2022-01-05 |
id | dept_name | location |
|---|---|---|
| 10 | Engineering | New York |
| 20 | Marketing | London |
| 30 | Sales | Chicago |
Syntax:
CREATE VIEW view_name AS
SELECT ...
FROM ...
WHERE ...;
Let's create a simple view:
-- Create a view of employee names and departments
CREATE VIEW employee_departments AS
SELECT
e.id,
e.name,
d.dept_name,
d.location
FROM employees e
INNER JOIN departments d
ON e.dept_id = d.id;Now use it like a regular table:
-- Query the view
SELECT * FROM employee_departments;What happened:
- We saved a complex JOIN query as a view
- Now we can
SELECT * FROM employee_departmentsinstead of writing the JOIN every time - The view doesn't store data - it runs the JOIN query when accessed
Benefits of Views - Simplifying Complexity
Without views, you'd write this every time:
-- Without views: complex query every time
SELECT
e.name,
d.dept_name,
d.location
FROM employees e
INNER JOIN departments d
ON e.dept_id = d.id
WHERE d.location = 'New York';With views, it's simple:
-- With views: clean and simple
SELECT name, dept_name, location
FROM employee_departments
WHERE location = 'New York';Views with Calculations and Aggregations
Views can contain any SELECT query - including calculations:
-- View with salary calculations
CREATE VIEW employee_summary AS
SELECT
e.id,
e.name,
e.salary,
d.dept_name,
TIMESTAMPDIFF(YEAR, e.hire_date, CURRENT_DATE) AS years_employed,
CASE
WHEN e.salary >= 100000 THEN 'High'
WHEN e.salary >= 75000 THEN 'Medium'
ELSE 'Entry'
END AS salary_tier
FROM employees e
INNER JOIN departments d
ON e.dept_id = d.id;SELECT * FROM employee_summary;Now all users get consistent salary tier calculations!
Views with Aggregations
Create summary views with GROUP BY:
-- Department summary view
CREATE VIEW dept_stats AS
SELECT
d.dept_name,
d.location,
COUNT(e.id) AS employee_count,
AVG(e.salary) AS avg_salary,
MIN(e.salary) AS min_salary,
MAX(e.salary) AS max_salary
FROM departments d
LEFT JOIN employees e
ON d.id = e.dept_id
GROUP BY d.id, d.dept_name, d.location;SELECT * FROM dept_stats
ORDER BY employee_count DESC;This summary is now available to everyone without rewriting the aggregation!
Querying Views with Additional Filters
Views can be filtered, sorted, and combined just like tables:
-- Filter view results
SELECT *
FROM employee_summary
WHERE salary_tier = 'High'
AND years_employed > 4;-- Sort view results
SELECT name, dept_name, salary
FROM employee_summary
ORDER BY salary DESC
LIMIT 2;Views for Security - Hiding Sensitive Data
Views can hide sensitive columns from certain users:
-- Public employee directory (no salary info)
CREATE VIEW public_employee_directory AS
SELECT
e.name,
d.dept_name,
d.location,
e.hire_date
FROM employees e
INNER JOIN departments d
ON e.dept_id = d.id;SELECT * FROM public_employee_directory;Security benefit: Grant regular users access to public_employee_directory instead of the employees table. They can see names and departments but not salaries!
Viewing Existing Views
See all views in your database:
-- MySQL/MariaDB
SHOW FULL TABLES WHERE Table_type = 'VIEW';PostgreSQL:
\dv
Show view definition:
SHOW CREATE VIEW employee_departments;
Dropping Views
Remove a view when no longer needed:
DROP VIEW IF EXISTS employee_departments;Important: Dropping a view only removes the saved query definition. It does NOT delete any data from the underlying tables. The original tables remain unchanged.
Updating Views - CREATE OR REPLACE
Modify an existing view:
-- Update view definition
CREATE OR REPLACE VIEW employee_departments AS
SELECT
e.id,
e.name,
d.dept_name,
d.location,
e.salary -- Added salary column
FROM employees e
INNER JOIN departments d
ON e.dept_id = d.id;Alternative approach:
DROP VIEW IF EXISTS employee_departments;
CREATE VIEW employee_departments AS ...
Practical Example: Sales Dashboard View
Create a comprehensive reporting view:
order_id | customer_id | order_date | total | status |
|---|---|---|---|---|
| 1 | 1 | 2024-01-15 | 500.00 | completed |
| 2 | 2 | 2024-02-20 | 350.00 | completed |
| 3 | 1 | 2024-03-10 | 750.00 | pending |
customer_id | name | city | country |
|---|---|---|---|
| 1 | Alice Corp | New York | USA |
| 2 | Bob Inc | London | UK |
-- Sales dashboard view
CREATE VIEW sales_dashboard AS
SELECT
o.order_id,
c.name AS customer,
c.city,
c.country,
o.order_date,
o.total,
o.status,
YEAR(o.order_date) AS year,
QUARTER(o.order_date) AS quarter,
MONTHNAME(o.order_date) AS month,
CASE
WHEN o.total >= 500 THEN 'Large'
WHEN o.total >= 200 THEN 'Medium'
ELSE 'Small'
END AS order_size,
DATEDIFF(CURRENT_DATE, o.order_date) AS days_ago
FROM orders o
INNER JOIN customers c
ON o.customer_id = c.customer_id;-- Use the dashboard view for reports
SELECT
customer,
COUNT(*) AS order_count,
SUM(total) AS total_revenue,
AVG(total) AS avg_order_value
FROM sales_dashboard
WHERE status = 'completed'
GROUP BY customer
ORDER BY total_revenue DESC;Views vs Tables - Key Differences
Aspect | View | Table |
|---|---|---|
| Stores data? | No (virtual) | Yes (physical) |
| Query execution | Runs underlying query each time | Direct data access |
| Disk space | Minimal (just query definition) | Stores all data |
| Data freshness | Always current | Can become outdated |
| Performance | Depends on underlying query | Generally faster |
| Can update data? | Sometimes (simple views only) | Yes |
Common Mistakes to Avoid
Don't make these mistakes:
-
Selecting * in views: ❌
CREATE VIEW my_view AS SELECT * FROM table- If table structure changes, view might break
- Better: explicitly list columns
-
Complex views with poor performance: ⚠️
- Views with multiple JOINs and subqueries can be slow
- Consider materialized views or indexed views for performance
-
Circular view references: ❌
- View A references View B, View B references View A
- Creates infinite loop - will fail
-
Forgetting view dependencies: ❌
- Dropping a table breaks views that use it
- Check dependencies before dropping tables
-
Assuming views improve performance: ❌
- Views don't cache results (regular views)
- They just save typing, not execution time
-
Updating data through views: ⚠️
- Only simple views (single table, no aggregations) allow INSERT/UPDATE
- Most complex views are read-only
-
Not using OR REPLACE: ❌
- Without OR REPLACE, you must DROP VIEW first
- Can lose permissions if you drop and recreate
Key Takeaways
What you learned:
✅ View = saved SELECT query that acts like a virtual table
✅ CREATE VIEW name AS SELECT ... - creates a view
✅ Views don't store data - they run the query each time
✅ Benefits: simplify complexity, reusability, security, consistency
✅ Query views like tables: SELECT * FROM view_name
✅ Views can have JOINs, calculations, aggregations, filters
✅ CREATE OR REPLACE VIEW - update view definition
✅ DROP VIEW - delete view (doesn't affect underlying tables)
✅ Views hide sensitive columns for security
✅ Use views for complex queries you run frequently
Real-world applications:
- Reporting dashboards
- User permission management
- Simplifying complex business logic
- Data abstraction layers
- Consistent calculated fields
Practice Exercise: Create these views:
high_earners- employees with salary > 90000, include name and departmentrecent_hires- employees hired in the last yeardept_summary- department name, employee count, average salaryemployee_full_info- combine employees with departments, calculate tenure in yearstop_spenders- customers with their total order value > 1000