Поле приращения с ненулевым и уникальным ограничением в PostgreSQL 8.3 - PullRequest
4 голосов
/ 24 октября 2010

У меня есть таблица "items" с колонкой "position". позиция имеет уникальное и ненулевое ограничение. Чтобы вставить новую строку в позицию x, я сначала попробую увеличить позиции следующих элементов:

UPDATE items SET position = position + 1 WHERE position >= x;

Это приводит к уникальному нарушению ограничения:

ERROR:  duplicate key value violates unique constraint

Проблема, похоже, заключается в порядке, в котором PostgreSQL выполняет обновления. Уникальные ограничения в PostgreSQL <9.0 не являются отложенными, и, к сожалению, использование 9.0 в настоящее время невозможно. Кроме того, оператор UPDATE не поддерживает предложение ORDER BY, и следующее также не работает (все еще нарушение ключа): </p>

UPDATE items SET position = position + 1 WHERE id IN (
  SELECT id FROM items WHERE position >= x ORDER BY position DESC)

Кто-нибудь знает решение, которое не включает в себя перебор всех элементов кода?

Ответы [ 4 ]

3 голосов
/ 24 октября 2010

Другая таблица с несколькими уникальными индексами:

create table utest(id integer, position integer not null, unique(id, position));
test=# \d utest
      Table "public.utest"
  Column  |  Type   | Modifiers 
----------+---------+-----------
 id       | integer | 
 position | integer | not null
Indexes:
    "utest_id_key" UNIQUE, btree (id, "position")

Некоторые данные:

insert into utest(id, position) select generate_series(1,3), 1;
insert into utest(id, position) select generate_series(1,3), 2;
insert into utest(id, position) select generate_series(1,3), 3;

test=# select * from utest order by id, position;
 id | position 
----+----------
  1 |        1
  1 |        2
  1 |        3
  2 |        1
  2 |        2
  2 |        3
  3 |        1
  3 |        2
  3 |        3
(9 rows)

Я создал процедуру, которая обновляет значения позиций в правильном порядке:

create or replace function update_positions(i integer, p integer) 
  returns void as $$
declare
  temprec record;
begin
  for temprec in 
    select * 
      from utest u 
      where id = i and position >= p 
      order by position desc 
  loop
    raise notice 'Id = [%], Moving % to %', 
      i, 
      temprec.position, 
      temprec.position+1;

    update utest 
      set position = position+1 
      where position=temprec.position and id = i;
  end loop;
end;
$$ language plpgsql;

Некоторые тесты:

test=# select * from update_positions(1, 2);
NOTICE:  Id = [1], Moving 3 to 4
NOTICE:  Id = [1], Moving 2 to 3
 update_positions 
------------------

(1 row)

test=# select * from utest order by id, position;
 id | position 
----+----------
  1 |        1
  1 |        3
  1 |        4
  2 |        1
  2 |        2
  2 |        3
  3 |        1
  3 |        2
  3 |        3
(9 rows)

Надеюсь, это поможет.

2 голосов
/ 24 октября 2010


, поскольку PostgreSQL поддерживает полный набор транзакционных DDL, вы можете легко сделать что-то вроде этого:

create table utest(id integer unique not null);
insert into utest(id) select generate_series(1,4);

Таблица теперь выглядит так:

test=# \d utest
     Table "public.utest"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | not null
Indexes:
    "utest_id_key" UNIQUE, btree (id)

test=# select * from utest;
 id 
----
  1
  2
  3
  4
(4 rows)

А теперьвся магия:

begin;
alter table utest drop constraint utest_id_key;
update utest set id = id + 1;
alter table utest add constraint utest_id_key unique(id);
commit;

После этого у нас есть:

test=# \d utest
     Table "public.utest"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | not null
Indexes:
    "utest_id_key" UNIQUE, btree (id)

test=# select * from utest;
 id 
----
  2
  3
  4
  5
(4 rows)

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

0 голосов
/ 19 сентября 2018

Вариант без изменения таблицы и ограничения удаления:

UPDATE items t1 
SET    position = t2.position + 1 
FROM   (SELECT position
    FROM   items 
    ORDER  BY position DESC) t2 
WHERE t2.position >= x AND t1.position = t2.position

Онлайн пример: http://rextester.com/FAU54991

0 голосов
/ 11 февраля 2016

Решением 'correcter' может быть ограничение DEFERRABLE

ALTER TABLE channels ADD CONSTRAINT 
channels_position_unique unique("position") 
DEFERRABLE INITIALLY IMMEDIATE

, а затем установить это ограничение на DEFERRED при увеличении и вернуть его обратно в IMMEDIATE, как только вы закончите.

SET CONSTRAINTS channels_position_unique DEFERRED;
UPDATE channels SET position = position+1 
WHERE position BETWEEN 1 AND 10;
SET CONSTRAINTS channels_position_unique IMMEDIATE;
...