12. 视图
视图在数据库中是一张虚拟表,表中的字段由自己定义或者来自其他数据库。表中的数据可以来自单张表、单表中若干个字段,还可以来自多个表、函数等。主要取决于视图定义命令里的查询语句。
视图的作用
- 加速查询,在查询语句特别复杂的时候,查询花费的时间很大概率会变长,而如果将查询语句的结果定义成一个视图,下次就可以直接通过查询视图来查询所需的数据,效率会更高。
- 隐藏某些加密字段,比如隐藏用户表的密码、联系方式等信息
视图的分类
- 普通视图,正常功能的视图,会一直保存在模式里。查询数据时从原来的表查询;
- 临时视图,数据从表中查询,数据库连接断开时,视图消失;
- 物化视图,用创建的时候表中查询到的数据创建的时候,可以更新。数据查询的时候直接查视图,不查原来的表。
视图的定义
命令格式如下所示:
CREATE [OR REPLACE] [TEMP | TEMPORARY ] [MATERIALIZED] VIEW view_name (field1, field2....)
AS query
[ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
- 可以创建或替换当前已有的视图;
- 可以创建临时视图,当会话结束时,视图消失;
- 可以创建物化视图,表中的数据全部存储在视图里,查询时不用查原来的表;
- 可以在视图名称view_name后面的括号里定义视图里的字段名称。
- query语句可以是查询语句,也可以是执行函数。
示例如下
示例1 先创建一个信息表,然后在信息表上创建一个临时视图
postgres=# create table info(id int, email text, phone text, address text);
CREATE TABLE
postgres=# create temp view vw_info (id, phone) as select id, phone from info;
CREATE VIEW
# 显示视图信息
postgres=# \d vw_info;
View "pg_temp_2.vw_info"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
phone | text |
postgres=# \d
List of relations
Schema | Name | Type | Owner
-----------+-----------+-------+----------
pg_temp_2 | vw_info | view | postgres
上面的视图vw_info,里面只保存了info信息表的id和phone字段,其他字段都被隐藏。在vw_info 视图名后面的括号里定义视图的字段id和phone。而且vw_info还是一个临时视图,我们退出后登录,结果如下所示:
postgres=# \q
-bash-4.2$ psql
Password:
psql (9.6.6)
Type "help" for help.
postgres=# \d
List of relations
Schema | Name | Type | Owner
--------+-----------+-------+----------
可以看到vw_info这个视图已经消失了。
示例2:定义一个vw_info2视图,只需要info里的id和email字段,但是使用新的名称。
postgres=# create view vw_info2 (user_id, user_email) as select id, email from info;
CREATE VIEW
postgres=# \d vw_info2
View "public.vw_info2"
Column | Type | Modifiers
------------+---------+-----------
user_id | integer |
user_email | text |
从上面可以看到vw_info2视图中的字段名是新定义的字段名。往表info里面插入几个值,然后再看视图vw_info2视图里的值。
postgres=# insert into info values(1, 'alias@linux.com', '1234-234', 'NewYork');
INSERT 0 1
postgres=# insert into info values(2, 'Bob@gmail.com', '3444-235', 'WashingTon');
INSERT 0 1
postgres=# select id,email from info;
id | email
----+-----------------
1 | alias@linux.com
2 | Bob@gmail.com
(2 rows)
postgres=# select * from vw_info2;
user_id | user_email
---------+-----------------
1 | alias@linux.com
2 | Bob@gmail.com
(2 rows)
可以看到,两者中的值是一致的。
示例3:物化视图,创建一个物化vw_info3,查看视图里的数据,然后往表里插入一条新数据,再查看视图。
postgres=# create materialized view vw_info3 as select id, name from triggerdb;
SELECT 1
postgres=# \d vw_info3
Materialized view "public.vw_info3"
Column | Type | Modifiers
--------+---------+-----------
id | integer |
name | text |
postgres=# select * from vw_info3;
id | name
-----+---------
125 | test123
(1 row)
# 往triggerdb数据库里插入一条新数据,再看。
postgres=# insert into triggerdb values(1, 'Bob');
INSERT 0 1
postgres=# select * from triggerdb ;
id | name
-----+---------
125 | test123
1 | Bob
(2 rows)
postgres=# select * from vw_info3;
id | name
-----+---------
125 | test123
(1 row)
# 可以看到物化视图里的数据没有更新,仍然是创建时的数据,这就是它的特性。
但是物化视图里的数据也是可以更新的,需要用到另外的命令格式,分别是:
refresh materialized view view_name with [no] data;
refresh materialized view concurrently viewname with [no] data;
命令解释:
- 命令里的
concurrently
表示增量更新数据,即将视图当前的数据和查询语句获取到的数据进行对比,然后将视图中没有的数据更新到视图里去。 - 默认带
with data
和不带这个参数效果一致。但是如果带了no关键字,那么会把这个视图变成不可读状态。
可更新视图
严格意义上来讲,视图并不是可更新的,因为它的数据只是从其他表中读取,然后在数据库中生成的一张逻辑表。更新视图最后更新的还是视图背后的表。因此在PostgreSQL中实现的视图更新,也还是通过一些内置工具将对视图的更新转为对表的更新。在版本9.4及之前版本,需要通过两种方法来更新,分别是:
- 通过规则(rule)来更新
- 同过instead of类型的触发器来更新
而在9.5及之后的版本中,已经不需要定义规则或者触发器,视图本身就已经可以更新了,只是会有一些限制,具体的限制是:
- 视图更新的对象必须是一张表或另外一个可更新的视图
- 视图定义不能在顶层包含WITH,DISTINCT,GROUP BY,HAVING,LIMIT或OFFSET等子句
- 视图定义在顶层不能包含集合操作,例如UNION,INTERSECT或EXCEPT等。
- 视图的select列表不能包含聚合、窗口函数或者返回集合的函数。
同时,如果可更新的视图包含的内容来自两张表或视图,且其中一张表可写,另外一张表只读。那么你更新视图的时候,只能更新那张可写的表或视图。否则会报错。
当一个自动更新的视图包含一个where条件,那么where条件会限制在视图上执行的update或insert语句能够作用的表中的行。最后我们来说说[ WITH [ CASCADED|LOCAL] CHECK OPTION ]
这个子句对视图的限制。这个子句的作用是:
- 创建视图时,A视图基础上创建B视图,A视图上有一个where子句,B视图上也有一个where子句。那么在对带有WITH LOCAL CHECK OPTION的B视图进行更新时,只会检查B视图where子句里的条件,A视图里的where子句会跳过。
- 创建视图时,A视图基础上创建B视图。A视图上有一个where子句,B视图上也有where子句,那么在对带有WITH CASCADED CHECK OPTION的B视图进行更新时,会同时检查这两个where子句。
示例如下:
先看表info的信息:
postgres=# select * from info;
id | email | phone | address | sexual
----+---------------------+----------+------------+--------
1 | alias@hotmail.com | 1234-234 | NewYork | female
2 | Bob@gmail.com | 87222375 | WashingTon | male
3 | Tom@outlook.com | 87231634 | NewYork | male
4 | Lissa@twitter.com | 76892342 | NewYork | male
在这张表的基础上创建4个视图:
# 视图vw_info3用来过滤表中地址address='NewYork'的信息
postgres=# create view vw_info3 as select * from info where address='NewYork';
CREATE VIEW
# 视图vw_ny_m是一个普通视图,也是筛选NewYork里sexual='male'的信息。
postgres=# create view vw_ny_m as select * from vw_info3 where sexual = 'male';
CREATE VIEW
# 视图vw_ny_m2 带有with local check option子句,用来筛选 NewYork里sexual='male'的信息。
postgres=# create view vw_ny_m2 as select * from vw_info3 where sexual='male' with local check option;
CREATE VIEW
# 视图vw_ny_m3带有with cascaded check option子句,也是筛选NewYork里sexual='male'的信息。
postgres=# create view vw_ny_m3 as select * from vw_info3 where sexual='male' with cascaded check option;
CREATE VIEW
然后我们来测试往这三个vw_ny_m开头的视图里插入数据:
#往普通的vw_ny_m视图里插入数据,address不匹配,sexual不匹配,可以插入,说明两个where子句都没有生效
postgres=# insert into vw_ny_m values(5, 'Tim@google.com', '87293342', 'Washington', 'female');
INSERT 0 1
#往with local check option子句的视图vw_ny_m2里插入数据
# 第一次是address不匹配,sexual不匹配,报错,说明筛选条件生效:
postgres=# insert into vw_ny_m2 values(6, 'Jessica@outlook.com', '98762123', 'Washington', 'female');
ERROR: new row violates check option for view "vw_ny_m2"
DETAIL: Failing row contains (6, Jessica@outlook.com, 98762123, Washington, female).
# 第二次是address 不匹配,sexual 匹配,成功插入数据,说明筛选条件只在第二层视图vw_ny_m2的where子句上生效
postgres=# insert into vw_ny_m values(6, 'Jessica@outlook.com', '98762123', 'Washington', 'male');
INSERT 0 1
# 往with cascaded check option子句的视图 vw_ny_m3里插入数据
# 第一次address 不匹配,sexual 匹配,失败,说明筛选条件生效
insert into vw_ny_m3 values(7, 'Jone@goole.com', '9138729', 'Wahsington', 'male');
ERROR: new row violates check option for view "vw_info3"
DETAIL: Failing row contains (7, Jone@goole.com, 9138729, Wahsington, male).
# 第二次address 匹配,不sexual 匹配,失败,说明两个视图里的where子句都生效了。
insert into vw_ny_m3 values(7, 'Jone@goole.com', '9138729', 'NewYork', 'female');
ERROR: new row violates check option for view "vw_info3"
DETAIL: Failing row contains (7, Jone@goole.com, 9138729, NewYork, female).
视图的修改
视图修改的基本命令格式如下:
ALTER VIEW [ IF EXISTS ] name ALTER [ COLUMN ] column_name SET DEFAULT expression
ALTER VIEW [ IF EXISTS ] name ALTER [ COLUMN ] column_name DROP DEFAULT
ALTER VIEW [ IF EXISTS ] name OWNER TO { new_owner | CURRENT_USER | SESSION_USER }
ALTER VIEW [ IF EXISTS ] name RENAME TO new_name
ALTER VIEW [ IF EXISTS ] name SET SCHEMA new_schema
ALTER VIEW [ IF EXISTS ] name SET ( view_option_name [= view_option_value] [, ... ] )
ALTER VIEW [ IF EXISTS ] name RESET ( view_option_name [, ... ] )
命令解释:
- 第一个用于设置视图里的某个字段的默认值
- 第二个用于删除视图里某个字段的默认值
- 第三个用于设置视图的所有者为新的所有者/ 当前用户/会话用户
- 第四个用户重命名视图
- 第五个用于修改视图所在的模式
- 第六个用于设置视图的一些属性,例如上面我们讲到的check option。它的属性名称是check_option,对应的值是local或cascaded
- 第七个用于修改对应的属性值。
- IF EXISTS 选项用于在视图不存在时不报错,只输出提示信息,同样适用于删除视图
这几个命令都很简单,大家可以自己去做相应的实验示例。
视图的删除
视图的删除命令基本格式如下:
DROP VIEW [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ]
删除视图也非常简单,后面带的两个选项的含义是:
- cascade 表示删除这个视图时,同时删除依赖在这个视图上的其他对象。比如说函数、其他视图等。
- restrict 表示当有其他对象依赖在当前视图上时,不删除该视图。这是默认选项。
这个操作也非常简单,留给读者自己测试了。
至此,视图的所有基本内容讲解完毕。