ORA-01779:无法修改映射到非密钥保留表的列

时间:2013-06-13 16:41:16

标签: oracle plsql

我有这个程序:

create or replace procedure changePermissionsToRead(
datasource  in varchar2
) 
IS

begin

update 
(
select * from 
WEB_USERROLE ur ,
WEB_USERDATASOURCE ds 
where 
    ur.username = ds.username 
    and 
    ds.datasource = datasource
    and 
    ur.READ_ONLY <> 'Y'  
)
r set r.role = replace(r.role, 'FULL', 'READ');
end;

我收到以下错误:

 ORA-01779

但如果我拿出更新并写下:

  update 
(
select * from 
WEB_USERROLE ur ,
WEB_USERDATASOURCE ds 
where 
    ur.username = ds.username 
    and 
    ds.datasource = 'PIPPO'
    and 
    ur.READ_ONLY <> 'Y'  
)
r set r.role = replace(r.role, 'FULL', 'READ');

然后这很好用。你能告诉我发生了什么吗?

1 个答案:

答案 0 :(得分:10)

DML表表达式子句仅在需要来自多个表的列时才有用。在您的情况下,您可以使用EXISTS的常规更新:

update web_userrole
set role = replace(role, 'FULL', 'READ')
where read_only <> 'Y'
    and exists
    (
        select 1/0
        from web_userdatasource
        where datasource = p_datasource
            and username = web_userrole.username
    );

如果您确实需要使用两个表中的列,则有三个选项:

  1. 重复SETWHERE子句中的联接。这很容易构建,但不是最佳的。
  2. DML表表达式。如果你有正确的索引,这个应该
  3. MERGE,下面就是一个例子。

    merge into web_userrole
    using
    (
        select distinct username
        from web_userdatasource
        where datasource = p_datasource
    ) web_userdatasource on
    (
        web_userrole.username = web_userdatasource.username
        and web_userrole.read_only <> 'Y'
    )
    when matched then update
    set role = replace(role, 'FULL', 'READ');
    
  4. 这不会直接回答您的问题,而是提供一些解决方法。我无法重现你得到的错误。我需要一个完整的测试用例来进一步研究它。

    可更新视图的通用建议

    可更新视图的主要问题之一是对它们可以包含的查询的大量限制。查询或视图不得包含许多功能,例如DISTINCT,GROUP BY,某些表达式等。具有这些功能的查询可能会引发异常“ORA-01732:数据操作操作在此视图上不合法”。

    可更新视图查询必须只明确地返回修改后的表的每一行。查询必须是“密钥保留”,这意味着Oracle必须能够使用主键或唯一约束来确保每行只修改一次。

    为了演示为什么密钥保留很重要,下面的代码会创建一个不明确的更新语句。它创建了两个表,第一个表有一行,第二个表有两行。表格按A列加入,并尝试更新第一个表中的列B。在这种情况下,Oracle阻止更新是好的,否则该值将是非确定性的。有时将值设置为“1”,有时将其设置为“2”。

    --Create table to update, with one row.
    create table test1 as
    select 1 a, 1 b from dual;
    
    --Create table to join two, with two rows that match the other table's one row.
    create table test2 as
    select 1 a, 1 b from dual union all
    select 1 a, 2 b from dual;
    
    --Simple view that joins the two tables.
    create or replace view test_view as
    select test1.a, test1.b b_1, test2.b b_2
    from test1
    join test2 on test1.a = test2.a;
    
    --Note how there's one value of B_1, but two values for B_2.
    select *
    from test_view;
    
    A  B_1  B_2
    -  ---  ---
    1    1    1
    1    1    2
    
    --If we try to update the view it fails with this error:
    --ORA-01779: cannot modify a column which maps to a non key-preserved table
    update test_view
    set b_1 = b_2;
    
    --Using a subquery also fails with the same error.
    update
    (
        select test1.a, test1.b b_1, test2.b b_2
        from test1
        join test2 on test1.a = test2.a
    )
    set b_1 = b_2;
    

    MERGE语句没有相同的限制。 MERGE语句似乎试图在运行时检测歧义,而不是编译时间。

    不幸的是MERGE并不总能很好地检测出歧义。在Oracle 12.2上,以下语句有时会起作用,然后失败。对查询进行小的更改可能会使其工作或失败,但我找不到特定的模式。

    --The equivalent MERGE may work and changes "2" rows, even though there's only one.
    --But if you re-run, or uncomment out the "order by 2 desc" it might raise:
    --  ORA-30926: unable to get a stable set of rows in the source tables
    merge into test1
    using
    (
        select test1.a, test1.b b_1, test2.b b_2
        from test1
        join test2 on test1.a = test2.a
        --order by 2 desc
    ) new_rows
        on (test1.a = new_rows.a)
    when matched then update set test1.b = new_rows.b_2;
    
    如果理论上可能有重复项,则

    UPDATE在编译时失败。 应该工作的一些陈述不会运行。

    如果数据库在运行时检测到不稳定的行,则

    MERGE将失败。 不应该工作的一些陈述仍将继续运行。