【问题标题】:ORA-38104: Columns referenced in the ON Clause cannot be updatedORA-38104: ON 子句中引用的列无法更新
【发布时间】:2011-08-19 13:04:19
【问题描述】:

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

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 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。

【问题讨论】:

  • 我确认在 SQL Server 上运行良好(对 Oracle 感到羞耻:)
  • 谢谢,我把命令改成如下: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;

标签: sql oracle merge ora-38104


【解决方案1】:

我怀疑在这种情况下你最好使用先拍后看算法。

取决于您期望的更常见的情况,或者:

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

【讨论】:

  • +1,好多了,因为你不能做 OP 想做的事 ;-) 至少,不是没有一些中间临时表,如这里发布的答案:asktom.oracle.com/pls/asktom/…跨度>
【解决方案2】:

与接受的响应相反,实际上有一种方法可以解决这个问题:将有问题的位从 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);

【讨论】:

  • 它并不适用于所有情况。如果 ISDELETED 可以有 3 个不同的值,它不会进行更新,如果 ISDELETED 获得第三个可能的值和相同的 ID,您将跳过插入。例如,如果 ISDELETED = 2 具有相同的 ID。您将进行 0 次更新,并且您将跳过插入。
  • 根据ISDELETED = 1 谓词的选择性,这可能会导致很大的开销,因为TARGETSOURCE 之间的连接将实现太多行。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    我们还需要考虑以下场景,

    如果有与IDELETED = 0 匹配的记录,我希望主键违规作为例外,这就是为什么我不能将“TARGET.ISDELETED = 1”从 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;
    

    【讨论】:

      【解决方案5】:

      将列放在某个表达式中并重命名它似乎有效。在下面的例子中,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.

      【讨论】:

      • 不幸的是,您的最后一个示例不起作用(至少在 18c XE 中不起作用),但是,在 ON 子句中添加一个简单的 OR 1=2 足以掩盖 ORA-38104 错误
      【解决方案6】:

      这不就行了吗?

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

      【讨论】:

        猜你喜欢
        • 2014-06-14
        • 1970-01-01
        • 2021-08-31
        • 1970-01-01
        • 2018-02-10
        • 1970-01-01
        • 1970-01-01
        • 2018-05-07
        • 1970-01-01
        相关资源
        最近更新 更多