ОБНОВЛЕНИЕ на казалось бы сохраняющем ключ представление в Oracle поднимает ORA-01779 - PullRequest
0 голосов
/ 24 августа 2018

Задача

Я пытаюсь реорганизовать неэффективный оператор 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_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 решило бы проблему, запретив 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: невозможно изменить столбец, который сопоставляется с таблицей без сохранения ключа

Интуитивно я бы применил транзитивное замыкание здесь, что должно гарантировать, что для каждой обновленной строки в 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 ]

0 голосов
/ 28 сентября 2018

Есть ли способ переписать этот запрос, чтобы заставить 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 <> демо fiddle


РЕДАКТИРОВАТЬ

Вариант идеи выше - подзапрос перемещен непосредственно в ON part:

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 <> fiddle demo2

Связанная статья: Столбцы, указанные в предложении ON, не могут быть обновлены

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

дБ <> Скрипка демо3

0 голосов
/ 02 октября 2018

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

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

Бобби

0 голосов
/ 24 августа 2018

Вы можете определить временную таблицу , содержащую предварительно объединенные данные из 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
;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...