Подтвердить что ты не робот

Атомное многострочное обновление с уникальным ограничением

У меня есть таблица меток, которые отображаются в ранжированном порядке. Чтобы гарантировать, что две строки не могут иметь один и тот же ранг, их значения уникальны:

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

Теперь предположим, что я хочу изменить порядок двух меток, например. Apple занимает до коров. Самый простой способ - изменить их ранговые значения:

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, заключается в следующем:

  • начать транзакцию
  • выберите ранги первой, второй записи и наивысшего (max) ранга в таблице (что, скорее всего, потребует объединение)
  • обновить первую запись до ранга = max + 1
  • обновить вторую запись до ранжирования первого
  • обновить первую запись до ранга секунды
  • совершить

Это просто невыразимо уродливое. Хуже того, чтобы отказаться от ограничения, обновить, а затем воссоздать ограничение. Предоставление таких привилегий оперативной роли требует неприятностей. Поэтому мой вопрос заключается в следующем: есть ли простая техника, которую я упустил, которая решает эту проблему, или я SOL?

4b9b3361

Ответ 1

С 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.

Ответ 2

Конечно, вы можете просто:

update label set rank = 5 where id_label=2

но проблема здесь, я думаю, вам нужно иметь возможность обрабатывать случай, когда нет "разрыва" между последовательными рядами. Для 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)
)

теперь вам нужно обновить только одну строку, чтобы переместить ее в любом месте рейтинга, независимо от того, какие ряды любой другой строки, разделив разницу между рангом выше и ниже.

Ответ 3

У меня была аналогичная проблема, и мое решение было следующим:

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

Если какие-либо ошибки, откат транзакции.

Вы можете сделать это, не отбрасывая уникальное ограничение.