2010-10-23 28 views
3

我有一個帶有「position」列的表「items」。位置具有唯一的非空約束。爲了在位置插入一個新行X我第一次嘗試增加後續項目的位置:在PostgreSQL中使用非空和唯一約束來增加字段8.3

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子句和以下不工作,太(還是重複鍵衝突):

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

是否有人知道一個解決方案,不涉及迭代所有條目在代碼中?

+0

'position'總是一個整數嗎? – SimonJ 2010-10-23 20:50:42

+0

是的,位置總是一個整數。它用於讓用戶定義項目的自定義順序。 – lassej 2010-10-23 20:53:18

回答

3

另一個表,具有多個唯一索引:

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) 

希望它有幫助。

+0

謝謝,我會盡力的。本來希望沒有存儲過程是可能的。 – lassej 2010-10-24 19:49:57

2


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

現在整個魔法:

​​

之後我們有:

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

感謝您的回答,但我不希望在每次交易中刪除並重新應用約束。上面的例子是一個簡化的例子。我在[foreign_key,position]上有一個複合唯一索引,並且隨着行數的增加,這種方法可能會變得很慢。 – lassej 2010-10-24 09:15:08

+0

確實......我會在下一個答案中插入另一個解決方案,這裏我沒有代碼高亮。 – 2010-10-24 10:12:53

0

的「correcter的解決方案可能是,使得約束DEFERRABLE

ALTER TABLE channels ADD CONSTRAINT 
channels_position_unique unique("position") 
DEFERRABLE INITIALLY 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;