【问题标题】:Update with 2 joins更新 2 个连接
【发布时间】:2016-03-26 04:53:00
【问题描述】:

我正在尝试使用以下代码在同一查询中更新 2 个不同表中的数据:

UPDATE (SELECT s.passNumb, a.hour, a.minute, p.number, s.situation
        FROM passwords s
        JOIN atend a ON s.passNumb = a.passNumb
        JOIN points p ON a.number = p.number
        WHERE s.passNumb = 1 AND 
              p.number = 1)
  SET a.hour = TO_CHAR(SYSDATE, 'HH24'),
      a.minute = TO_CHAR(SYSDATE, 'MI'),
      s.situation = 'F';

但我收到此错误:Cannot modify a column which maps to a non key-preserved table。我做错了什么?

【问题讨论】:

    标签: oracle sql-update inner-join


    【解决方案1】:

    具有连接的视图(或在您的情况下包含连接的内联视图)必须满足以下条件才能更新:
    https://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_8004.htm

    如果您希望连接视图可更新,则以下所有内容 条件必须为真:

    DML 语句必须只影响连接下的一张表。

    对于 INSERT 语句,视图不能用 WITH CHECK 创建 OPTION,并且所有插入值的列必须来自 一个密钥保留的表。密钥保留表是每个 基表中的主键或唯一键值在 加入视图。

    对于 UPDATE 语句,不能使用 WITH CHECK 创建视图 OPTION,并且所有更新的列都必须从保留的键中提取 表。


    第一个条件比较明显:The DML statement must affect only one table underlying the join.

    但它是什么意思:“key保留表”?

    保留键的表是其中每个主键或唯一键 基表中的键值在连接视图中也是唯一的。

    键保留表意味着该表中的每一行将在视图结果中最多出现一次

    考虑一个简单的例子:

    CREATE TABLE users(
      user_id int primary key,
      user_name varchar(100),
      age int
    );
    
    insert into users values(1,'Tom', 22);
    
    CREATE TABLE emails(
      user_id int,
      email varchar(100)
    );
    
    Insert into emails values( 1, 'tom@somedomain.com' );
    Insert into emails values( 1, 'tom@www.example.org' );
    commit;
    

    还有一个连接:

    SELECT * 
    FROM users u
    JOIN emails e ON u.user_id = e.user_id;
    
       USER_ID USER_NAME              AGE    USER_ID EMAIL              
    ---------- --------------- ---------- ---------- --------------------
             1 Tom                     22          1 tom@somedomain.com   
             1 Tom                     22          1 tom@www.example.org  
    

    如果您查看此连接的结果,很明显:

    • user_id、user_name 和 age 来自非键保留表
    • 电子邮件来自密钥保留表

    现在:这个更新是可以接受的,因为它更新了这个连接中的一个键保留列(表):

        UPDATE (
          SELECT * FROM users u
          JOIN emails e ON u.user_id = e.user_id
        )
        SET email = email || '.it' ; 
    
       USER_ID USER_NAME              AGE    USER_ID EMAIL                   
    ---------- --------------- ---------- ---------- -------------------------
             1 Tom                     22          1 tom@somedomain.com.it     
             1 Tom                     22          1 tom@www.example.org.it 
    

    但无法完成此更新,因为它涉及非键保留表中的列:

    UPDATE (
      SELECT * FROM users u
      JOIN emails e ON u.user_id = e.user_id
    )
    SET age = age + 2; 
    
    SQL Error: ORA-01779: cannot modify a column which maps to a non key-preserved table
    01779. 00000 -  "cannot modify a column which maps to a non key-preserved table"
    *Cause:    An attempt was made to insert or update columns of a join view which
               map to a non-key-preserved table.
    *Action:   Modify the underlying base tables directly.
    

    如果您想一想...... Tom 在连接结果中出现了 2 次(但 users 表中只有一个 Tom)。

    当我们尝试在此连接中更新age = age + 2 时,此更新的结果应该是什么?

    汤姆应该只更新一次吗? 更新后汤姆应该是 22+2 = 24 岁吗?

    或者也许汤姆应该更新两次(因为它在连接结果中出现两次)所以它应该是 22 + 2 + 2 = 26 岁。

    另一个例子 - 请告诉我这次更新的结果是什么?:

    UPDATE ( ....our join ... ) SET age = length( email );
    

    有非常难的问题:)
    正因为如此,Oracle 阻止更新非键保留表。


    错误消息给出了以下提示: *Action: Modify the underlying base tables directly.

    这意味着我们必须使用单独的 UPDATE 命令直接更新此表:

      UPDATE users SET age = age + 2
    

    【讨论】:

    • 谢谢你的解释,我明白了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-19
    • 2016-01-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多