ORA-38104: столбцы, указанные в предложении ON, не могут быть обновлены - PullRequest
27 голосов
/ 05 мая 2011

у меня есть простая таблица с флагом удаления (записи должны обновляться в этом столбце, а не удаляться):

create table PSEUDODELETETABLE
(
  ID        NUMBER(8) not null, -- PKEY
  NAME      VARCHAR2(50) not null,
  ISDELETED NUMBER(1) default 0 not null
)

При вставке новых записей я должен проверить, есть ли уже запись, соответствующая первичному ключу, но имеющая ISDELETED = 1. В этом случае я должен изменить ISDELETED на 0 и обновить другие столбцы. Поэтому я использую следующее Merge-Statement:

merge into ET.PSEUDODELETETABLE TARGET
using (select 1 as ID, 'Horst' as NAME from sys.dual) SOURCE
on (TARGET.ISDELETED = 1 and SOURCE.ID = TARGET.ID)
when matched then
  update set ISDELETED = 0, NAME = SOURCE.NAME
when not matched then
  insert values (SOURCE.ID, SOURCE.NAME, 0);

На Sql-Server он прекрасно работает, но Oracle говорит:

ORA-38104: Columns referenced in the ON Clause cannot be updated: TARGET.ISDELETED

Если есть соответствующая запись с IDELETED = 0, я хочу, чтобы нарушение первичного ключа было исключением, поэтому я не могу переместить "TARGET.ISDELETED = 1" из предложения on в оператор update.

Ответы [ 6 ]

43 голосов
/ 29 ноября 2011

Вопреки принятому ответу, на самом деле есть способ справиться с этим: переместить нарушающий бит из предложения ON в предложение WHERE оператора обновления:

merge into ET.PSEUDODELETETABLE TARGET
using (select 1 as ID, 'Horst' as NAME from sys.dual) SOURCE
on (SOURCE.ID = TARGET.ID)
when matched then
  update 
      set ISDELETED = 0, 
      NAME = SOURCE.NAME
  where TARGET.ISDELETED = 1 -- Magic!
when not matched then
  insert 
      values (SOURCE.ID, SOURCE.NAME, 0);
3 голосов
/ 05 мая 2011

Я подозреваю, что в этом случае вам лучше с алгоритмом «стреляй, а потом посмотри».

В зависимости от того, что вы ожидаете, будет более частым случаем:* Обновление, и если строки не обновляются, вставьте;или

Вставьте, и, если есть нарушение ключа, обновите.
2 голосов
/ 02 января 2019

Помещение столбца в какое-либо выражение и переименование, похоже, работает. В приведенном ниже примере ISDELETED_ и ISDELETED фактически одно и то же:

merge into (
  select nvl(ISDELETED, ISDELETED) as ISDELETED_, ISDELETED, ID, 
  from ET.PSEUDODELETETABLE
) TARGET
using (select 1 as ID, 'Horst' as NAME from sys.dual) SOURCE
on (TARGET.ISDELETED_ = 1 and SOURCE.ID = TARGET.ID) -- Use the renamed version here
when matched then
  update set ISDELETED = 0, NAME = SOURCE.NAME       -- Use the original version here
when not matched then
  insert values (SOURCE.ID, SOURCE.NAME, 0);

Примечание:

  • Просто переименование не работает. Парсер кажется достаточно «умным», чтобы обнаружить, что это все тот же столбец. Но переименование и выражение в «глупом» выражении превосходит синтаксический анализатор.
  • Это, очевидно, обходится дорого. Индексы не могут быть легко использованы в переименованном столбце, проверьте план выполнения. В этом конкретном примере это может работать
  • Oracle может «исправить» это в будущем (и сделать обнаружение ORA-38104 более последовательным), поэтому этот обходной путь может прекратиться.

Кажется, это также работает, но определенно не позволяет разумно использовать индексы (проверьте еще раз свою версию Oracle):

merge into ET.PSEUDODELETETABLE TARGET
using (select 1 as ID, 'Horst' as NAME from sys.dual) SOURCE
on ((select TARGET.ISDELETED from dual) = 1 and SOURCE.ID = TARGET.ID)
when matched then
  update set ISDELETED = 0, NAME = SOURCE.NAME
when not matched then
  insert values (SOURCE.ID, SOURCE.NAME, 0);

Даже это работает (что вызывает серьезные сомнения относительно проверки ORA-38104 в целом)!

merge into ET.PSEUDODELETETABLE TARGET
using (select 1 as ID, 'Horst' as NAME from sys.dual) SOURCE
on ((TARGET.ISDELETED, 'dummy') = ((1, 'dummy')) and SOURCE.ID = TARGET.ID)
when matched then
  update set ISDELETED = 0, NAME = SOURCE.NAME
when not matched then
  insert values (SOURCE.ID, SOURCE.NAME, 0);

Я уже писал об этих обходных путях (и планах выполнения) здесь .

1 голос
/ 02 января 2019

Разве это не работает?

merge into (select * from ET.PSEUDODELETETABLE where ISDELETED = 1) TARGET
using (select 1 as ID, 'Horst' as NAME from sys.dual) SOURCE
on (SOURCE.ID = TARGET.ID)
when matched then
  update set ISDELETED = 0, NAME = SOURCE.NAME
when not matched then
  insert values (SOURCE.ID, SOURCE.NAME, 0);
1 голос
/ 05 марта 2016

Нам также необходимо рассмотреть следующий сценарий:

Если есть совпадающая запись с IDELETED = 0, я хочу, чтобы нарушение первичного ключа было исключением, поэтому я не могу двигаться "TARGET.ISDELETED= 1 "от предложения on до оператора update.

Таким образом, точное решение, как показано ниже,

begin 
    update ET.PSEUDODELETETABLE set ISDELETED = 0, NAME = 'Horst' 
    where ISDELETED = 1 and ID = 1; 
    if (sql%rowcount = 0) then 
        insert into ET.PSEUDODELETETABLE values (1, 'Horst', 0); 
    end if; 
end;
0 голосов
/ 14 октября 2014

«Условные вставки и обновления теперь возможны при использовании предложения WHERE в этих операторах».http://www.oracle -base.com / статьи / 10г / слияния-усовершенствования-10g.php

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...