Обновление, по-видимому, ключевого представления в 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);
Бобби