使用唯一约束批量更新表 a

Posted

技术标签:

【中文标题】使用唯一约束批量更新表 a【英文标题】:Bulk updating a table a with a unique constraint 【发布时间】:2021-01-08 05:54:38 【问题描述】:

我有一个更新表的 PL/SQL 例程,它根据包含的值为每个外键分配一个顺序,并根据值是否更大来评估值是“高”还是“低”超过该外键最高值的 10%。下面是一个示例表:

ID FOREIGN_KEY VALUE ORDER IMPORTANCE
1 1 10000 1 high
2 1 100 2 low
3 1 1 3 low
4 2 20 1 high
5 2 2 2 high

这是执行此操作的示例代码


cursor c_order is select foreign_key, item from my_table 
              order by foreign_key, item, desc;
              
v_old_foreign_key number;     
v_current_order number := 0;        
v_top_value number;
v_importance my_table.importance%type;
begin
              for v_row in c_order
                loop
               
                    if v_row.foreign_key = v_old_foreign_key then
                        v_current_order := v_current_order + 1;
                        IF v_row.item < (v_top_value * 10 / 100) THEN 
                            v_importance := 'low';
                        END IF;
                    else
                        v_current_order := 1;
                        v_importance := 'high';
                        v_top_value := v_row.value;
                    end if;
                    update mytable
                    set order = v_current_order,
                        importance   =  v_importance
                    where id = v_row.id;
                      
                    v_old_foreign_key := v_row.id;
                end loop;
                end;

为了更好地执行数据完整性,我们决定使ORDER 列不可为空,并且它具有与外键的唯一约束。如果订单以某种方式发生变化,上述脚本就会出现问题,因为当我们遍历下表时,我们会尝试在已经存在的地方设置 ORDER=2,从而打破约束。

使用 PL/SQL 执行此操作的最佳方法是什么?

我考虑过加载到临时表并进行批量更新,但这似乎不是最高效的方法。

【问题讨论】:

【参考方案1】:

首先让我们解决违反唯一约束的问题。

假设这是您的数据

select * from my_table order by fk_id, order_id;

        ID      FK_ID      VALUE   ORDER_ID IMPO
---------- ---------- ---------- ---------- ----
         1          1      10000          1 high
         2          1        100          2 low 
         3          1          1          3 low 
         5          2        200          1 high
         4          2         20          2 high

你定义的唯一约束如下

alter table my_table add  constraint my_order unique  (FK_ID, ORDER_ID);

现在您尝试将ID= 5 中的值更新为 2000,这会将行获取到第一个订单。

update   my_table
set value = 2000, order_id = 1
where id = 5;

这当然会失败,但有以下例外

ORA-00001: unique constraint (XXX.MY_ORDER) violated

解决方案是使用deferrable constraints - 即容忍 没有违反并首先使用commit 验证的约束。

alter table my_table drop  constraint my_order;
alter table my_table add  constraint my_order unique  (FK_ID, ORDER_ID) deferrable;

警告:请注意,不可能简单地更改将约束条件推迟。 alter 将成功执行,但您仍会收到异常 ORA-00001,因为约束仍然由 唯一索引 支持。所以需要删除和新的创建,以便它基于一个非唯一索引

现在您可以毫无问题地重新排序这两行。

update   my_table
set value = 2000, order_id = 1
where id = 5;

update   my_table
set   order_id = 2
where id = 4;

commit;

数据的新状态如下

select * from my_table order by fk_id, order_id;

        ID      FK_ID      VALUE   ORDER_ID IMPO
---------- ---------- ---------- ---------- ----
         1          1      10000          1 high
         2          1        100          2 low 
         3          1          1          3 low 
         5          2       2000          1 high
         4          2         20          2 high

现在让我们接近 importance 列 - 它也必须更新。

不需要 PL/SQL - 简单的 SQL 可以正常工作并且性能更高。

此查询计算 阈值,即每个 FK_ID 的 MAX 值的 10%,并使用它来分配新的重要性标志。

with thresh as (
 select FK_ID, max(VALUE/10) threshold
 from my_table
 group by FK_ID)
select ID, a.FK_ID, VALUE, ORDER_ID, IMPORTANCE,
 case when VALUE < b.threshold
     then 'low'
     else 'high' end as NEW_IMPORTANCE
from my_table a
join thresh b on a.FK_ID = b.FK_ID
order by fk_id, order_id;

       ID      FK_ID      VALUE   ORDER_ID IMPO NEW_
---------- ---------- ---------- ---------- ---- ----
         1          1      10000          1 high high
         2          1        100          2 low  low 
         3          1          1          3 low  low 
         5          2       2000          1 high high
         4          2         20          2 high low

这个非常相同的查询我可以在 UPDATE 语句中直接使用(没有order by):

update (         
 with thresh as (
  select FK_ID, max(VALUE/10) threshold
  from my_table
  group by FK_ID)
 select ID, a.FK_ID, VALUE, ORDER_ID, IMPORTANCE,
 case when VALUE < b.threshold
     then 'low'
     else 'high' end as NEW_IMPORTANCE
 from my_table a
 join thresh b on a.FK_ID = b.FK_ID
)
set IMPORTANCE = NEW_IMPORTANCE
where IMPORTANCE != NEW_IMPORTANCE
;

1 row updated.

请注意,因为我通过 WHERE 子句限制了更新,所以只有一个更改的行被更新,整个表(如您的解决方案中所示)。 SQL 还会进行集合更新,而不是 游标 row by row(= 慢到慢)处理。

请随意比较这两种方法在一个有 1M 行的表上。

【讨论】:

以上是关于使用唯一约束批量更新表 a的主要内容,如果未能解决你的问题,请参考以下文章

使用 JDBC 和唯一约束进行批量插入

PLSQL中批量更新数据

oracle 如何实现对单个表批量更新

oracle批量更新的问题

SQL两表关联批量更新一列数据下面有数据参考

Oracle两张表关联批量更新其中一张表的数据