視圖概述
視圖(View)本質上是一個存儲在數據庫中的查詢語句。視圖本身不包含數據,也被稱爲虛擬表;我們在創建視圖時給它指定了一個名稱,然後可以像表一樣對其進行查詢。
合理使用的視圖可以給我們帶來一下好處:
- 替代複雜查詢,減少複雜性。將複雜的查詢語句定義爲視圖,然後使用視圖進行查詢,可以隱藏具體的實現;
- 提供一致性接口,實現業務規則。在視圖的定義中增加業務邏輯,對外提供統一的接口;當底層表結構發生變化時,只需要修改視圖接口,而不需要修改外部應用,可以簡化代碼的維護並減少錯誤;
- 控制對於表的訪問,提高安全性。通過視圖爲用戶提供數據訪問,而不是直接訪問表;同時可以限制允許訪問某些敏感信息,例如身份證號、工資等。
創建視圖
PostgreSQL 使用CREATE VIEW
語句創建視圖:
CREATE VIEW view_name AS query;
其中,view_name 是視圖的名稱;AS 之後是視圖的查詢語句,可以是簡單查詢或者複雜的查詢。以下語句創建了一個包含員工詳細信息的視圖:
create view emp_details_view
as select
e.employee_id,
e.job_id,
e.manager_id,
e.department_id,
d.location_id,
e.first_name,
e.last_name,
e.salary,
e.commission_pct,
d.department_name,
j.job_title
from employees e
join departments d on (e.department_id = d.department_id)
join jobs j on (j.job_id = e.job_id);
該視圖使用了連接查詢從 3 個表中獲取信息。
接下來,我們可以直接從視圖中查詢數據,不需要每次編寫複雜的連接查詢:
select * from emp_details_view
where department_name = 'IT';
該語句返回了 IT 部門的員工信息。
修改視圖
如果需要修改視圖定義中的查詢,可以使用CREATE OR REPLACE
語句:
CREATE OR REPLACE view_name
AS
query
PostgreSQL 目前只支持追加視圖定義中的字段,不支持減少字段或者修改字段的名稱或順序。例如,我們可以爲視圖 emp_details_view 增加一個字段 hire_date:
create or replace view emp_details_view
as select
e.employee_id,
e.job_id,
e.manager_id,
e.department_id,
d.location_id,
e.first_name,
e.last_name,
e.salary,
e.commission_pct,
d.department_name,
j.job_title,
e.hire_date
from employees e
join departments d on (e.department_id = d.department_id)
join jobs j on (j.job_id = e.job_id);
另外,PostgreSQL 還提供了ALTER VIEW
語句修改視圖的屬性。例如以下語句用於修改視圖的名稱:
alter view emp_details_view rename to emp_info_view;
該語句將視圖 emp_details_view 重命名爲 emp_info_view。
ALTER VIEW
語句還提供了其他的修改功能,例如設置字段的默認值、修改視圖所屬的模式等,具體可以參考官方文檔。
刪除視圖
使用DROP VIEW語句刪除一個已有的視圖:
DROP VIEW [ IF EXISTS ] name [ CASCADE | RESTRICT ];
其中,IF EXISTS 可以避免刪除一個不存在的視圖時產生錯誤;CASCADE 表示級聯刪除依賴於該視圖的對象;RESTRICT 表示如果存在依賴對象則提示錯誤信息,這是默認值。
我們將視圖 emp_info_view 刪除:
drop view emp_info_view;
遞歸視圖
在專欄的第 20 篇中,我們介紹了通用表表達式(CTE),包括使用 RECURSIVE 關鍵字實現遞歸查詢。與此類似,視圖的定義中也可以使用 RECURSIVE:
CREATE RECURSIVE VIEW view_name (column_names) AS query;
需要注意的是,遞歸視圖需要指定字段的名稱 column_names。以上語句實際上等價於:
CREATE VIEW view_name AS
WITH RECURSIVE cte_name (column_names) AS (query)
SELECT column_names FROM cte_name;
我們將第 20 篇中通過遞歸 CTE 遍歷組織結構的查詢語句定義爲一個遞歸視圖:
create recursive view employee_path(employee_id, employee_name, path) as
select employee_id, CONCAT(first_name, ',', last_name), CONCAT(first_name, ',', last_name) as path
from employees
where manager_id is null
union all
select e.employee_id, CONCAT(e.first_name, ',', e.last_name), CONCAT(ep.path, '->', e.first_name, ',', e.last_name)
from employee_path ep
join employees e on ep.employee_id = e.manager_id;
可更新視圖
如果一個視圖滿足以下條件:
- 視圖定義的 FROM 子句中只包含一個表或者可更新視圖;
- 視圖定義的最頂層查詢語句中不包含以下子句:GROUP BY、HAVING、LIMIT、OFFSET、DISTINCT、WITH、UNION、INTERSECT 以及 EXCEPT;
- SELECT 列表中不包含窗口函數、集合函數或者聚合函數(例如 SUM、COUNT、AVG 等)。
那麼該視圖被稱爲可更新視圖(updatable view),意味着我們可以對其執行 INSERT、UPDATE 以及 DELETE 語句。PostgreSQL 會將這些操作轉換爲對底層表的操作。
我們創建一個視圖 employees_it :
create view employees_it as
select employee_id,
first_name,
last_name,
email,
phone_number,
hire_date,
job_id,
manager_id,
department_id
from employees
where department_id = 60;
視圖 employees_it 只包含了 IT 部門的員工信息,並且不包含員工的月薪字段。該視圖目前只能查詢到 5 條數據:
select employee_id,first_name, last_name from employees_it;
employee_id|first_name|last_name|
-----------|----------|---------|
103|Alexander |Hunold |
104|Bruce |Ernst |
105|David |Austin |
106|Valli |Pataballa|
107|Diana |Lorentz |
我們通過視圖 employees_it 爲 employees 表增加一個員工:
INSERT INTO employees_it
(employee_id, first_name, last_name, email, phone_number, hire_date, job_id, manager_id, department_id)
VALUES(207, 'Tony', 'Dong', 'DONG', '590.423.5568', '2020-05-06', 'IT_PROG', 103, 60);
select employee_id,first_name, last_name from employees_it;
employee_id|first_name|last_name|
-----------|----------|---------|
103|Alexander |Hunold |
104|Bruce |Ernst |
105|David |Austin |
106|Valli |Pataballa|
107|Diana |Lorentz |
207|Tony |Dong |
需要注意,不在視圖定義中的字段不能通過視圖進行修改。以下語句嘗試通過視圖 employees_it 修改員工的月薪:
update employees_it
set salary = 5000
where employee_id = 207;
SQL Error [42703]: ERROR: column "salary" of relation "employees_it" does not exist
Position: 26
不在視圖定義中的字段不能通過視圖進行操作。
我們將新增的員工從 employees 表中刪除:
delete from employees_it
where employee_id = 207;
WITH CHECK OPTION
我們再次通過視圖 employees_it 爲 employees 表增加一個員工,此時將部門編號改爲 80:
INSERT INTO employees_it
(employee_id, first_name, last_name, email, phone_number, hire_date, job_id, manager_id, department_id)
VALUES(207, 'Tony', 'Dong', 'DONG', '590.423.5568', '2020-05-06', 'IT_PROG', 149, 80);
select employee_id,first_name, last_name
from employees_it
where employee_id = 207;
employee_id|first_name|last_name|
-----------|----------|---------|
select employee_id,first_name, last_name
from employees
where employee_id = 207;
employee_id|first_name|last_name|
-----------|----------|---------|
207|Tony |Dong |
新增加的員工不屬於 IT 部門,增加之後無法通過視圖查詢,但是可以通過 employees 表查詢。
爲了防止通過視圖插入或者修改視圖不可見的數據,可以使用 WITH CHECK OPTION 選項:
create or replace view employees_it as
select employee_id,
first_name,
last_name,
email,
phone_number,
hire_date,
job_id,
manager_id,
department_id
from employees
where department_id = 60
with check option;
再次通過視圖 employees_it 爲 employees 表增加一個員工:
delete from employees
where employee_id = 207;
INSERT INTO employees_it
(employee_id, first_name, last_name, email, phone_number, hire_date, job_id, manager_id, department_id)
VALUES(207, 'Tony', 'Dong', 'DONG', '590.423.5568', '2020-05-06', 'IT_PROG', 149, 80);
SQL Error [44000]: ERROR: new row violates check option for view "employees_it"
Detail: Failing row contains (207, Tony, Dong, DONG, 590.423.5568, 2020-05-06, IT_PROG, null, null, 149, 80).
執行結果顯示違反檢查選項,無法插入數據。
進一步來說,WITH CASCADED CHECK OPTION 選項會對視圖以及它所依賴的其他視圖進行級聯檢查;WITH LOCAL CHECK OPTION 選項只對當前視圖進行檢查。默認爲 CASCADED。
歡迎點贊👍、評論📝、收藏❤️!