在Oracle中看似关键的保留视图的更新提出了ORA-01779

Luk*_*der 6 sql oracle merge updatable-views

问题

我正在尝试将低性能MERGE语句重构为UPDATEOracle 12.1.0.2.0中的语句.该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
Run Code Online (Sandbox Code Playgroud)

它大多是低性能的,因为对大型(100M行)表有两个昂贵的访问 t

架构

这些是涉及的简化表:

  • taccount_no正在迁移其列的目标表.
  • u迁移指令表包含account_no_oldaccount_no_new映射
  • v建模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)
);
Run Code Online (Sandbox Code Playgroud)

我无法修改架构.我知道添加t.tenant_id可以通过阻止JOIN来解决问题v

替代MERGE不起作用:

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
Run Code Online (Sandbox Code Playgroud)

UPDATE视图不起作用:

ORA-01779:无法修改映射到非密钥保留表的列

直观地说,我会在这里适用传递闭包,这应该保证每个更新的行中t,有能里最多只有1行uv.但显然,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
Run Code Online (Sandbox Code Playgroud)

以上提出ORA-01779./*+BYPASS_UJVC*/在12c上添加未记录的提示似乎不再起作用.

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

在我看来,认为仍是关键保护,即在每一行t,有恰好一排v,因此至多在一排u.因此,视图应该是可更新的.有没有办法重写此查询以使Oracle信任我的判断?

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

Luk*_*zda 1

有没有办法重写这个查询,让Oracle相信我的判断?

我成功地“说服”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;
Run Code Online (Sandbox Code Playgroud)

db<>小提琴演示


编辑

上述想法的变体 - 子查询直接移动到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;
Run Code Online (Sandbox Code Playgroud)

db<>小提琴演示2

相关文章:ON 子句中引用的列无法更新

编辑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;
Run Code Online (Sandbox Code Playgroud)

db<>小提琴演示3