使用唯一约束批量更新表 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的主要内容,如果未能解决你的问题,请参考以下文章