SQL 视图
视图(View)是一种虚拟表,它基于 SQL 语句的结果集。本章将详细介绍视图的创建、使用和最佳实践。
什么是视图?
视图是一个 SELECT 语句的命名结果集,它不存储实际数据,每次访问时动态生成。
为什么使用视图?
-- 假设我们经常需要查询用户的订单统计
-- 不使用视图:每次都要写复杂的 SQL
SELECT
u.id,
u.name,
u.email,
COUNT(o.id) AS order_count,
COALESCE(SUM(o.total), 0) AS total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name, u.email;
-- 使用视图:简化查询
CREATE VIEW user_order_stats AS
SELECT
u.id,
u.name,
u.email,
COUNT(o.id) AS order_count,
COALESCE(SUM(o.total), 0) AS total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name, u.email;
-- 查询时就像使用普通表一样
SELECT * FROM user_order_stats WHERE order_count > 5;
视图的优点
- 简化复杂查询:将复杂查询封装为视图
- 数据安全:限制用户访问特定列或行
- 逻辑独立:修改底层表结构时,视图可以保持不变
- 数据一致性:确保不同用户看到一致的数据格式
视图的缺点
- 性能开销:每次访问都要执行查询
- 更新限制:不是所有视图都支持更新
- 调试困难:复杂视图可能难以调试
创建视图
基本语法
CREATE VIEW view_name AS
SELECT column1, column2, ...
FROM table_name
WHERE condition;
简单视图
-- 创建员工基本信息视图
CREATE VIEW employee_basic AS
SELECT
id,
name,
department,
position
FROM employees;
-- 使用视图
SELECT * FROM employee_basic WHERE department = '技术部';
复杂视图
-- 创建销售统计视图
CREATE VIEW sales_statistics AS
SELECT
p.id AS product_id,
p.name AS product_name,
c.name AS category_name,
COUNT(oi.id) AS order_count,
SUM(oi.quantity) AS total_quantity,
SUM(oi.quantity * oi.price) AS total_revenue
FROM products p
JOIN categories c ON p.category_id = c.id
LEFT JOIN order_items oi ON p.id = oi.product_id
LEFT JOIN orders o ON oi.order_id = o.id AND o.status = 'completed'
GROUP BY p.id, p.name, c.name;
-- 使用视图查询热门商品
SELECT * FROM sales_statistics
ORDER BY total_revenue DESC
LIMIT 10;
带条件的视图
-- 创建活跃用户视图
CREATE VIEW active_users AS
SELECT
id,
name,
email,
last_login_at
FROM users
WHERE
status = 'active'
AND last_login_at > DATE_SUB(NOW(), INTERVAL 30 DAY);
-- 只有活跃用户会被包含
SELECT * FROM active_users;
带计算列的视图
-- 创建产品利润视图
CREATE VIEW product_profit AS
SELECT
id,
name,
cost_price,
selling_price,
selling_price - cost_price AS profit,
ROUND((selling_price - cost_price) / cost_price * 100, 2) AS profit_margin
FROM products;
-- 查询高利润商品
SELECT * FROM product_profit WHERE profit_margin > 30;
查看视图
查看视图定义
-- 查看视图创建语句
SHOW CREATE VIEW view_name;
-- 从信息模式查询
SELECT
TABLE_NAME AS view_name,
VIEW_DEFINITION
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_SCHEMA = 'your_database';
查看数据库中的视图
-- 查看所有表和视图
SHOW FULL TABLES;
-- 只查看视图
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'VIEW'
AND TABLE_SCHEMA = 'your_database';
修改视图
使用 CREATE OR REPLACE
-- 如果视图存在则替换,不存在则创建
CREATE OR REPLACE VIEW employee_basic AS
SELECT
id,
name,
department,
position,
hire_date -- 新增列
FROM employees;
使用 ALTER VIEW
-- 修改视图定义
ALTER VIEW employee_basic AS
SELECT
id,
name,
department,
position,
hire_date,
salary -- 新增列
FROM employees;
删除视图
-- 删除视图
DROP VIEW IF EXISTS view_name;
-- 删除多个视图
DROP VIEW IF EXISTS view1, view2, view3;
可更新视图
不是所有视图都支持 INSERT、UPDATE、DELETE 操作。
可更新视图的条件
视图必须满足以下条件才是可更新的:
- 没有 DISTINCT、GROUP BY、HAVING
- 没有 UNION、INTERSECT、EXCEPT
- 没有聚合函数(SUM、COUNT、AVG 等)
- 没有子查询(某些情况下允许)
- 没有不可更新的视图嵌套
更新视图示例
-- 创建可更新视图
CREATE VIEW active_employees AS
SELECT
id,
name,
department,
salary
FROM employees
WHERE status = 'active';
-- 通过视图更新数据
UPDATE active_employees
SET salary = salary * 1.1
WHERE department = '技术部';
-- 通过视图插入数据
INSERT INTO active_employees (name, department, salary)
VALUES ('新员工', '技术部', 8000);
-- 通过视图删除数据
DELETE FROM active_employees WHERE id = 100;
WITH CHECK OPTION
使用 WITH CHECK OPTION 确保通过视图修改的数据仍然满足视图条件:
-- 创建带检查选项的视图
CREATE VIEW high_salary_employees AS
SELECT
id,
name,
department,
salary
FROM employees
WHERE salary > 10000
WITH CHECK OPTION;
-- 尝试更新为不满足条件的值
UPDATE high_salary_employees
SET salary = 8000
WHERE id = 1;
-- 错误!因为更新后 salary <= 10000,不满足视图条件
-- 尝试插入不满足条件的行
INSERT INTO high_salary_employees (name, department, salary)
VALUES ('测试', '技术部', 5000);
-- 错误!salary <= 10000 不满足视图条件
CASCADE 和 LOCAL 检查
-- CASCADE:检查所有底层视图的条件(默认)
CREATE VIEW high_tech_employees AS
SELECT * FROM high_salary_employees
WHERE department = '技术部'
WITH CASCADED CHECK OPTION;
-- LOCAL:只检查当前视图的条件
CREATE VIEW high_tech_employees_local AS
SELECT * FROM high_salary_employees
WHERE department = '技术部'
WITH LOCAL CHECK OPTION;
视图实战案例
案例 1:数据安全视图
限制用户只能看到自己的数据:
-- 创建用户专属视图
CREATE VIEW my_orders AS
SELECT
id,
total,
status,
created_at
FROM orders
WHERE user_id = CURRENT_USER_ID();
-- 授权给普通用户
GRANT SELECT ON my_orders TO 'app_user'@'%';
-- 不授权访问原始 orders 表
案例 2:数据脱敏视图
隐藏敏感信息:
-- 创建脱敏视图
CREATE VIEW user_public_info AS
SELECT
id,
name,
CONCAT(LEFT(email, 3), '***@', SUBSTRING(email, INSTR(email, '@') + 1)) AS masked_email,
CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS masked_phone,
created_at
FROM users;
-- 非管理员用户只能访问脱敏视图
GRANT SELECT ON user_public_info TO 'analyst'@'%';
案例 3:报表视图
简化报表查询:
-- 月度销售报表视图
CREATE VIEW monthly_sales_report AS
SELECT
DATE_FORMAT(order_date, '%Y-%m') AS month,
COUNT(*) AS order_count,
SUM(total) AS revenue,
AVG(total) AS avg_order_value,
COUNT(DISTINCT user_id) AS unique_customers
FROM orders
WHERE status = 'completed'
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY month DESC;
-- 查询最近 12 个月的报表
SELECT * FROM monthly_sales_report LIMIT 12;
案例 4:层级数据视图
处理组织架构等层级数据:
-- 部门层级视图
CREATE VIEW department_hierarchy AS
WITH RECURSIVE dept_tree AS (
-- 基础查询:顶级部门
SELECT
id,
name,
parent_id,
CAST(name AS CHAR(1000)) AS path,
1 AS level
FROM departments
WHERE parent_id IS NULL
UNION ALL
-- 递归查询:子部门
SELECT
d.id,
d.name,
d.parent_id,
CONCAT(dt.path, ' > ', d.name),
dt.level + 1
FROM departments d
JOIN dept_tree dt ON d.parent_id = dt.id
)
SELECT * FROM dept_tree;
-- 查询部门层级
SELECT * FROM department_hierarchy WHERE name LIKE '%技术%';
案例 5:历史数据视图
追踪数据变更:
-- 创建产品价格历史视图
CREATE VIEW product_price_history AS
SELECT
p.id,
p.name,
ph.old_price,
ph.new_price,
ph.changed_at,
ph.changed_by
FROM products p
JOIN product_history ph ON p.id = ph.product_id
WHERE ph.field_name = 'price'
ORDER BY ph.changed_at DESC;
-- 查看产品价格变化
SELECT * FROM product_price_history WHERE id = 100;
视图性能优化
视图与性能
视图本身不存储数据,每次访问都会执行底层查询:
-- 这个视图每次访问都会执行复杂的聚合
CREATE VIEW complex_stats AS
SELECT
user_id,
COUNT(*) AS order_count,
SUM(total) AS total_spent,
AVG(total) AS avg_order,
MAX(created_at) AS last_order_date
FROM orders
GROUP BY user_id;
-- 多次访问会多次执行
SELECT * FROM complex_stats WHERE user_id = 1;
SELECT * FROM complex_stats WHERE user_id = 2;
优化建议
1. 使用索引
确保底层表的查询字段有索引:
-- 视图查询使用了 user_id 和 created_at
CREATE VIEW recent_orders AS
SELECT * FROM orders
WHERE created_at > DATE_SUB(NOW(), INTERVAL 7 DAY);
-- 确保有对应的索引
CREATE INDEX idx_created_at ON orders(created_at);
2. 避免嵌套视图
-- 不好:嵌套视图
CREATE VIEW view1 AS SELECT * FROM users WHERE status = 'active';
CREATE VIEW view2 AS SELECT * FROM view1 WHERE department = '技术部';
CREATE VIEW view3 AS SELECT * FROM view2 WHERE salary > 10000;
-- 好:直接定义
CREATE VIEW high_salary_tech_users AS
SELECT * FROM users
WHERE status = 'active'
AND department = '技术部'
AND salary > 10000;
3. 使用物化视图(MySQL 8.0+)
MySQL 原生不支持物化视图,但可以使用表模拟:
-- 创建汇总表
CREATE TABLE order_summary (
user_id INT PRIMARY KEY,
order_count INT,
total_spent DECIMAL(12, 2),
last_updated TIMESTAMP
);
-- 定时更新汇总表(使用事件或定时任务)
INSERT INTO order_summary (user_id, order_count, total_spent, last_updated)
SELECT
user_id,
COUNT(*),
SUM(total),
NOW()
FROM orders
GROUP BY user_id
ON DUPLICATE KEY UPDATE
order_count = VALUES(order_count),
total_spent = VALUES(total_spent),
last_updated = NOW();
-- 使用汇总表代替视图
SELECT * FROM order_summary WHERE user_id = 1;
视图最佳实践
1. 命名规范
-- 好的命名:清晰表明视图用途
CREATE VIEW view_active_users AS ...
CREATE VIEW view_order_statistics AS ...
CREATE VIEW view_product_sales AS ...
-- 或使用后缀
CREATE VIEW active_users_v AS ...
CREATE VIEW order_stats_v AS ...
2. 添加注释
-- 创建视图时添加注释
CREATE VIEW user_order_summary AS
SELECT
u.id AS user_id,
u.name,
COUNT(o.id) AS total_orders,
COALESCE(SUM(o.total), 0) AS total_amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
WITH CHECK OPTION;
-- 通过视图属性添加说明
ALTER VIEW user_order_summary
COMMENT '用户订单汇总视图,包含订单数量和总金额';
3. 限制视图复杂度
-- 避免:过度复杂的视图
CREATE VIEW overly_complex AS
SELECT ... FROM (
SELECT ... FROM (
SELECT ... FROM (
-- 多层嵌套
)
)
);
-- 推荐:拆分为多个简单视图或使用 CTE
4. 权限管理
-- 创建只读视图并授权
CREATE VIEW customer_info AS
SELECT id, name, email, created_at FROM customers;
-- 授予只读权限
GRANT SELECT ON customer_info TO 'app_readonly'@'%';
-- 撤销对原表的直接访问
REVOKE ALL ON customers FROM 'app_readonly'@'%';
小结
本章我们学习了:
- 视图概念:虚拟表,基于 SELECT 语句的结果集
- 创建视图:CREATE VIEW、CREATE OR REPLACE VIEW
- 可更新视图:通过视图修改数据的条件和限制
- WITH CHECK OPTION:确保修改后的数据满足视图条件
- 视图应用:数据安全、数据脱敏、报表查询
- 性能优化:索引、避免嵌套、物化视图替代
练习
- 创建一个视图,显示每个分类的商品数量和平均价格
- 创建一个可更新视图,并测试 WITH CHECK OPTION 的效果
- 设计一个数据脱敏视图,隐藏用户的敏感信息
- 使用视图实现一个简单的权限控制系统