简体   繁体   English

具有唯一约束的原子多行更新

[英]Atomic multi-row update with a unique constraint

I have a table of labels that are displayed in a ranked order. 我有一个按排名顺序显示的标签表。 To ensure that no two rows can have the same rank, their values are unique: 为确保没有两行可以具有相同的排名,它们的值是唯一的:

create table label (
  id_label serial not null,
  rank integer not null,
  title text not null,
  constraint pri primary key (id_label),
  constraint unq unique (rank)
)

Doesn't matter if it's PostgreSQL or MySQL, they exhibit the same behaviour. 无论是PostgreSQL还是MySQL,它们都表现出相同的行为。 A query might look like select title from label order by rank . 查询可能看起来像select title from label order by rank Assume the table contains: 假设该表包含:

id_label rank title
1        10   Cow
2        20   Apple
3        45   Horse
4        60   Beer

Now suppose I want to reorder two labels, eg have Apple ranked before Cow. 现在假设我想重新排序两个标签,例如让Apple排在Cow之前。 The easiest way is to swap their rank values: 最简单的方法是交换他们的等级值:

update label
set rank = case when rank = 20 then 10 else 20 end
where id_label in (1,2)

Nope. 不。 Nor: 也不:

update label
set rank = case when rank = 20 then rank - 10 else rank + 10 end
where id_label in (1,2)

Nor even: 甚至不是:

update label
set rank = 30 - rank
where id_label in (1,2)

Each time, the unique constraint fires on the first row update and aborts the operation. 每次,唯一约束触发第一行更新并中止操作。 If I could defer the check until the end of the statement I would be fine. 如果我可以将支票推迟到声明结束,我会没事的。 This happens on both PostgreSQL and MySQL. 这种情况发生在PostgreSQL和MySQL上。

An ACID-safe workaround is to: ACID安全的解决方法是:

  1. begin transaction 开始交易
  2. select ranks of first, second record, and highest (max) rank in table (which offhand will probably require a union) 在表格中选择第一,第二记录和最高(最高)排名的排名(这可能需要一个联合)
  3. update first record to rank = max + 1 将第一条记录更新为rank = max + 1
  4. update second record to rank of first 将第二条记录更新为第一名
  5. update first record to rank of second 将第一条记录更新为第二级
  6. commit 承诺

That's just unspeakably ugly. 这简直难以置信。 Worse is to drop the constraint, update, and then recreate the constraint. 更糟糕的是删除约束,更新,然后重新创建约束。 Granting such privileges to an operational role is asking for trouble. 授予操作角色这样的权限是一件麻烦事。 So my question is this: is there a simple technique I have overlooked that solves this problem, or am I SOL? 所以我的问题是:有一种我忽略的简单技术可以解决这个问题,还是我是SOL?

With PostgreSQL this can only be solved in a "nice" way using Version 9.0 because you can define unique constraints to be deferrable there. 使用PostgreSQL,只能使用9.0版以“漂亮”的方式解决这个问题,因为您可以定义唯一的约束,以便在那里进行延迟。

With PostgreSQL 9.0 you'd simply do: 使用PostgreSQL 9.0,您只需:

create table label (
  id_label serial not null,
  rank integer not null,
  title text not null,
  constraint pri primary key (id_label)
);
alter table label add constraint unique_rank unique (rank) 
      deferrable initially immediate;

Then the update is as simple as this: 然后更新就像这样简单:

begin;
set constraints unique_rank DEFERRED;
update rank
   set rank = case when rank = 20 then 10 else 20 end
   where id_label in (1,2);
commit;

Edit: 编辑:
If you don't want to bother setting the constraint to deferred inside your transaction, you can simply define the constraint as initially deferred . 如果您不想在事务中将约束设置为延迟,则可以简单地将约束定义为initially deferred

I had a similar problem and my solution was the following: 我有类似的问题,我的解决方案如下:

  1. START TRANSACTION
  2. SELECT * FROM label WHERE id_label IN(1,2)
  3. Delete FROM label WHERE id_label IN(1,2)
  4. INSERT INTO label(all, columns, of, table) VALUES(all, values, we, selected)
  5. COMMIT TRANSACTION

If any errors, rollback transaction. 如果有任何错误,回滚事务。

You can do this without dropping the unique constraint. 您可以执行此操作而不删除唯一约束。

Of course you can just: 当然你可以:

update label set rank = 5 where id_label=2

but the problem here I guess is you need to be able to handle the case when there is no 'gap' between consecutive ranks. 但是我想这里的问题是你需要能够处理连续排名之间没有“差距”的情况。 For postgres, using numeric instead of integer gets round the problem because it has almost unlimited precision 对于postgres,使用numeric而不是integer来解决问题,因为它具有几乎无限的精度

create table label (
  id_label serial not null,
  rank numeric not null,
  title text not null,
  constraint pri primary key (id_label),
  constraint unq unique (rank)
)

now you need to update just one row to move it anywhere in the ranking, no matter what the ranks of any other row, by splitting the difference between the rank above and the one below. 现在你只需更新一行就可以将它移动到排名中的任何位置,无论任何其他行的排名如何,都可以通过分割上面的等级和下面的等级之间的差异来实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM