【问题标题】:Update the results of a SELECT statement更新 SELECT 语句的结果
【发布时间】:2010-11-03 05:50:49
【问题描述】:

Oracle 允许您更新 SELECT 语句的结果。

UPDATE (<SELECT Statement>)
SET <column_name> = <value>
WHERE <column_name> <condition> <value>;

我想这可以用于根据另一个表中匹配行的值来更新一个表中的列。

这个特性是怎么称呼的,它能否有效地用于大型更新,当SELECT连接多个表时它是否有效,如果是,如何?

【问题讨论】:

  • 有一个示例更新另一个 SO 中的连接:*.com/questions/975315/… -- 关于效率:这可能是更新一组行的最有效方式
  • 一般称为视图更新。

标签: sql oracle sql-update bulk


【解决方案1】:

我还没有看到这个的正式名称。 Oracle SQL Reference 只是指更新子查询。我倾向于将其视为“视图更新”的一种形式,子查询位于内联视图中。

是的,它在连接多个表时有效,但受视图更新规则的约束。这意味着只能更新视图的一个基表,并且该表必须在视图中“保留键”:即它的行应该只能在视图中出现一次。这要求视图(子查询)中的任何其他表都通过要更新的表上的外键约束来引用。

一些例子可能会有所帮助。使用标准的 Oracle EMP 和 DEPT 表,将 EMP.EMPNO 定义为 EMP 的主键,并将 EMP.DEPTNO 定义为 DEPT.DEPTNO 的外键,则允许此更新:

update (select emp.empno, emp.ename, emp.sal, dept.dname
        from   emp join dept on dept.deptno = emp.deptno
       )
set sal = sal+100;

但这不是:

-- DEPT is not "key-preserved" - same DEPT row may appear
-- several times in view
update (select emp.ename, emp.sal, dept.deptno, dept.dname
        from   emp join dept on dept.deptno = emp.deptno
       )
set dname = upper(dname);

至于性能:优化器将(必须)在解析期间识别要更新的基表,并且与其他表的连接将被忽略,因为它们与要执行的更新没有任何关系 - 正如此 AU​​TOTRACE 输出所示:

SQL> update (select emp.ename, emp.sal, dept.dname
  2              from   emp join dept on dept.deptno = emp.deptno
  3             )
  4      set sal = sal-1;

33 rows updated.


Execution Plan
----------------------------------------------------------
Plan hash value: 1507993178

------------------------------------------------------------------------------------
| Id  | Operation           | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT    |              |    33 |   495 |     3   (0)| 00:00:01 |
|   1 |  UPDATE             | EMP          |       |       |            |          |
|   2 |   NESTED LOOPS      |              |    33 |   495 |     3   (0)| 00:00:01 |
|   3 |    TABLE ACCESS FULL| EMP          |    33 |   396 |     3   (0)| 00:00:01 |
|*  4 |    INDEX UNIQUE SCAN| SYS_C0010666 |     1 |     3 |     0   (0)| 00:00:01 |
------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")

(请注意,即使 DEPT.DNAME 出现在子查询中,也永远不会访问表 DEPT)。

【讨论】:

    【解决方案2】:

    您提到的表单没有特定名称 AFAIK。只是更新 select 语句的结果。

    还有另一种形式称为相关更新(单列或多列更新)

    UPDATE TABLE(<SELECT STATEMENT>) <alias>
    SET <column_name> = (
      SELECT <column_name>
      FROM <table_name> <alias>
      WHERE <alias.table_name> <condition> <alias.table_name>
    );
    

    多列表单

    ...
    SET (<column_name_list>) = (
      SELECT <column_name_list>
    ...
    

    还有一个也可以从中返回值的方法,称为使用返回子句更新

    还有一些关于嵌套表更新的细节。最好至少检查这两页

    Oracle® Database SQL Language Reference SELECT

    Oracle® Database SQL Language Reference UPDATE

    【讨论】:

      【解决方案3】:

      感谢 cmets,我认为这是标准的 Sql... :(

      对于 Oracle,您可以在表上编写更新,在该表中您可以使用以下连接检索信息:

      UPDATE (
          SELECT * 
          FROM table1 t1 
          LEFT JOIN table2 t2 ON t2.t1id = t1.ID
      ) SET t1.col1 = t2.col2
      

      对于 Sql Server,它是:

      UPDATE t1
      SET col1 = t2.col2
      FROM table1 t1
      LEFT JOIN table2 t2 on t2.t1id = t1.id
      

      如果有人知道一种适用于 Oracle、Sql Server 和 MySql 的方法,我会很感兴趣。

      【讨论】:

      • 你确定这有效吗?在 Oracle 中,我在“FROM”处得到“ORA-00933:SQL 命令未正确结束”?
      • 使用 dpriver.com/pp/sqlformat.htm 似乎适用于 MSSQL,但会出现 Oracle、MySQL 或 DB2 的语法错误
      • -1 不,它在 Oracle 中不起作用。我也不确定它是否是标准 SQL。