对 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:

架构是:

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 中的每个更新行,在 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。添加未记录的提示 /*+BYPASS_UJVC*/ 似乎不再适用于 12c。

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

在我看来,视图仍然保留密钥,即对于 t 中的每一行,在 v 正好 一行,并且因此 最多 一行 u。因此,视图应该是 updatable。有什么方法可以重写这个查询,让 Oracle 相信我的判断吗?

或者是否有任何我忽略的其他语法阻止 MERGE 语句对 t 的双重访问?

您可以定义一个 临时 table 包含来自 UV.

的预连接数据

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;

db<>fiddle demo


编辑

上述想法的变体 - 子查询直接移动到 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;

db<>fiddle demo2

相关文章: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;

db<>fiddle demo3

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

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

鲍比