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

Posted

技术标签:

【中文标题】ORA-38104: ON 子句中引用的列无法更新【英文标题】:ORA-38104: Columns referenced in the ON Clause cannot be updated 【发布时间】: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; 【参考方案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);

【讨论】:

以上是关于ORA-38104: ON 子句中引用的列无法更新的主要内容,如果未能解决你的问题,请参考以下文章

Oracle -- 更新 ON 子句中引用的确切列

更新连接中使用的列

SQL语句中,子句不能使用列别名问题

INSERT + SELECT + ON DUPLICATE KEY 与 SELECT 子句中的列别名

两者都在模型子句中的列之间引用

1.6 在WHERE子句中引用取别名的列