Skip to content

视图

视图(View)是一种虚拟表,它基于SQL查询语句的结果集。视图不存储实际数据,每次查询视图时都会执行其定义的SQL语句。视图可以简化复杂查询、提供数据安全性和逻辑独立性。

创建视图

基本语法

sql
-- 创建视图的基本语法
CREATE VIEW 视图名 AS
SELECT 语句;

-- 创建示例表
CREATE TABLE departments (
    dept_id SERIAL PRIMARY KEY,
    dept_name VARCHAR(50) NOT NULL,
    location VARCHAR(100)
);

CREATE TABLE employees (
    emp_id SERIAL PRIMARY KEY,
    emp_name VARCHAR(50) NOT NULL,
    dept_id INTEGER REFERENCES departments(dept_id),
    salary NUMERIC(10, 2),
    hire_date DATE,
    status VARCHAR(20) DEFAULT 'active'
);

INSERT INTO departments (dept_name, location) VALUES
    ('技术部', '北京'),
    ('销售部', '上海'),
    ('人事部', '广州');

INSERT INTO employees (emp_name, dept_id, salary, hire_date, status) VALUES
    ('张三', 1, 20000, '2020-01-15', 'active'),
    ('李四', 1, 18000, '2021-03-20', 'active'),
    ('王五', 1, 16000, '2022-06-10', 'active'),
    ('赵六', 2, 15000, '2021-01-01', 'active'),
    ('钱七', 2, 12000, '2022-08-15', 'inactive'),
    ('孙八', 3, 14000, '2021-05-20', 'active');

-- 创建简单视图
CREATE VIEW active_employees AS
SELECT emp_id, emp_name, salary
FROM employees
WHERE status = 'active';

-- 查询视图
SELECT * FROM active_employees;

-- 如果视图已存在则替换
CREATE OR REPLACE VIEW active_employees AS
SELECT emp_id, emp_name, salary, hire_date
FROM employees
WHERE status = 'active';

创建带列名的视图

sql
-- 指定视图的列名
CREATE VIEW employee_summary (
    员工编号,
    员工姓名,
    部门名称,
    薪资
) AS
SELECT e.emp_id, e.emp_name, d.dept_name, e.salary
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id;

SELECT * FROM employee_summary;

创建临时视图

sql
-- 临时视图只在当前会话中存在
CREATE TEMP VIEW temp_high_salary AS
SELECT * FROM employees WHERE salary > 15000;

-- 会话结束后自动删除
SELECT * FROM temp_high_salary;

查询视图

视图可以像普通表一样查询:

sql
-- 基本查询
SELECT * FROM active_employees;

-- 带条件查询
SELECT * FROM active_employees WHERE salary > 15000;

-- 排序
SELECT * FROM active_employees ORDER BY salary DESC;

-- 分组统计
SELECT 
    dept_name,
    COUNT(*) AS emp_count,
    AVG(salary) AS avg_salary
FROM employee_summary
GROUP BY dept_name;

-- 与其他表连接
SELECT 
    es.员工姓名,
    es.部门名称,
    d.location
FROM employee_summary es
JOIN departments d ON es.部门名称 = d.dept_name;

-- 在子查询中使用视图
SELECT * FROM employee_summary
WHERE 薪资 > (SELECT AVG(薪资) FROM employee_summary);

可更新视图

简单视图可以进行INSERT、UPDATE、DELETE操作,这些操作会直接影响底层表。

可更新视图的条件

视图必须满足以下条件才能更新:

  • 不包含DISTINCT
  • 不包含聚合函数
  • 不包含GROUP BY、HAVING
  • 不包含UNION、INTERSECT、EXCEPT
  • 不包含窗口函数
  • 不包含子查询(FROM子句除外)
  • 不包含不可更新的视图

更新视图示例

sql
-- 创建可更新视图
CREATE VIEW tech_employees AS
SELECT emp_id, emp_name, salary, status
FROM employees
WHERE dept_id = 1;

-- 通过视图更新数据
UPDATE tech_employees 
SET salary = 20000 
WHERE emp_name = '王五';

-- 验证更新
SELECT * FROM employees WHERE emp_name = '王五';

-- 通过视图插入数据(需要包含所有必要列)
INSERT INTO tech_employees (emp_name, salary, status, dept_id)
VALUES ('新员工', 15000, 'active', 1);

