SQL 入门阶段,十个人里总有八个会在“视图”这个概念上绕一下。我第一次在同事代码里看到CREATE VIEW的时候,还以为他是建了一张临时表,后来发现数据对不上,才老老实实回头翻定义。这篇就围绕“SQL 自学:怎么创建视图”来写,把视图的语法、实操、权限、性能踩坑一次讲清楚。看完之后,你至少能在 SQL Server 2022 或 2019 环境下,用 T-SQL 和 SSMS 轻松创建出自己想要的视图,也知道遇到“创建视图权限不足”或者视图查询变慢时该从哪里排查。
1. 先把“视图是什么”说透,再来谈怎么创建
1.1 视图不是表,而是一段“存起来的查询”
很多人刚学 SQL 时,容易把视图当成一张“能看到数据的表”。本质不是这样。视图本身不保存数据,它保存的是一条SELECT查询语句。当你执行SELECT * FROM v_视图名时,数据库引擎会把视图名字替换成视图里定义的那段子查询,然后重新执行一次。
我习惯用一个类比来解释:视图就像 Windows 桌面上的快捷方式。快捷方式本身不是程序,双击它才会打开对应软件;视图本身不装数据,查询它时才会去底层表里现取数据。所以视图里的数据永远跟着基础表走,基础表数据变了,视图查出来的结果跟着变。也正因为这一点,后端报表、前端看板才会那么喜欢用视图——查同一个视图,谁都能拿到当前最新状态,不用反复粘贴同一段 SQL。
学习视图的困难,多半来自这里:你明明创建了一个名字像表的东西,但它没有自己的存储空间,也不能像表那样随便加索引(普通视图不行)。记住这条主线,后面所有细节都顺了。
1.2 视图到底解决什么问题
我整理了一下实际项目里最常见的四个使用场景,每个场景都对应一类需求,创建视图时心里要有数。
第一是实现“统一口径”。业务部门要统计订单金额时,有人算含税,有人算不含税,有人把退款单也算进去了。这时候建一个v_order_amount_standard视图,把过滤条件、公式一次性定死,开发人员只查这个视图,口径就统一了。
第二是隐藏敏感字段。用户表可能包含密码、身份证号、内部备注,直接给外部系统开整表权限风险太大。这时创建一个视图,只暴露姓名、部门、工号这些必要字段,再把视图的查询权限授出去,底层表的访问权限仍然收紧。
第三是简化高频复杂查询。多表JOIN、多层子查询、聚合报表这些 SQL 又长又容易拼错。建个视图,把复杂度封装起来,业务层每次只需要SELECT * FROM v_xxx WHERE ...就好。这也是为什么网上很多 SQL 复习资料里,一定会出现 CREATE VIEW 练习题。
第四是兼容表结构变更。老系统改名了、字段拆分了,但外部接口还在用旧字段名,可以在旧接口层建个视图,把新结构映射成老结构,避免一大批代码重写。这种场景在升级遗留系统时特别实用。
1.3 创建视图前必须先想清楚的几件事
我建议你在写CREATE VIEW之前,先回答三个问题:这个视图给谁用?它依赖的表和字段稳定吗?查询语句能不能做到只取必要字段?
给谁用,决定了你要不要加权限控制、要不要过滤掉敏感列。依赖的表稳定程度,决定了要不要加SCHEMABINDING,因为一旦用了模式绑定,底层表结构改动就会受到限制,好处是视图定义不容易因为字段被改而悄悄烂掉。只取必要字段,则能避免“SELECT *写进视图,后面表加了列导致视图结果集和业务代码不匹配”这种经典事故。
别小看这些问题。很多人创建视图失败,不是因为语法不会写,而是因为没想清楚视图要在什么边界内生存。我自己带新人时就强调:视图是给你解决查询问题的,不是给你制造“又一个数据源”的。能一条 SQL 解决就别急着封装;确实要复用的查询,才值得建视图。
2. 创建视图的语法拆解与设计要点
2.1 最小可用的 CREATE VIEW 语句
T-SQL 里创建视图的语法非常短:
CREATE VIEW 视图名 AS SELECT 列1, 列2, ... FROM 表名 WHERE 条件;如果你想显式指定列名,可以在视图名后面加个括号列表:
CREATE VIEW v_employee_brief (emp_no, emp_name, dept_name) AS SELECT e.emp_no, e.emp_name, d.dept_name FROM dbo.employee e JOIN dbo.department d ON e.dept_id = d.dept_id;注意视图名字前面最好带上 schema,比如dbo.v_employee_brief。没有写 schema 时,会默认取当前用户的默认 schema,容易在不同环境里出现解析差异。我见过两个测试环境执行同样代码,一个建在dbo,一个建在guest用户自己的 schema 里,后面排查了半天,就是因为视图建错了位置。
视图里的SELECT基本可以用所有查询语法:JOIN、GROUP BY、HAVING、CASE WHEN、LEFT JOIN都可以。但默认情况下,视图内不能用ORDER BY,除非你在ORDER BY前面加了TOP、OFFSET或FOR XML之类的子句。因为视图是关系对象,理论上行序不该有固定意义,排序应该在外部查询里做:
-- 这样会报错:除非另外还指定了 TOP、OFFSET 或 FOR XML,否则,ORDER BY 子句在视图、内联函数、派生表、子查询和公用表表达式中无效。 CREATE VIEW v_order_list AS SELECT * FROM dbo.orders ORDER BY order_date DESC; -- 错误示范 -- 正确的做法: CREATE VIEW v_order_list AS SELECT TOP (1000) * FROM dbo.orders ORDER BY order_date DESC; -- TOP 存在时允许日常查询视图时,你在外部加上ORDER BY就好。这一条几乎新手必踩。
2.2 常用选项:ENCRYPTION、SCHEMABINDING、WITH CHECK OPTION
除了上面基础语法,SQL Server 还支持几个创建视图时直接写在语句里的选项,每个选项背后都有明确的使用目的。
WITH ENCRYPTION会在系统目录里混淆视图定义文本,避免别人用sp_helptext或 SSMS 直接看到你写的 SQL。适合封装一些含算法、敏感业务规则的对象。但副作用也很明显:一旦连你自己都看不到定义,后续维护就完全依赖脚本备份。我强烈建议,如果加密视图,一定要把源代码脚本同步放到版本库里。
CREATE VIEW v_salary_stat (dept_id, avg_salary) WITH ENCRYPTION AS SELECT dept_id, AVG(salary) FROM dbo.employee_salary GROUP BY dept_id;WITH SCHEMABINDING则是模式绑定,它要求视图中引用的对象都用“两段式名称”:dbo.employee_salary这样带 schema 的写法。启用以后,底层表不能直接删除,也不能随意修改视图引用的列。好处是视图定义稳固,索引视图也必须带这个选项。坏处是改表时得多一步:先把视图删掉或修改,否则表结构改动会失败。
WITH CHECK OPTION要在可更新视图上使用。它强制所有通过视图写入的数据都必须满足视图里的WHERE条件。举个最简单的例子:视图v_order_valid只查有效订单status = '有效',如果直接往这个视图插入一条status = '作废'的记录,没有CHECK OPTION时可能插入成功,但插入后数据却从视图里“消失”,查不到,容易造成混乱;加上WITH CHECK OPTION后,数据库会直接拒绝这条不符合视图条件的数据写入。
CREATE VIEW v_order_valid AS SELECT order_id, order_amount, status FROM dbo.orders WHERE status = '有效' WITH CHECK OPTION;很多资料把这几个选项分开讲,实际项目里更常组合使用。比如做报表层视图,我一般会加SCHEMABINDING,因为基础表都是受控的表结构;给外部系统暴露数据时,则更常用WITH ENCRYPTION保护字段逻辑。
2.3 别把视图做成“能更新的万能表”
视图能不能插入和更新,是程序员最爱问的问题之一。简单回答:单表、包含基础表主键、没做聚合运算的视图,大多数情况下是可更新的。你甚至可以直接对视图INSERT和UPDATE,数据库会把操作映射到基础表上去。
但一旦视图里出现了JOIN、GROUP BY、DISTINCT、聚合函数,可更新性就会变得复杂。不加处理地往多表关联视图里插数据,SQL Server 会报“视图或函数不可更新,因为修改会影响多个基表”之类的错误。这时候有三个选择:一是改业务逻辑,直接更新基础表;二是对视图创建INSTEAD OF触发器,在里面自己写清楚插入、更新、删除逻辑;三是慎用可更新视图,把它当成“只读报表”使用就好。
我的看法是,别把视图当成万能表。视图最稳的用法是查询,写入操作尽量走明确的INSERT/UPDATE语句,这样执行计划、权限控制、日志审计都清晰。你要是把大量可更新视图摊开给业务方,后面一旦有人插入了一条违反业务规则的数据,排查成本会高到让你怀疑人生。
3. 实操:从零创建一个可用的 SQL 视图
3.1 准备演示表:员工表和订单表
纸上谈兵没意思,直接实操。这里我用 SQL Server 2019/2022 语法,在 SSMS 里新建一个查询窗口,依次执行建表脚本。演示场景是:员工表、部门表,再建了一张销售订单表,最终生成一个“销售订单明细视图”。
CREATE TABLE dbo.department ( dept_id INT PRIMARY KEY, dept_name NVARCHAR(50) NOT NULL ); CREATE TABLE dbo.employee ( emp_no INT PRIMARY KEY, emp_name NVARCHAR(50) NOT NULL, dept_id INT NOT NULL, salary DECIMAL(10, 2), hire_date DATE ); CREATE TABLE dbo.sales_order ( order_id INT PRIMARY KEY, emp_no INT NOT NULL, customer_name NVARCHAR(50), order_date DATE, order_amount DECIMAL(12, 2), status NVARCHAR(20) );插入一些测试数据:
INSERT INTO dbo.department(dept_id, dept_name) VALUES (1, N'技术部'), (2, N'市场部'), (3, N'销售部'); INSERT INTO dbo.employee(emp_no, emp_name, dept_id, salary, hire_date) VALUES (1001, N'张三', 1, 20000, '2021-03-01'), (1002, N'李四', 2, 15000, '2022-05-10'), (1003, N'王五', 3, 18000, '2020-01-15'), (1004, N'赵六', 3, 12000, '2023-07-01'); INSERT INTO dbo.sales_order(order_id, emp_no, customer_name, order_date, order_amount, status) VALUES (5001, 1001, N'客户A', '2024-01-10', 1200.00, N'有效'), (5002, 1003, N'客户B', '2024-01-11', 2300.50, N'有效'), (5003, 1003, N'客户C', '2024-01-12', 500.00, N'作废'), (5004, 1004, N'客户D', '2024-01-15', 8000.00, N'有效');建表和插入数据都要在同一个数据库里执行,建议选一个测试库,不要动生产数据。
3.2 用 T-SQL 创建第一个多表关联视图
现在需求来了:报表组需要一张“员工销售明细表”,里面要有订单号、客户、金额、状态,还要带上员工姓名和部门名称。最笨的做法是每次写一遍三表JOIN,很烦,所以我们把它做成视图:
CREATE VIEW v_sales_order_detail AS SELECT so.order_id, so.order_date, so.customer_name, so.order_amount, so.status, e.emp_no, e.emp_name, d.dept_name, d.dept_id FROM dbo.sales_order so JOIN dbo.employee e ON so.emp_no = e.emp_no JOIN dbo.department d ON e.dept_id = d.dept_id;这段代码执行成功后,SSMS 左侧的“视图”文件夹展开,就能看到v_sales_order_detail。马上验证:
SELECT * FROM v_sales_order_detail;你会看到 4 条订单数据。注意5003那条虽然状态是作废,但因为我们没在视图里写过滤条件,它也正常显示出来了。如果业务上只需要有效订单,可以在创建视图的时候加上WHERE status = '有效',或者做报表时在外部查询过滤。
每当有人问我“创建视图和写普通 SELECT 有什么区别”时,我会说:区别就在“复用”。上面这个视图建完之后,你想按部门汇总金额,可以直接:
SELECT dept_name, SUM(order_amount) AS total_amount FROM v_sales_order_detail WHERE status = '有效' GROUP BY dept_name;不用再关心三张表的关联细节。
3.3 通过 SSMS 图形界面创建视图
除了写 T-SQL,SSMS 也提供了可视化创建视图的方式,适合刚接触 SQL、对表结构不够熟的初学者。
操作路径是:在对象资源管理器里找到目标数据库,展开“视图”,右键点击“新建视图”。会弹出“添加表”窗口,勾选你需要用到的表,选择“添加”。然后可以在图形化区域里勾选列、设置表之间的JOIN关系、配置条件,底层会自动生成对应的SELECT语句。编辑完点击运行,确认数据没问题,再按 Ctrl+S 保存,输入视图名字即可。
这个界面有个好处是能实时看到JOIN关系图,对理解多表关联非常有帮助。但你也要知道,它生成的 SQL 有时候比较“啰嗦”,因为会带上括号和默认别名。等你有经验之后,还是建议直接手写 T-SQL,可控性高很多。
另外提醒一点:SSMS 图形界面创建的视图,默认保存在你当前连接的数据库里。如果你在多人共用的服务器上做练习,建出来的视图可能别人也能看到,命名最好加上自己标识,避免覆盖别人的同名视图。
3.4 视图建好后,怎么验证和查看定义
视图创建成功后,不能只看“命令已完成”就结束。建议做三件事:验证字段、查看定义、测试权限。
验证字段可以执行:
SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('dbo.v_sales_order_detail');或者用sp_help:
EXEC sp_help 'dbo.v_sales_order_detail';它会列出视图的列清单、类型、是否可空等信息,方便你和业务文档对照。
查看定义则用:
EXEC sp_helptext 'dbo.v_sales_order_detail';如果当初创建时加了WITH ENCRYPTION,这个存储过程看不清原码。这也是为什么加密视图要格外注意脚本备份。
3.5 创建视图写脚本前的一个小习惯
我个人建视图时,会先写一段IF OBJECT_ID判断,避免重复执行时报“数据库中已存在名为...”的错误:
IF OBJECT_ID('dbo.v_sales_order_detail', 'V') IS NOT NULL DROP VIEW dbo.v_sales_order_detail; GO CREATE VIEW dbo.v_sales_order_detail AS ...不过要注意,直接DROP VIEW再CREATE VIEW会丢掉已经授予视图的权限,所以更推荐在已有对象上使用ALTER VIEW。后面专门聊这个坑。
4. 我在创建视图时踩过的坑:排查实录
4.1 “创建视图权限不足”的完整处理过程
热搜词里赫然有一条“创建视图权限不足”,这是日常 DBA 和开发协作时最常遇到的权限错误。SQL Server 报错通常长这样:
消息 262,级别 14,状态 1,第 XX 行 在数据库 'Test' 中拒绝了 CREATE VIEW 权限。数据库级别权限不足。
原因很简单:当前登录用户在目标数据库里没有创建对象的权限。解决办法是在该库上授予CREATE VIEW权限,同时还要有引用表的SELECT权限。以数据库管理员身份执行:
USE Test; GO GRANT CREATE VIEW TO [你的用户名]; GRANT SELECT ON OBJECT::dbo.employee TO [你的用户名]; GRANT SELECT ON OBJECT::dbo.department TO [你的用户名]; GRANT SELECT ON OBJECT::dbo.sales_order TO [你的用户名];如果还是不行,检查用户是否属于数据库角色。开发账号最好只加入db_datareader,再单独授予CREATE VIEW,不要直接塞进db_owner。这样既能支持开发,又不会把整个库的写权限都放开。
有人会问:为什么我明明能查询基础表,却创建不了视图?因为“查询数据”和“创建对象”是两种不同权限。你能够查表,只说明你有SELECT权限;创建视图却还需要对当前 schema 的CREATE权限。理解这一点,就不会被这个报错吓住了。
4.2 视图查询很慢:从“慢SQL优化”角度去查
视图慢,是另一个高发问题。很多人觉得用了视图,查询就会被“预先优化”,其实普通视图每次执行都会实时查表,优化器对它的处理,跟你直接执行那段 SELECT 没有什么本质区别。所以视图查询慢,第一件事就是打开执行计划看内部语句。
我常用的排查路径是先看“实际执行计划”中耗时最高的操作,确认是不是缺索引。比如v_sales_order_detail里按sales_order.emp_no关联employee.emp_no,如果两个表数据量大、关联字段没有索引,就会出现嵌套循环配大表扫描,几百毫秒甚至几秒。
这时候不是去给视图加什么选项,而是去基础表上补索引:
CREATE INDEX IX_sales_order_emp_no ON dbo.sales_order(emp_no); CREATE INDEX IX_employee_dept_id ON dbo.employee(dept_id);第二个常见原因是视图套视图。有人为了提高复用度,建了v1,然后用v1建了v2,再用v2建了v3,最后查v3时,嵌套层次很深,执行计划膨胀,优化器也不一定能把中间层合并掉。遇到这种慢 SQL,把视图展开,原样改成基础表 JOIN,往往立竿见影。
再说一遍:视图的命名是“封装复杂逻辑”,不是“让查询一定变快”。如果非要用视图提升性能,请考虑索引视图,见第 5 章。
4.3 SQL Server 连接时出现 SSL 相关的连接错误
SSMS 实操过程中,很多新手不是被 SQL 语法难倒,而是连连接都没建起来。热搜词里那条“驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接”就非常典型。
这类报错常见于 SQL Server 开启“强制加密”后,客户端 SSMS 却无法验证服务器证书,或者客户端信任级别设置不一致。解决办法有几个方向:
- 在连接对话框点击“选项” -> “加密”,尝试勾选“信任服务器证书”,但前提是测试环境或者可控内网环境。
- 检查 SQL Server 的证书配置,确认服务器安装了有效证书。
- 如果只是本地开发,可以暂时关闭服务器的强制加密,但我更建议保留加密,并补齐证书信任链。
需要注意,我不建议为了消除报错就在公网环境下关闭加密或随意跳过证书验证。安全底线不能妥协。连接问题通常是环境问题,不是视图语法问题,所以别再纠结 SQL 写没写对了。
4.4 修改视图时“DROP后CREATE”带来的权限丢失
我见过很多老开发,改视图时特别喜欢写:
IF OBJECT_ID('dbo.v_demo', 'V') IS NOT NULL DROP VIEW dbo.v_demo; GO CREATE VIEW dbo.v_demo AS SELECT ...坏处很明显:每 DROP 一次,视图上原有的GRANT SELECT权限、以及相关依赖关系(比如下游存储过程对它的引用元数据)全部丢失。如果这是报表系统正在用的视图,很可能造成间歇性访问失败。
更稳的做法是直接ALTER VIEW。它会保留对象本身,只替换内部查询逻辑:
ALTER VIEW dbo.v_demo AS SELECT ...同时,ALTER VIEW也保留了视图上的权限设置。这一点在正式环境里非常重要。我现在的习惯是:除非视图结构变化太大必须重建,否则一律用ALTER VIEW。
4.5 给视图起名时的小雷区
给视图命名也有讲究。不要用sp_开头,SQL Server 会优先把它当成系统存储过程来处理,每次调用都可能先查master数据库,性能受影响。不要用系统保留字和空格。建议统一前缀,比如v_或view_,一看就知道是视图。
还要注意,SQL Server 有个坑:视图名和基础表名在同一个 schema 不能重复。想绕开的话,可以让视图建在独立 schema 下,比如report.v_sales_order_detail。这样报表 schema 和业务表 schema 分离,权限隔离也更干净。
5. 视图创建后的维护经验与安全建议
5.1 当视图越来越多,怎么避免“套娃式垃圾视图”
我见过最夸张的情况,一个小项目里塞了 80 多个视图,很多视图只被用了一两次,还有 A 视图引用 B 视图、B 视图又引用 A 视图的循环依赖。创建视图一定要有节制。
我的经验是:每个视图都必须能说清楚“服务哪个业务场景”,并且定期清理没人用的视图。你可以用下面这个查询,结合sys.sql_expression_dependencies找出视图之间相互引用关系,定位没用到的视图:
SELECT referencing.referencing_schema_name, referencing.referencing_entity_name, referenced.referenced_schema_name, referenced.referenced_entity_name FROM sys.sql_expression_dependencies AS deps JOIN sys.objects AS referencing ON deps.referencing_id = referencing.object_id JOIN sys.objects AS referenced ON deps.referenced_id = referenced.object_id WHERE referencing.type = 'V' AND referenced.type = 'V';不要怕删视图。删除一个没人用的视图,比留着一个每天都在产生疑惑的对象要好得多。视图本质上是代码,代码需要有维护者,没人维护的代码早晚是负担。
5.2 用索引视图把“虚拟表”变成“可加速实体”
普通视图性能可能不理想,但 SQL Server 支持“索引视图”,相当于把视图计算结果实体化存储,并且自动维护更新。效果类似其他数据库里的物化视图。
创建索引视图有比较严格的前提:视图必须使用WITH SCHEMABINDING,基础表必须存在唯一聚集索引,视图里的连接必须明确、不能使用子查询(某些条件),等等。比如给上面的销售明细视图创建索引,流程大致是:
-- 先改成模式绑定 ALTER VIEW v_sales_order_detail WITH SCHEMABINDING AS SELECT ... GO -- 创建唯一聚集索引 CREATE UNIQUE CLUSTERED INDEX IX_v_sales_order_detail_order_id ON dbo.v_sales_order_detail(order_id);建了聚集索引后,还可以在视图上继续建非聚集索引,进一步提升聚合查询速度。但请记住,索引视图有维护成本。每次基础表INSERT/UPDATE/DELETE,索引视图都要同步更新,如果你的业务写多读少,千万别为了查询快一点而把写入拖慢。
我很建议在报表库、数仓层使用索引视图,而事务型业务库要谨慎。
5.3 用视图做权限隔离时要补的一块短板
视图最常见的权限用途是隐藏敏感字段,但只有视图是不够的。比如用户有db_datareader角色,他可能仍然可以直接SELECT基础表,只要基础表权限没封住,视图的隔离就是摆设。
正确做法分两层:
- 底层表只授权给管理账号或服务账号,不直接授权给报表用户;
- 报表用户只拥有视图的
SELECT权限。
如果你不希望用户绕过视图直接查基础表,甚至可以创建独立 schema(比如sec)放基础表,再创建一个reportschema 放视图,权限交界非常清晰。我在项目里做权限模型时,最讨厌的就是“表权限和视图权限乱成一锅粥”的情况,权限设计越简单,后面审计越轻松。
5.4 视图不是防 SQL 注入的盾牌
创建视图能隐藏字段,但它不能防 SQL 注入。如果你把用户输入的参数直接拼进 SQL 字符串,然后去查询那个视图,注入风险依旧存在。比如很多人喜欢写:
-- 错误示范:拼接 SQL DECLARE @sql NVARCHAR(MAX) = 'SELECT * FROM v_sales_order_detail WHERE customer_name = ''' + @input + ''''; EXEC(@sql);如果@input被传入'客户A' OR 1=1--,过滤条件就形同虚设。正确做法是参数化查询,在数据库里可以用sp_executesql:
DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM dbo.v_sales_order_detail WHERE customer_name = @cust'; EXEC sp_executesql @sql, N'@cust NVARCHAR(50)', @cust = @input;在应用层,用 ADO.NET、JDBC 等框架的SqlParameter/PreparedStatement传参数,也是同样的道理。视图能帮你统一口径、控制权限,但注入防护还是要靠编码习惯。
回到创建视图这件事本身。很多人学 SQL 学到视图,会把它当成一个特别高级的功能,其实它只是把一条 SELECT 语句封装成了可复用的数据库对象。在这个基础上,真正让你水平拉开差距的,是对权限、性能、维护边界的理解。我自己喜欢的做法是:先写一条清晰的 SELECT,确认数据没问题,再套一层 CREATE VIEW,最后做权限和索引设计。整个过程听起来简单,但每步都做扎实,比背一百个语法规则都管用。