所谓联接视图,就是有多张基表的视图(From子句中引用了多张表或者视图)
通常情况下,只有满足了一定的条件,我们才能更新联接视图,比如
1、视图定义中没有
WITH READ ONLY选项
2、一次只能更新一张基表
3、只能更新映射到键值保存表(key-preserved table)的字段
4、满足其它简单视图的更新条件(比如没有聚合等)
什么是键值保存表?
如果基表的每条记录在视图结果中最多出现1次(或者不出现,即不会重复),那么该基表就是键值保存表。
例如:
CREATE VIEW staff_dept_10_30 AS
SELECT e.employee_id, e.last_name, e.job_id, d.department_id, d.department_name
FROM employees e, departments d
WHERE e.department_id IN (10, 30)
AND e.department_id = d.department_id;
在该视图定义中,对于empoyees中的每条记录,在最终的视图结果中最多只能出现一次(department_id不为10、30的则不会出现);
而departments表中的记录则会重复出现多次,所以对于此视图,employees表为键值保存表,而departments表则不是
注:department_id为departments的主键
所以staff_dept_10_30视图中的employee_id,last_name,job_id可以更新(employees表是键值保存表),而department_id则不可更新(departments表不是键值保存表)
我们可以通过以下SQL来进行验证:
SELECT TABLE_NAME, COLUMN_NAME, UPDATABLE
FROM USER_UPDATABLE_COLUMNS
WHERE TABLE_NAME = 'STAFF_DEPT_10_30';
TABLE_NAME COLUMN_NAME UPD
------------------------------ ------------------------------ ---
STAFF_DEPT_10_30 EMPLOYEE_ID YES
STAFF_DEPT_10_30 LAST_NAME YES
STAFF_DEPT_10_30 JOB_ID YES
STAFF_DEPT_10_30 DEPARTMENT_ID NO
STAFF_DEPT_10_30 DEPARTMENT_NAME NO
OK,让我们来验证一下
只更新employees的字段
SQL> update STAFF_DEPT_10_30 set last_name=last_name||'a';
7 rows updated
只更新departments的字段
SQL> update STAFF_DEPT_10_30 set DEPARTMENT_NAME=DEPARTMENT_NAME||'b';
update STAFF_DEPT_10_30 set DEPARTMENT_NAME=DEPARTMENT_NAME||'b'
ORA-01779: 无法修改与非键值保存表对应的列
那么Oracle怎么知道employees是键值保存表,而departments不是呢?
很简单,前面提到department_id为departments的主键;当表A通过另表B的主键来join表B里,表A的记录肯定不会重复!但表B的记录则不一定,所以此时表A是键值保存表,
而表B则不是。
那这样一来是不是我们就可以在表A的join字段上也建上主键(或者唯一索引)就也能让表B变成键值保存表呢?答案是肯定的
例如:
创建EMP表,让其每个DEPARTMENT_ID只有一条记录
create table emp
select *
from (select a.*,
row_number() over(partition by DEPARTMENT_ID order by EMPLOYEE_ID) lvl
from employees a)
where lvl = 1
在DEPARTMENT_ID上创建唯一索引
-- Create/Recreate indexes
create unique index EMP_UK on EMP (department_id);
修改STAFF_DEPT_10_30的定义,让其引用EMP表
CREATE OR REPLACE VIEW staff_dept_10_30 AS
SELECT e.employee_id, e.last_name, e.job_id, d.department_id, d.department_name
FROM emp e, departments d
WHERE e.department_id IN (10, 30)
AND e.department_id = d.department_id;
重新查询
SELECT TABLE_NAME, COLUMN_NAME, UPDATABLE
FROM USER_UPDATABLE_COLUMNS
WHERE TABLE_NAME = 'STAFF_DEPT_10_30';
TABLE_NAME COLUMN_NAME UPD
------------------------------ ------------------------------ ---
STAFF_DEPT_10_30 EMPLOYEE_ID YES
STAFF_DEPT_10_30 LAST_NAME YES
STAFF_DEPT_10_30 JOB_ID YES
STAFF_DEPT_10_30 DEPARTMENT_ID YES
STAFF_DEPT_10_30 DEPARTMENT_NAME YES
OK,测试通过!
到现在为止,我们都遵循了前面提到的一般原则,则一次只更新一张基表以及只能更新映射到键值保存表(key-preserved table)的字段,
但是如果我们想要同时更新staff_dept_10_30的last_name和department_name应该怎么办呢?
答案是使用instead of类型的trigger
将STAFF_DEPT_10_30的定义还原
CREATE OR REPLACE VIEW staff_dept_10_30 AS
SELECT e.employee_id, e.last_name, e.job_id, d.department_id, d.department_name
FROM employees e, departments d
WHERE e.department_id IN (10, 30)
AND e.department_id = d.department_id;
在视图上创建TRIGGER
CREATE OR REPLACE TRIGGER STAFF_DEPT_10_30_TRI
INSTEAD OF UPDATE ON STAFF_DEPT_10_30
FOR EACH ROW
DECLARE
BEGIN
UPDATE EMPLOYEES
SET LAST_NAME = :NEW.LAST_NAME,
JOB_ID = :NEW.JOB_ID,
DEPARTMENT_ID =:NEW.DEPARTMENT_ID
WHERE employee_id = :OLD.employee_id;
UPDATE DEPARTMENTS
SET DEPARTMENT_NAME = :NEW.DEPARTMENT_NAME
WHERE DEPARTMENT_ID = :OLD.DEPARTMENT_ID;
END STAFF_DEPT_10_30_TRI;
OK,测试一下!
SQL> update STAFF_DEPT_10_30
2 set last_name = 'a', DEPARTMENT_NAME = DEPARTMENT_NAME || 'b'
3 where employee_id = 200
1 row updated
测试通过!