-- 通过视图删除数据
DELETE FROM tech_employees WHERE emp_name = '新员工';

使用WITH CHECK OPTION

sql
-- WITH CHECK OPTION确保通过视图修改的数据仍然满足视图条件
CREATE VIEW high_salary_employees AS
SELECT emp_id, emp_name, salary, dept_id
FROM employees
WHERE salary > 15000
WITH CHECK OPTION;  -- 确保更新后仍满足条件

-- 这个更新会成功(修改后仍满足salary > 15000)
UPDATE high_salary_employees SET salary = 18000 WHERE emp_name = '李四';

-- 这个更新会失败(修改后不满足salary > 15000)
-- UPDATE high_salary_employees SET salary = 10000 WHERE emp_name = '李四';
-- 错误:new row violates check option for view "high_salary_employees"

-- LOCAL CHECK OPTION(只检查当前视图条件)
CREATE VIEW local_check_view AS
SELECT * FROM high_salary_employees
WHERE dept_id = 1
WITH LOCAL CHECK OPTION;

-- CASCADED CHECK OPTION(检查所有底层视图条件,默认行为)
CREATE VIEW cascaded_check_view AS
SELECT * FROM high_salary_employees
WHERE dept_id = 1
WITH CASCADED CHECK OPTION;

物化视图

物化视图(Materialized View)将查询结果实际存储在磁盘上,适合用于不经常变化但查询复杂的数据。

创建物化视图

sql
-- 创建物化视图
CREATE MATERIALIZED VIEW dept_stats AS
SELECT 
    d.dept_name,
    COUNT(e.emp_id) AS emp_count,
    ROUND(AVG(e.salary), 2) AS avg_salary,
    MAX(e.salary) AS max_salary,
    MIN(e.salary) AS min_salary
FROM departments d
LEFT JOIN employees e ON d.dept_id = e.dept_id
GROUP BY d.dept_id, d.dept_name;

-- 查询物化视图
SELECT * FROM dept_stats;

-- 物化视图可以创建索引
CREATE INDEX idx_dept_stats_name ON dept_stats(dept_name);

刷新物化视图

sql
-- 刷新物化视图(全量刷新)
REFRESH MATERIALIZED VIEW dept_stats;

-- 并发刷新(不阻塞查询,需要有唯一索引)
CREATE UNIQUE INDEX idx_dept_stats_unique ON dept_stats(dept_name);
REFRESH MATERIALIZED VIEW CONCURRENTLY dept_stats;

-- 查看物化视图是否正在被使用
SELECT * FROM pg_stat_user_tables 
WHERE relname = 'dept_stats';

删除物化视图

sql
-- 删除物化视图
DROP MATERIALIZED VIEW dept_stats;

-- 如果存在则删除
DROP MATERIALIZED VIEW IF EXISTS dept_stats;

视图管理

查看视图信息

sql
-- 查看所有视图
\dv

-- 查看视图定义
\d+ active_employees

-- 使用SQL查询视图信息
SELECT 
    viewname AS 视图名称,
    viewowner AS 所有者,
    definition AS 定义
FROM pg_views
WHERE schemaname = 'public';

-- 查看物化视图
SELECT 
    schemaname,
    matviewname AS 物化视图名称,
    matviewowner AS 所有者
FROM pg_matviews;

-- 查看视图的列信息
SELECT 
    column_name,
    data_type,
    is_nullable
FROM information_schema.columns
WHERE table_name = 'active_employees'
ORDER BY ordinal_position;

修改视图

sql
-- 使用CREATE OR REPLACE修改视图定义
CREATE OR REPLACE VIEW active_employees AS
SELECT 
    emp_id, 
    emp_name, 
    salary, 
    hire_date,
    status
FROM employees
WHERE status = 'active';

-- 重命名视图
ALTER VIEW active_employees RENAME TO active_staff;

-- 设置视图所有者
ALTER VIEW active_staff OWNER TO postgres;

-- 设置视图Schema
ALTER VIEW active_staff SET SCHEMA public;

删除视图

sql
-- 删除视图
DROP VIEW active_staff;

-- 如果存在则删除
DROP VIEW IF EXISTS active_staff;

-- 级联删除(删除依赖该视图的对象)
DROP VIEW employee_summary CASCADE;

视图应用场景

简化复杂查询

sql
-- 创建复杂的统计视图
CREATE VIEW sales_report AS
SELECT 
    d.dept_name AS 部门,
    COUNT(e.emp_id) AS 员工数,
    ROUND(AVG(e.salary), 2) AS 平均薪资,
    ROUND(SUM(e.salary), 2) AS 薪资总额,
    MAX(e.salary) AS 最高薪资,
    MIN(e.salary) AS 最低薪资,
    COUNT(CASE WHEN e.status = 'active' THEN 1 END) AS 活跃员工数
FROM departments d
LEFT JOIN employees e ON d.dept_id = e.dept_id
GROUP BY d.dept_id, d.dept_name;

-- 使用视图简化查询
SELECT * FROM sales_report ORDER BY 员工数 DESC;

数据安全

sql
-- 创建只显示部分列的视图(隐藏敏感信息)
CREATE VIEW employee_public AS
SELECT 
    emp_id,
    emp_name,
    dept_id,
    hire_date
FROM employees;
-- 不显示salary和status等敏感字段

-- 授予用户访问视图的权限,而不是表的权限
GRANT SELECT ON employee_public TO readonly_user;

数据逻辑独立性

sql
-- 当底层表结构变化时,只需修改视图定义
-- 原始视图
CREATE VIEW employee_info AS
SELECT emp_id, emp_name, salary FROM employees;

-- 如果employees表拆分为employee_basic和employee_salary
-- 只需修改视图定义,应用程序无需修改
CREATE OR REPLACE VIEW employee_info AS
SELECT 
    b.emp_id, 
    b.emp_name, 
    s.salary
FROM employee_basic b
JOIN employee_salary s ON b.emp_id = s.emp_id;

行级安全

sql
-- 创建基于用户的行级视图
CREATE VIEW my_department_employees AS
SELECT e.*
FROM employees e
WHERE e.dept_id = (
    SELECT dept_id FROM employees WHERE emp_name = CURRENT_USER
);

-- 或者使用会话变量
CREATE VIEW filtered_employees AS
SELECT * FROM employees
WHERE dept_id = current_setting('app.current_dept_id')::INTEGER;

视图最佳实践

命名规范

sql
-- 使用有意义的前缀或后缀
CREATE VIEW vw_employee_summary AS ...;   -- vw前缀
CREATE VIEW employee_summary_view AS ...; -- view后缀

-- 物化视图使用mv前缀
CREATE MATERIALIZED VIEW mv_daily_stats AS ...;

性能考虑

sql
-- 1. 避免嵌套过深的视图
-- 不好的做法:视图套视图
CREATE VIEW view1 AS SELECT * FROM employees;
CREATE VIEW view2 AS SELECT * FROM view1;
CREATE VIEW view3 AS SELECT * FROM view2;

-- 2. 对于复杂查询,考虑使用物化视图
CREATE MATERIALIZED VIEW mv_complex_report AS
SELECT ... -- 复杂的聚合查询

-- 3. 定期刷新物化视图
-- 可以使用pg_cron扩展设置定时任务
-- SELECT cron.schedule('refresh_stats', '0 2 * * *', 
--     'REFRESH MATERIALIZED VIEW mv_daily_stats');

-- 4. 为物化视图创建合适的索引
CREATE INDEX idx_mv_report_date ON mv_daily_stats(report_date);

视图文档化

sql
-- 使用COMMENT为视图添加注释
COMMENT ON VIEW employee_summary IS '员工汇总视图,包含员工基本信息和部门名称';
COMMENT ON MATERIALIZED VIEW dept_stats IS '部门统计物化视图,每日刷新';

-- 查看视图注释
SELECT 
    c.relname AS 视图名称,
    d.description AS 说明
FROM pg_class c
LEFT JOIN pg_description d ON c.oid = d.objoid
WHERE c.relkind = 'v'  -- v表示视图
AND d.description IS NOT NULL;

视图与表的区别

特性视图物化视图
存储数据
实时性实时需刷新实时
更新操作受限受限完全支持
性能依赖查询查询快查询快
存储空间极小较大

小结

本章介绍了PostgreSQL视图的主要内容:

内容说明
普通视图虚拟表,不存储数据
可更新视图满足条件的视图可以更新
WITH CHECK OPTION确保更新后仍满足视图条件
物化视图存储查询结果,适合复杂查询
视图权限可以通过视图控制数据访问

视图是数据库开发中的重要工具,下一章我们将学习函数的创建和使用。