ORA-38104:无法更新ON子句中引用的列

时间:2011-05-05 16:11:16

标签: sql oracle merge ora-38104

我有一个带有删除标志的简单表(记录应该在此列中更新而不是删除):

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-clause移动到update-statement。< / p>

6 个答案:

答案 0 :(得分:43)

与接受的响应相反,实际上有一种方法可以解决这个问题:将违规位移出ON子句并进入update语句的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);

答案 1 :(得分:2)

我怀疑你在这种情况下使用射击然后看算法会更好。

根据您预期的更常见情况,可以:

  • 更新,如果没有更新行,请插入;或
  • 插入,如果存在密钥违规,请更新。

答案 2 :(得分:2)

将列放入某些表达式中并重命名似乎可行。在下面的示例中,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);

I have blogged about these workarounds (and execution plans) here

答案 3 :(得分:1)

我们还需要考虑以下情况,

如果匹配记录与IDELETED = 0匹配,我希望将主键违规作为例外,这就是我无法移动的原因&#34; TARGET.ISDELETED = 1&#34 ;从on-clause到update-statement。

所以确切的解决方案如下,

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;

答案 4 :(得分:1)

这不行吗?

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

答案 5 :(得分:-1)

“现在可以通过对这些语句使用WHERE子句来进行条件插入和更新。” http://www.oracle-base.com/articles/10g/merge-enhancements-10g.php