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
谓词的选择性,这可能会导致很大的开销,因为TARGET
和SOURCE
之间的连接将实现太多行。【参考方案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 子句中引用的列无法更新的主要内容,如果未能解决你的问题,请参考以下文章