对 Oracle 中看似关键的保留视图的更新引发了 ORA-01779
UPDATE on seemingly key preserving view in Oracle raises ORA-01779
问题
我正在尝试将低性能的 MERGE
语句重构为 Oracle 12.1.0.2.0 中的 UPDATE
语句。 MERGE
语句如下所示:
MERGE INTO t
USING (
SELECT t.rowid rid, u.account_no_new
FROM t, u, v
WHERE t.account_no = u.account_no_old
AND t.contract_id = v.contract_id
AND v.tenant_id = u.tenant_id
) s
ON (t.rowid = s.rid)
WHEN MATCHED THEN UPDATE SET t.account_no = s.account_no_new
它主要是低性能的,因为有两个对大型(100M 行)的昂贵访问 table t
架构
这些是涉及的简化 table:
t
正在迁移其 account_no
列的目标 table。
u
迁移指令table包含一个account_no_old
→account_no_new
映射
v
辅助 table 模拟 contract_id
和 tenant_id
之间的一对一关系
架构是:
CREATE TABLE v (
contract_id NUMBER(18) NOT NULL PRIMARY KEY,
tenant_id NUMBER(18) NOT NULL
);
CREATE TABLE t (
t_id NUMBER(18) NOT NULL PRIMARY KEY,
-- tenant_id column is missing here
account_no NUMBER(18) NOT NULL,
contract_id NUMBER(18) NOT NULL REFERENCES v
);
CREATE TABLE u (
u_id NUMBER(18) NOT NULL PRIMARY KEY,
tenant_id NUMBER(18) NOT NULL,
account_no_old NUMBER(18) NOT NULL,
account_no_new NUMBER(18) NOT NULL,
UNIQUE (tenant_id, account_no_old)
);
我无法修改架构。我知道添加 t.tenant_id
可以通过阻止 JOIN 到 v
来解决问题
替代 MERGE 不起作用:
ORA-38104: Columns referenced in the ON Clause cannot be updated
注意,无法避免自连接,因为这种替代的等效查询会导致 ORA-38104:
MERGE INTO t
USING (
SELECT u.account_no_old, u.account_no_new, v.contract_id
FROM u, v
WHERE v.tenant_id = u.tenant_id
) s
ON (t.account_no = s.account_no_old AND t.contract_id = s.contract_id)
WHEN MATCHED THEN UPDATE SET t.account_no = s.account_no_new
UPDATE 视图不起作用:
ORA-01779: cannot modify a column which maps to a non-key-preserved table
直觉上,我会在这里应用传递闭包,这应该保证对于 t
中的每个更新行,在 u
和 v
中最多只能有 1 行.但显然,Oracle 不识别这一点,因此以下 UPDATE
语句不起作用:
UPDATE (
SELECT t.account_no, u.account_no_new
FROM t, u, v
WHERE t.account_no = u.account_no_old
AND t.contract_id = v.contract_id
AND v.tenant_id = u.tenant_id
)
SET account_no = account_no_new
以上提出ORA-01779
。添加未记录的提示 /*+BYPASS_UJVC*/
似乎不再适用于 12c。
如何告诉 Oracle 视图是密钥保留的?
在我看来,视图仍然保留密钥,即对于 t
中的每一行,在 v
中 正好 一行,并且因此 最多 一行 u
。因此,视图应该是 updatable。有什么方法可以重写这个查询,让 Oracle 相信我的判断吗?
或者是否有任何我忽略的其他语法阻止 MERGE
语句对 t
的双重访问?
您可以定义一个 临时 table 包含来自 U
和 V
.
的预连接数据
用 contract_id, account_no_old
上的 unique 索引支持它(应该是唯一的)。
然后您可以在可更新的连接视图中使用这个临时 table。
create table tmp as
SELECT v.contract_id, u.account_no_old, u.account_no_new
FROM u, v
WHERE v.tenant_id = u.tenant_id;
create unique index tmp_ux1 on tmp ( contract_id, account_no_old);
UPDATE (
SELECT t.account_no, tmp.account_no_new
FROM t, tmp
WHERE t.account_no = tmp.account_no_old
AND t.contract_id = tmp.contract_id
)
SET account_no = account_no_new
;
Is there any way to rewrite this query to make Oracle trust my judgement?
我已经设法 "convince" Oracle 通过在目标中引入辅助列来进行 MERGE:
MERGE INTO (SELECT (SELECT t.account_no FROM dual) AS account_no_temp,
t.account_no, t.contract_id
FROM t) t
USING (
SELECT u.account_no_old, u.account_no_new, v.contract_id
FROM u, v
WHERE v.tenant_id = u.tenant_id
) s
ON (t.account_no_temp = s.account_no_old AND t.contract_id = s.contract_id)
WHEN MATCHED THEN UPDATE SET t.account_no = s.account_no_new;
编辑
上述想法的变体 - 子查询直接移动到 ON
部分:
MERGE INTO (SELECT t.account_no, t.contract_id FROM t) t
USING (
SELECT u.account_no_old, u.account_no_new, v.contract_id
FROM u, v
WHERE v.tenant_id = u.tenant_id
) s
ON ((SELECT t.account_no FROM dual) = s.account_no_old
AND t.contract_id = s.contract_id)
WHEN MATCHED THEN UPDATE SET t.account_no = s.account_no_new;
相关文章:Columns referenced in the ON Clause cannot be updated
编辑 2:
MERGE INTO (SELECT t.account_no, t.contract_id FROM t) t
USING (SELECT u.account_no_old, u.account_no_new, v.contract_id
FROM u, v
WHERE v.tenant_id = u.tenant_id) s
ON((t.account_no,t.contract_id,'x')=((s.account_no_old,s.contract_id,'x')) OR 1=2)
WHEN MATCHED THEN UPDATE SET t.account_no = s.account_no_new;
尝试通过更简单的更新来做到这一点。仍然需要子选择。
update t
set t.account_no = (SELECT u.account_no_new
FROM u, v
WHERE t.account_no = u.account_no_old
AND t.contract_id = v.contract_id
AND v.tenant_id = u.tenant_id);
鲍比
问题
我正在尝试将低性能的 MERGE
语句重构为 Oracle 12.1.0.2.0 中的 UPDATE
语句。 MERGE
语句如下所示:
MERGE INTO t
USING (
SELECT t.rowid rid, u.account_no_new
FROM t, u, v
WHERE t.account_no = u.account_no_old
AND t.contract_id = v.contract_id
AND v.tenant_id = u.tenant_id
) s
ON (t.rowid = s.rid)
WHEN MATCHED THEN UPDATE SET t.account_no = s.account_no_new
它主要是低性能的,因为有两个对大型(100M 行)的昂贵访问 table t
架构
这些是涉及的简化 table:
t
正在迁移其account_no
列的目标 table。u
迁移指令table包含一个account_no_old
→account_no_new
映射v
辅助 table 模拟contract_id
和tenant_id
之间的一对一关系
架构是:
CREATE TABLE v (
contract_id NUMBER(18) NOT NULL PRIMARY KEY,
tenant_id NUMBER(18) NOT NULL
);
CREATE TABLE t (
t_id NUMBER(18) NOT NULL PRIMARY KEY,
-- tenant_id column is missing here
account_no NUMBER(18) NOT NULL,
contract_id NUMBER(18) NOT NULL REFERENCES v
);
CREATE TABLE u (
u_id NUMBER(18) NOT NULL PRIMARY KEY,
tenant_id NUMBER(18) NOT NULL,
account_no_old NUMBER(18) NOT NULL,
account_no_new NUMBER(18) NOT NULL,
UNIQUE (tenant_id, account_no_old)
);
我无法修改架构。我知道添加 t.tenant_id
可以通过阻止 JOIN 到 v
替代 MERGE 不起作用:
ORA-38104: Columns referenced in the ON Clause cannot be updated
注意,无法避免自连接,因为这种替代的等效查询会导致 ORA-38104:
MERGE INTO t
USING (
SELECT u.account_no_old, u.account_no_new, v.contract_id
FROM u, v
WHERE v.tenant_id = u.tenant_id
) s
ON (t.account_no = s.account_no_old AND t.contract_id = s.contract_id)
WHEN MATCHED THEN UPDATE SET t.account_no = s.account_no_new
UPDATE 视图不起作用:
ORA-01779: cannot modify a column which maps to a non-key-preserved table
直觉上,我会在这里应用传递闭包,这应该保证对于 t
中的每个更新行,在 u
和 v
中最多只能有 1 行.但显然,Oracle 不识别这一点,因此以下 UPDATE
语句不起作用:
UPDATE (
SELECT t.account_no, u.account_no_new
FROM t, u, v
WHERE t.account_no = u.account_no_old
AND t.contract_id = v.contract_id
AND v.tenant_id = u.tenant_id
)
SET account_no = account_no_new
以上提出ORA-01779
。添加未记录的提示 /*+BYPASS_UJVC*/
似乎不再适用于 12c。
如何告诉 Oracle 视图是密钥保留的?
在我看来,视图仍然保留密钥,即对于 t
中的每一行,在 v
中 正好 一行,并且因此 最多 一行 u
。因此,视图应该是 updatable。有什么方法可以重写这个查询,让 Oracle 相信我的判断吗?
或者是否有任何我忽略的其他语法阻止 MERGE
语句对 t
的双重访问?
您可以定义一个 临时 table 包含来自 U
和 V
.
用 contract_id, account_no_old
上的 unique 索引支持它(应该是唯一的)。
然后您可以在可更新的连接视图中使用这个临时 table。
create table tmp as
SELECT v.contract_id, u.account_no_old, u.account_no_new
FROM u, v
WHERE v.tenant_id = u.tenant_id;
create unique index tmp_ux1 on tmp ( contract_id, account_no_old);
UPDATE (
SELECT t.account_no, tmp.account_no_new
FROM t, tmp
WHERE t.account_no = tmp.account_no_old
AND t.contract_id = tmp.contract_id
)
SET account_no = account_no_new
;
Is there any way to rewrite this query to make Oracle trust my judgement?
我已经设法 "convince" Oracle 通过在目标中引入辅助列来进行 MERGE:
MERGE INTO (SELECT (SELECT t.account_no FROM dual) AS account_no_temp,
t.account_no, t.contract_id
FROM t) t
USING (
SELECT u.account_no_old, u.account_no_new, v.contract_id
FROM u, v
WHERE v.tenant_id = u.tenant_id
) s
ON (t.account_no_temp = s.account_no_old AND t.contract_id = s.contract_id)
WHEN MATCHED THEN UPDATE SET t.account_no = s.account_no_new;
编辑
上述想法的变体 - 子查询直接移动到 ON
部分:
MERGE INTO (SELECT t.account_no, t.contract_id FROM t) t
USING (
SELECT u.account_no_old, u.account_no_new, v.contract_id
FROM u, v
WHERE v.tenant_id = u.tenant_id
) s
ON ((SELECT t.account_no FROM dual) = s.account_no_old
AND t.contract_id = s.contract_id)
WHEN MATCHED THEN UPDATE SET t.account_no = s.account_no_new;
相关文章:Columns referenced in the ON Clause cannot be updated
编辑 2:
MERGE INTO (SELECT t.account_no, t.contract_id FROM t) t
USING (SELECT u.account_no_old, u.account_no_new, v.contract_id
FROM u, v
WHERE v.tenant_id = u.tenant_id) s
ON((t.account_no,t.contract_id,'x')=((s.account_no_old,s.contract_id,'x')) OR 1=2)
WHEN MATCHED THEN UPDATE SET t.account_no = s.account_no_new;
尝试通过更简单的更新来做到这一点。仍然需要子选择。
update t
set t.account_no = (SELECT u.account_no_new
FROM u, v
WHERE t.account_no = u.account_no_old
AND t.contract_id = v.contract_id
AND v.tenant_id = u.tenant_id);
鲍比