6

问题

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

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

架构

这些是所涉及的简化表:

  • taccount_no正在迁移其列的目标表。
  • u包含account_no_oldaccount_no_new映射的迁移指令表
  • vcontract_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: 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

更新视图不​​起作用:

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

直观地说,我会在这里应用传递闭包,这应该保证对于 in 中的每个更新行,in和 int最多只能有 1 行。但显然,Oracle 不承认这一点,因此以下语句不起作用:uvUPDATE

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。因此视图应该是可更新的。有没有办法重写这个查询,让 Oracle 相信我的判断?

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

4

3 回答 3

1

您可以定义一个临时U表,其中包含来自和的预连接数据V

使用唯一索引支持它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
;
于 2018-08-24T11:32:56.467 回答
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;

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;

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;

db<>小提琴演示3

于 2018-09-28T19:37:39.990 回答
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);

鲍比

于 2018-10-01T21:38:41.010 回答