Oracle中看似关键的保留视图的更新引发了ORA-01779

时间:2018-08-24 09:10:38

标签: sql oracle merge updatable-views

问题

我正在尝试将性能低下的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

大多数情况下性能较低,因为对大型(1亿行)表t进行了两次昂贵的访问

架构

这些是涉及的简化表:

  • t要迁移其account_no列的目标表。
  • u迁移说明表,其中包含account_no_oldaccount_no_new映射
  • v辅助表,用于建模contract_idtenant_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

来解决此问题。

替代合并无效:

  

ORA-38104:“ ON”子句中引用的列无法更新

请注意,无法避免自我联接,因为这种替代的等效查询会导致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:无法修改映射到非键保留表的列

直觉上,我将在此处应用传递闭包,这应该保证对于t中的每个更新行,uv中最多只能有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。在12c上似乎无法添加未记录的提示/*+BYPASS_UJVC*/

如何告诉Oracle该视图是密钥保存的?

我认为该视图仍然是键保留的,即对于t中的每一行,v中有确切行,因此 at u中最多一行。因此,观点应该是可更新的。有什么方法可以重写此查询以使Oracle相信我的判断?

还是我忽略了其他语法,这些语法阻止MERGE语句对t的双重访问?

3 个答案:

答案 0 :(得分:1)

您可以定义一个临时表,其中包含来自UV的预联接数据。

contract_id, account_no_old上带有唯一索引(应该是唯一的)来支持它。

然后,您可以在可更新的联接视图中使用此临时表。

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
;

答案 1 :(得分:1)

  

是否可以重写此查询以使Oracle相信我的判断?

通过在目标中引入帮助器列,我设法“说服” Oracle进行合并:

/sys/class/net

db<>fiddle demo


编辑

上述想法的变体-子查询直接移至/sys/devices/virtual部分:

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;

db<>fiddle demo2

相关文章:Columns referenced in the ON Clause cannot be updated

编辑2:

ON

db<>fiddle demo3

答案 2 :(得分:1)

尝试通过更简单的更新来做到这一点。仍然需要一个子选择。

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);

鲍比