Обновление, по-видимому, ключевого представления в 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_old→account_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;
вы можете определить временное таблица, содержащая предварительно Соединенные данные из 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);
Бобби