ОБНОВЛЕНИЕ на казалось бы сохраняющем ключ представление в Oracle поднимает ORA-01779
проблема
Я пытаюсь рефакторинг низкоэффективных MERGE
заявление к UPDATE
заявление в Oracle 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
схема
Это упрощенные таблицы:
t
Целевая таблица, чьяaccount_no
столбец переносится.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
Альтернатива 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: невозможно изменить столбец, который сопоставляется с таблицей без сохранения ключа
Интуитивно я бы применил транзитивное замыкание здесь, что должно гарантировать, что для каждой обновленной строки в 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 ответа
Попытка сделать это с более простым обновлением. Все еще требует подвыбора.
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);
Бобби
Вы можете определить временную таблицу, содержащую предварительно объединенные данные из 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
;
Есть ли способ переписать этот запрос, чтобы заставить Oracle доверять моему мнению?
Мне удалось "убедить" Oracle сделать MERGE, введя вспомогательный столбец в 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;