在 Oracle 更新语句中使用子查询而不是表名

Posted

技术标签:

【中文标题】在 Oracle 更新语句中使用子查询而不是表名【英文标题】:Using a subquery instead of a table name in an Oracle Update Statement 【发布时间】:2008-10-23 15:30:37 【问题描述】:

我需要编写一个使用多个表来确定要更新哪些行的更新语句,因为在 Oracle 中,不允许多个表。以下查询将返回“ORA-00971:缺少 SET 关键字”错误

UPDATE
  TABLE1 a,
  TABLE2 b
SET
  a.COL1 = 'VALUE'
WHERE
  a.FK = b.PK
  AND b.COL2 IN ('SET OF VALUES')

在oracle上查找UPDATE语句语法,发现如下link,说明可以使用子查询代替表名。

当我尝试编写这样的查询时,我得到了“ORA-01779:无法修改映射到非键保留表的列”

UPDATE
  (
    SELECT
      a.COL1
    FROM
      TABLE1 a,
      TABLE2 b
    WHERE
      a.FK = b.PK
      AND b.COL2 IN ('SET OF VALUES')
  ) update_tbl
SET
  update_tbl.COL1 = 'VALUE'

我确实使用 EXISTS 语句重写了查询(如下所示),它工作正常,但仍然想知道这是如何完成的。

UPDATE
  TABLE1 update_tbl
SET
  update_tbl.COL1 = 'VALUE'
WHERE
  EXISTS (
    SELECT
      1
    FROM
      TABLE1 a
      TABLE2 b
    WHERE
      a.FK = b.PK
      AND b.COL2 IN ('SET OF VALUES')
      AND update_tbl.PK = a.PK
  )

谢谢! -内特

【问题讨论】:

【参考方案1】:

另一种选择:

UPDATE TABLE1 a
SET a.COL1 = 'VALUE'
WHERE a.FK IN
( SELECT b.PK FROM TABLE2 b
  WHERE b.COL2 IN ('SET OF VALUES')
)

如果 (a) 视图包含 TABLE1 的 已声明 PK,则您的第二个示例将起作用:

UPDATE
  (
    SELECT
      a.COL1, a.PKCOL
    FROM
      TABLE1 a,
      TABLE2 b
    WHERE
      a.FK = b.PK
      AND b.COL2 IN ('SET OF VALUES')
  ) update_tbl
SET
  update_tbl.COL1 = 'VALUE'

...和 ​​(b) TABLE1.FK 是 TABLE2 的已声明外键

(通过声明是指存在并启用约束)。

【讨论】:

【参考方案2】:

我发现将 SELECT 语句转换为 UPDATE 的一种很好、快速、一致的方法是根据 ROWID 进行更新。

UPDATE
  TABLE1
SET
  COL1 = 'VALUE'
WHERE
  ROWID in
    (
    SELECT
      a.rowid
    FROM
      TABLE1 a,
      TABLE2 b
    WHERE
      a.FK = b.PK
      AND b.COL2 IN ('SET OF VALUES')
    )

因此,您的内部查询正在定义要更新的行。

【讨论】:

【参考方案3】:

您的示例的语法很好,但 Oracle 要求子查询包含主键。这是一个相当大的限制。

在相关说明中,您还可以使用括号在 IN 语句中使用 2 个或更多字段,如:

UPDATE
  TABLE1 update_tbl
SET
  update_tbl.COL1 = 'VALUE'
WHERE
  (update_tbl.PK1, update_tbl.pk2) in(
                      select some_field1, some_field2
                      from some_table st
                      where st.some_fields = 'some conditions'
                      );

【讨论】:

【参考方案4】:

当您执行更新时,您显然只能告诉系统将值更新为一个新值——告诉它将“X”更新为“Y”和“Z”是没有意义的。因此,当您基于内联视图的结果进行更新时,Oracle 会检查是否有足够的约束来防止修改的列可能被更新两次。

在您的情况下,我希望 TABLE2.PK 实际上不是已声明的主键。如果您在该列上放置一个主要或唯一约束,那么您会很高兴。

有一个未记录的提示绕过更新连接基数检查,由 Oracle 内部使用,但我不建议使用它。

一种解决方法是使用 MERGE 语句,该语句不受相同的测试。

【讨论】:

【参考方案5】:

我在这里找到了我需要的东西: Useful SQL Commands 我需要使用连接结果更新一个表 我尝试了上述解决方案但没有成功:(

这是我上面指出的页面的摘录 使用游标我能够成功完成任务 我确定还有另一种解决方案,但这个解决方案很有效...

DECLARE

 /* Output variables to hold the result of the query: */
 a T1.e%TYPE;
 b T2.f%TYPE;
 c T2.g%TYPE;

 /* Cursor declaration: */
 CURSOR T1Cursor IS
   SELECT T1.e, T2.f, T2.g
   FROM T1, T2
   WHERE T1.id = T2.id AND T1.e <> T2.f

 FOR UPDATE;

BEGIN

  OPEN T1Cursor;

  LOOP

    /* Retrieve each row of the result of the above query
    into PL/SQL variables: */
    FETCH T1Cursor INTO a, b;

    /* If there are no more rows to fetch, exit the loop: */
    EXIT WHEN T1Cursor%NOTFOUND;

    /* Delete the current tuple: */
    DELETE FROM T1 WHERE CURRENT OF T1Cursor;

    /* Insert the reverse tuple: */
    INSERT INTO T1 VALUES(b, a);

    /* Here is my stuff using the variables to update my table */
    UPDATE T2
    SET T2.f = a
    WHERE T2.id = c;

  END LOOP;

  /* Free cursor used by the query. */
  CLOSE T1Cursor;

END;
.
run;

注意:不要忘记提交 ;-)

【讨论】:

【参考方案6】:

您的 UPDATE 子句中查询结果集中的每一行都必须映射回您尝试更新的表中的一行且只有一行,并且 Oracle 可以自动遵循这一方式。由于查询实际上是一个视图,因此考虑它的一种方法是 Oracle 需要能够将视图连接回目标表,以便知道要更新哪一行。

这实质上意味着您需要在该查询中包含目标表的主键。您也许也可以使用其他一些唯一索引字段,但我不能保证 Oracle DBMS 足够聪明以允许这样做。

【讨论】:

以上是关于在 Oracle 更新语句中使用子查询而不是表名的主要内容,如果未能解决你的问题,请参考以下文章

使用子查询更新语句

oracle序列怎么用

Oracle SQL 更新基于两个表之间的子查询

在 SQL 子查询中使用多个表进行 Oracle 半联接

带有子查询的 Oracle 更新 - 性能问题

MySQL的更新语句update中可以用子查询吗?