Обновление, по-видимому, ключевого представления в Oracle вызывает ORA-01779

я пытаюсь рефакторинг с низкой производительностью MERGE заявление к UPDATE инструкция в Oracle 12.1.0.2.0. The 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

это в основном низкая производительность, потому что есть два дорогих доступа к большой таблице (100 м строк)t

- схемы

это упрощенные таблицы, связанные:

  • t целевая таблица, чья будет перенесенный.
  • 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)
);

я не могу изменить схему. Я знаю, что добавление t.tenant_id решит проблему, предотвратив присоединение к 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

вид обновления не работает:

ORA-01779: не удается изменить столбец, который сопоставляется с неключевой таблицей

интуитивно я бы применил транзитивное закрытие здесь, что должно гарантировать, что для каждой обновленной строки в t, может быть только не более 1 строки в u и v. Но, по-видимому, 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. Таким образом, представление должно быть обновляемым. Есть ли способ переписать этот запрос, чтобы заставить Oracle доверять моему суждению?

или есть другой синтаксис я с видом, что мешает MERGE двойной доступ оператора к t?

3 ответов


есть ли способ переписать этот запрос, чтобы заставить Oracle доверять моему суждению?

мне удалось "убедить" Oracle сделать слияние, введя вспомогательный столбец в target:

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


вы можете определить временное таблица, содержащая предварительно Соединенные данные из 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
;

попытка сделать это с более простым обновлением. По-прежнему требуется подселект.

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

Бобби