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

时间:2011-03-23 09:56:01

标签: mysql sql postgresql sql-update

我有一个按排名顺序显示的标签表。为确保没有两行可以具有相同的排名,它们的值是唯一的:

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)
)

无论是PostgreSQL还是MySQL,它们都表现出相同的行为。查询可能看起来像select title from label order by rank。假设该表包含:

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

现在假设我要重新排序两个标签,例如苹果排在牛之前。最简单的方法是交换他们的等级值:

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

不。也不:

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

甚至不是:

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

每次,唯一约束触发第一行更新并中止操作。如果我可以将支票推迟到声明结束,我会没事的。这种情况发生在PostgreSQL和MySQL上。

ACID安全的解决方法是:

  1. 开始交易
  2. 在表格中选择第一,第二记录和最高(最高)排名的排名(这可能需要一个联盟)
  3. 将第一条记录更新为rank = max + 1
  4. 将第二个记录更新为第一个等级
  5. 将第一记录更新为第二名
  6. 提交
  7. 这简直难以置信。更糟糕的是删除约束,更新,然后重新创建约束。授予操作角色这样的权限是一件麻烦事。所以我的问题是:有一种我忽略的简单技术可以解决这个问题,还是我是SOL?

3 个答案:

答案 0 :(得分:4)

使用PostgreSQL,只能使用版本9.0以“漂亮”的方式解决这个问题,因为您可以定义唯一的约束以便在那里推迟。

使用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;

然后更新就像这样简单:

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;

编辑:
如果您不想在事务中将约束设置为延迟,则只需将约束定义为initially deferred

答案 1 :(得分:0)

当然你可以:

update label set rank = 5 where id_label=2

但问题在于我猜你需要能够处理连续排名之间没有“差距”的情况。对于postgres,使用numeric代替integer可以解决问题,因为它几乎有unlimited precision

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)
)

现在你只需更新一行就可以将它移动到排名的任何位置,无论任何其他行的排名是什么,方法是将上面的等级和下面的等级之间的差异分开。

答案 2 :(得分:0)

我遇到了类似的问题,我的解决方案如下:

  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
  6. 如果有任何错误,回滚事务。

    您可以在不删除唯一约束的情况下执行此操作。