一、什么是PostgreSQL视图?
视图(View)是基于SQL查询结果的虚拟表——它不物理存储数据,仅保存查询的逻辑定义。当你查询视图时,PostgreSQL会动态执行视图的定义查询,返回基础表的最新结果。视图的核心价值在于:
- 简化复杂查询:将常用的多表关联、过滤逻辑封装成视图,避免重复写冗长SQL;
- 限制数据访问:仅暴露基础表的部分列/行给用户,保障数据安全;
- 隔离 schema 变化:当基础表结构调整时,只需修改视图定义,不影响应用代码。
例如,若你频繁需要查询“喜剧类型的电影”,可以创建comedies视图,之后直接查询comedies即可,无需每次写WHERE kind = 'Comedy'。
二、创建视图的基本语法
PostgreSQL用CREATE VIEW语句创建视图,完整语法如下(来自官方文档):
CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] [ RECURSIVE ] VIEW name [ ( column_name [, ...] ) ]
[ WITH ( view_option_name [= view_option_value] [, ... ] ) ]
AS query
[ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
下面拆解关键参数的含义和用法:
1. 可选修饰符:OR REPLACE、TEMPORARY
- OR REPLACE:若同名视图已存在,则替换它(需保证新视图的列名、顺序、类型与原视图一致,可添加新列到末尾);
- TEMPORARY(或TEMP):创建临时视图,会话结束后自动删除。若视图引用的表是临时表,视图会自动转为临时视图。
示例:替换已有视图并添加新列:
-- 原视图:仅包含id、title
CREATE VIEW comedies AS SELECT id, title FROM films WHERE kind = 'Comedy';
-- 替换视图:添加release_year列
CREATE OR REPLACE VIEW comedies AS
SELECT id, title, release_year FROM films WHERE kind = 'Comedy';
2. RECURSIVE:递归视图
递归视图用于处理递归结构(如层级数据、序列生成),等价于WITH RECURSIVE的CTE(公共表表达式)。必须显式指定列名列表。
递归视图的结构分为两部分:
- 初始查询(非递归部分):返回递归的起始行;
- 递归查询(递归部分):引用视图本身,返回下一层行,直到条件不满足。
示例:生成1到100的连续数字:
CREATE RECURSIVE VIEW nums_1_100 (n) AS
VALUES (1) -- 初始查询:起始值1
UNION ALL
SELECT n + 1 FROM nums_1_100 WHERE n < 100; -- 递归查询:每次加1,直到n=99
查询结果:SELECT * FROM nums_1_100;(返回1~100的整数)
3. 列名指定
若不指定column_name,视图列名将从查询中自动推导(如SELECT id, title的列名是id、title)。但建议显式指定,避免默认的?column?或歧义。
示例:显式指定列名:
CREATE VIEW film_ratings (film_id, average_rating) AS
SELECT film_id, AVG(rating) FROM user_ratings GROUP BY film_id;
4. WITH选项:视图的高级参数
WITH clause 用于设置视图的附加属性,常见参数:
- security_barrier(布尔值):用于行级安全(RLS),确保视图的
WHERE条件先于用户的查询条件执行,防止信息泄露; - security_invoker(布尔值):默认
false。若设为true,访问基础表的权限检查将使用执行查询的用户(而非视图所有者)的权限; - check_option(枚举值):等价于后面的
CHECK OPTION(local或cascaded)。
示例:创建安全屏障视图(用于RLS):
CREATE VIEW secure_films WITH (security_barrier = true) AS
SELECT * FROM films WHERE is_public = true;
5. AS query:视图的核心逻辑
query是视图的定义查询,必须是有效的SELECT或VALUES语句。例如:
-- 查询“2020年以后上映的喜剧电影”
CREATE VIEW recent_comedies AS
SELECT id, title, release_year, director
FROM films
WHERE kind = 'Comedy' AND release_year >= 2020;
6. CHECK OPTION:确保更新的行可见
CHECK OPTION用于可更新视图,确保INSERT/UPDATE/MERGE操作产生的行仍满足视图的定义条件(即能通过视图看到)。有两种模式:
- LOCAL:仅检查当前视图的条件,不检查基础视图;
- CASCADED:检查当前视图和所有基础视图的条件(默认)。
示例:
-- 基础视图:喜剧电影
CREATE VIEW comedies AS SELECT * FROM films WHERE kind = 'Comedy';
-- 子视图:U级喜剧(LOCAL CHECK OPTION)
CREATE VIEW universal_comedies AS
SELECT * FROM comedies WHERE classification = 'U'
WITH LOCAL CHECK OPTION; -- 仅检查classification='U'
-- 子视图:PG级喜剧(CASCADED CHECK OPTION)
CREATE VIEW pg_comedies AS
SELECT * FROM comedies WHERE classification = 'PG'
WITH CASCADED CHECK OPTION; -- 检查classification='PG' + kind='Comedy'
三、可更新视图(Updatable Views)
不是所有视图都能执行INSERT/UPDATE/DELETE/MERGE——PostgreSQL会自动判断视图是否“可更新”。
1. 自动可更新的条件
视图需满足以下所有条件:
FROM子句仅包含一个表或另一个可更新视图;- 视图定义无
WITH/DISTINCT/GROUP BY/HAVING/LIMIT/OFFSET; - 无顶层集合操作(
UNION/INTERSECT/EXCEPT); SELECT列表无聚合函数(AVG/SUM)、窗口函数(ROW_NUMBER)或返回集合的函数(generate_series)。
2. 可更新列 vs 只读列
- 可更新列:直接引用基础表的可更新列(如
films.id); - 只读列:计算列(函数结果)、聚合结果、子查询结果等。
示例:混合列的视图:
CREATE VIEW comedy_details AS
SELECT
f.id, -- 可更新(来自films.id)
f.title, -- 可更新(来自films.title)
country_code_to_name(f.country_code) AS country, -- 只读(函数结果)
(SELECT AVG(r.rating) FROM user_ratings r WHERE r.film_id = f.id) AS avg_rating -- 只读(聚合结果)
FROM films f
WHERE f.kind = 'Comedy';
若尝试更新country列,会报错:ERROR: column "country" is read only。
3. 不可更新视图的解决方案
若视图不满足自动可更新条件,可通过**INSTEAD OF触发器**实现更新——将视图的操作转换为基础表的操作。
示例:为聚合视图添加更新触发器:
-- 不可更新视图(有GROUP BY)
CREATE VIEW film_ratings AS
SELECT film_id, AVG(rating) AS avg_rating FROM user_ratings GROUP BY film_id;
-- 创建触发器函数:将视图更新转为基础表更新
CREATE OR REPLACE FUNCTION update_film_rating()
RETURNS TRIGGER AS $$
BEGIN
-- 简化逻辑:将avg_rating更新到user_ratings表的对应film_id
UPDATE user_ratings SET rating = NEW.avg_rating WHERE film_id = NEW.film_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 绑定INSTEAD OF触发器到视图
CREATE TRIGGER trigger_update_film_rating
INSTEAD OF UPDATE ON film_ratings
FOR EACH ROW EXECUTE FUNCTION update_film_rating();
四、递归视图的高级应用
递归视图常用于处理层级结构或序列生成,以下是两个典型场景:
1. 场景1:生成日期序列
需求:生成2024年1月的所有日期:
CREATE RECURSIVE VIEW jan_2024_dates (date) AS
VALUES ('2024-01-01'::date) -- 初始日期
UNION ALL
SELECT date + INTERVAL '1 day' FROM jan_2024_dates WHERE date < '2024-01-31'; -- 每天加1天
查询结果:SELECT * FROM jan_2024_dates;(返回31行日期)
2. 场景2:查询组织层级
假设employees表有id(员工ID)、name(姓名)、manager_id(上级ID),需求:查询所有员工的层级关系:
CREATE RECURSIVE VIEW employee_hierarchy (id, name, manager_id, level) AS
-- 初始查询:顶层管理者(无上级)
SELECT id, name, manager_id, 1 FROM employees WHERE manager_id IS NULL
UNION ALL
-- 递归查询:关联上级,层级+1
SELECT e.id, e.name, e.manager_id, eh.level + 1
FROM employees e
JOIN employee_hierarchy eh ON e.manager_id = eh.id;
查询结果:SELECT * FROM employee_hierarchy;(返回所有员工的层级,如顶层管理者level=1,下属level=2)
五、视图的安全与权限
PostgreSQL视图的权限默认基于视图所有者:用户访问视图时,PostgreSQL检查视图所有者对基础表的权限,而非用户自己的权限。
1. security_invoker:以调用者权限访问基础表
若视图的security_invoker设为true,则权限检查会使用执行查询的用户的权限。例如:
-- 视图:仅显示当前用户的电影
CREATE VIEW user_films WITH (security_invoker = true) AS
SELECT * FROM films WHERE user_id = current_user;
当用户alice查询user_films时,PostgreSQL会检查alice对films表的权限,而非视图所有者的权限。
2. security_barrier:防止信息泄露
security_barrier用于行级安全(RLS),确保视图的WHERE条件先于用户的查询条件执行,避免“条件泄露”。例如:
-- 视图:仅显示公开电影
CREATE VIEW secure_films WITH (security_barrier = true) AS
SELECT * FROM films WHERE is_public = true;
当用户查询secure_films WHERE title LIKE '%secret%'时,PostgreSQL会先过滤is_public = true的电影,再执行用户的条件,确保不泄露非公开电影的信息。
六、课后Quiz
问题1:如何确保插入到视图的行满足所有基础视图的条件?
答案:使用WITH CASCADED CHECK OPTION。例如:
CREATE VIEW pg_comedies AS
SELECT * FROM comedies WHERE classification = 'PG'
WITH CASCADED CHECK OPTION;
插入时会检查comedies视图的kind = 'Comedy'和当前视图的classification = 'PG'。
问题2:请写出创建“1到50的数字”的递归视图的SQL。
答案:
CREATE RECURSIVE VIEW nums_1_50 (n) AS
VALUES (1) -- 初始值
UNION ALL
SELECT n + 1 FROM nums_1_50 WHERE n < 50; -- 递归加1
问题3:为什么聚合视图无法自动更新?如何解决?
答案:聚合视图包含GROUP BY或聚合函数(如AVG),不满足自动可更新的条件。解决方法是为视图创建INSTEAD OF触发器,将更新操作转换为基础表的操作。
七、常见报错及解决方案
报错1:ERROR: cannot create view without a query
原因:创建视图时缺少AS query,例如:
CREATE VIEW comedies; -- 错误:无查询逻辑
解决:添加AS和有效查询:
CREATE VIEW comedies AS SELECT * FROM films WHERE kind = 'Comedy';
报错2:ERROR: view "view_name" cannot be modified because it contains a non-updatable column
原因:尝试更新视图中的只读列(如聚合结果),例如:
-- 视图有avg_rating(聚合列,只读)
CREATE VIEW film_ratings AS SELECT film_id, AVG(rating) AS avg_rating FROM user_ratings GROUP BY film_id;
-- 错误:更新只读列
UPDATE film_ratings SET avg_rating = 4 WHERE film_id = 1;
解决:
- 去掉视图中的只读列,使其成为自动可更新视图;
- 为视图创建
INSTEAD OF触发器。
报错3:ERROR: recursive view "view_name" must have a column list
原因:创建递归视图时未指定列名列表,例如:
-- 错误:无列名列表
CREATE RECURSIVE VIEW nums_1_100 AS VALUES (1) UNION ALL SELECT n+1 FROM nums_1_100 WHERE n < 100;
解决:显式指定列名列表:
CREATE RECURSIVE VIEW nums_1_100 (n) AS VALUES (1) UNION ALL SELECT n+1 FROM nums_1_100 WHERE n < 100;
报错4:ERROR: permission denied for table base_table
原因:视图所有者无基础表的权限,例如:
-- 视图所有者view_owner无films表的SELECT权限
CREATE VIEW comedies AS SELECT * FROM films WHERE kind = 'Comedy';
解决:授予视图所有者基础表的权限:
GRANT SELECT ON films TO view_owner;
若视图的security_invoker = true,则需授予执行查询的用户基础表的权限。
八、参考链接
参考链接:www.postgresql.org/docs/17/sql…
余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:编程智域 前端至全栈交流与成长,阅读完整的文章:PostgreSQL视图不存数据?那它怎么简化查询还能递归生成序列和控制权限?
往期文章归档
- PostgreSQL索引这么玩,才能让你的查询真的“飞”起来? - cmdragon's Blog
- PostgreSQL的表关系和约束,咋帮你搞定用户订单不混乱、学生选课不重复? - cmdragon's Blog
- PostgreSQL查询的筛子、排序、聚合、分组?你会用它们搞定数据吗? - cmdragon's Blog
- PostgreSQL数据类型怎么选才高效不踩坑? - cmdragon's Blog
- 想解锁PostgreSQL查询从基础到进阶的核心知识点?你都get了吗? - cmdragon's Blog
- PostgreSQL DELETE居然有这些操作?返回数据、连表删你试过没? - cmdragon's Blog
- PostgreSQL UPDATE语句怎么玩?从改邮箱到批量更新的避坑技巧你都会吗? - cmdragon's Blog
- PostgreSQL插入数据还在逐条敲?批量、冲突处理、返回自增ID的技巧你会吗? - cmdragon's Blog
- PostgreSQL的“仓库-房间-货架”游戏,你能建出电商数据库和表吗? - cmdragon's Blog
- PostgreSQL 17安装总翻车?Windows/macOS/Linux避坑指南帮你搞定? - cmdragon's Blog
- 能当关系型数据库还能玩对象特性,能拆复杂查询还能自动管库存,PostgreSQL凭什么这么香? - cmdragon's Blog
- 给接口加新字段又不搞崩老客户端?FastAPI的多版本API靠哪三招实现? - cmdragon's Blog
- 流量突增要搞崩FastAPI?熔断测试是怎么防系统雪崩的? - cmdragon's Blog
- FastAPI秒杀库存总变负数?Redis分布式锁能帮你守住底线吗 - cmdragon's Blog
- FastAPI的CI流水线怎么自动测端点,还能让Allure报告美到犯规? - cmdragon's Blog
- 如何用GitHub Actions为FastAPI项目打造自动化测试流水线? - cmdragon's Blog
- 如何用Git Hook和CI流水线为FastAPI项目保驾护航? - cmdragon's Blog
- FastAPI如何用契约测试确保API的「菜单」与「菜品」一致?
- 为什么TDD能让你的FastAPI开发飞起来? - cmdragon's Blog
- 如何用FastAPI玩转多模块测试与异步任务,让代码不再“闹脾气”? - cmdragon's Blog
- 如何在FastAPI中玩转“时光倒流”的数据库事务回滚测试?
- 如何在FastAPI中优雅地模拟多模块集成测试? - cmdragon's Blog
- 多环境配置切换机制能否让开发与生产无缝衔接? - cmdragon's Blog
- 如何在 FastAPI 中巧妙覆盖依赖注入并拦截第三方服务调用? - cmdragon's Blog
- 为什么你的单元测试需要Mock数据库才能飞起来? - cmdragon's Blog
- 如何在FastAPI中巧妙隔离依赖项,让单元测试不再头疼? - cmdragon's Blog
- 如何在FastAPI中巧妙隔离依赖项,让单元测试不再头疼? - cmdragon's Blog
- 测试覆盖率不够高?这些技巧让你的FastAPI测试无懈可击! - cmdragon's Blog
- 为什么你的FastAPI测试覆盖率总是低得让人想哭? - cmdragon's Blog
- 如何让FastAPI测试不再成为你的噩梦? - cmdragon's Blog
- FastAPI测试环境配置的秘诀,你真的掌握了吗? - cmdragon's Blog
- 全链路追踪如何让FastAPI微服务架构的每个请求都无所遁形? - cmdragon's Blog
- 如何在API高并发中玩转资源隔离与限流策略? - cmdragon's Blog
- 任务分片执行模式如何让你的FastAPI性能飙升? - cmdragon's Blog
- 冷热任务分离:是提升Web性能的终极秘籍还是技术噱头? - cmdragon's Blog
- 如何让FastAPI在百万级任务处理中依然游刃有余? - cmdragon's Blog
- 如何让FastAPI与消息队列的联姻既甜蜜又可靠? - cmdragon's Blog
- 如何在FastAPI中巧妙实现延迟队列,让任务乖乖等待? - cmdragon's Blog
- FastAPI的死信队列处理机制:为何你的消息系统需要它? - cmdragon's Blog
- 如何让FastAPI任务系统在失败时自动告警并自我修复? - cmdragon's Blog
- 如何用Prometheus和FastAPI打造任务监控的“火眼金睛”? - cmdragon's Blog
- 如何用APScheduler和FastAPI打造永不宕机的分布式定时任务系统? - cmdragon's Blog
免费好用的热门在线工具
- 智能提词器 - 应用商店 | By cmdragon
- 魔法简历 - 应用商店 | By cmdragon
- Image Puzzle Tool - 图片拼图工具 | By cmdragon
- 字幕下载工具 - 应用商店 | By cmdragon
- 歌词生成工具 - 应用商店 | By cmdragon
- 网盘资源聚合搜索 - 应用商店 | By cmdragon
- ASCII字符画生成器 - 应用商店 | By cmdragon
- JSON Web Tokens 工具 - 应用商店 | By cmdragon
- Bcrypt 密码工具 - 应用商店 | By cmdragon
- GIF 合成器 - 应用商店 | By cmdragon
- GIF 分解器 - 应用商店 | By cmdragon
- 文本隐写术 - 应用商店 | By cmdragon
- CMDragon 在线工具 - 高级AI工具箱与开发者套件 | 免费好用的在线工具
- 应用商店 - 发现1000+提升效率与开发的AI工具和实用程序 | 免费好用的在线工具
- CMDragon 更新日志 - 最新更新、功能与改进 | 免费好用的在线工具
- 支持我们 - 成为赞助者 | 免费好用的在线工具
- AI文本生成图像 - 应用商店 | 免费好用的在线工具
- 临时邮箱 - 应用商店 | 免费好用的在线工具
- 二维码解析器 - 应用商店 | 免费好用的在线工具
- 文本转思维导图 - 应用商店 | 免费好用的在线工具
- 正则表达式可视化工具 - 应用商店 | 免费好用的在线工具
- 文件隐写工具 - 应用商店 | 免费好用的在线工具
- IPTV 频道探索器 - 应用商店 | 免费好用的在线工具
- 快传 - 应用商店 | 免费好用的在线工具
- 随机抽奖工具 - 应用商店 | 免费好用的在线工具
- 动漫场景查找器 - 应用商店 | 免费好用的在线工具
- 时间工具箱 - 应用商店 | 免费好用的在线工具
- 网速测试 - 应用商店 | 免费好用的在线工具
- AI 智能抠图工具 - 应用商店 | 免费好用的在线工具
- 背景替换工具 - 应用商店 | 免费好用的在线工具
- 艺术二维码生成器 - 应用商店 | 免费好用的在线工具
- Open Graph 元标签生成器 - 应用商店 | 免费好用的在线工具
- 图像对比工具 - 应用商店 | 免费好用的在线工具
- 图片压缩专业版 - 应用商店 | 免费好用的在线工具
- 密码生成器 - 应用商店 | 免费好用的在线工具
- SVG优化器 - 应用商店 | 免费好用的在线工具
- 调色板生成器 - 应用商店 | 免费好用的在线工具
- 在线节拍器 - 应用商店 | 免费好用的在线工具
- IP归属地查询 - 应用商店 | 免费好用的在线工具
- CSS网格布局生成器 - 应用商店 | 免费好用的在线工具
- 邮箱验证工具 - 应用商店 | 免费好用的在线工具
- 书法练习字帖 - 应用商店 | 免费好用的在线工具
- 金融计算器套件 - 应用商店 | 免费好用的在线工具
- 中国亲戚关系计算器 - 应用商店 | 免费好用的在线工具
- Protocol Buffer 工具箱 - 应用商店 | 免费好用的在线工具
- IP归属地查询 - 应用商店 | 免费好用的在线工具
- 图片无损放大 - 应用商店 | 免费好用的在线工具
- 文本比较工具 - 应用商店 | 免费好用的在线工具
- IP批量查询工具 - 应用商店 | 免费好用的在线工具
- 域名查询工具 - 应用商店 | 免费好用的在线工具
- DNS工具箱 - 应用商店 | 免费好用的在线工具
- 网站图标生成器 - 应用商店 | 免费好用的在线工具
- XML Sitemap