2011-10-09 97 views
3

我有一個表,其中包含一個唯一的整數字段包含連續值。當我嘗試使用下面的方法增加這些值時,違反了唯一約束。有沒有一種方法可以成功做到這一點?SQLite:遞增唯一整數字段

CREATE TABLE numbers(num INT UNIQUE NOT NULL) 
UPDATE numbers SET num=num+1 
+0

如何消除約束,更新值和設置約束? – Li0liQ

回答

10

這必須是一個錯誤。它顯然不會導致違反約束,並且它在SQL Server中起作用。事實上,我敢肯定這是一個錯誤,因爲我可以把它成功,如果我插入按照從大到小的順序編號:

sqlite> INSERT INTO numbers (num) VALUES (3); 
sqlite> INSERT INTO numbers (num) VALUES (2); 
sqlite> INSERT INTO numbers (num) VALUES (1); 
sqlite> UPDATE numbers SET num = num + 1; 
sqlite> SELECT * FROM numbers; 
4 
3 
2 

一個更新的正確性不應該依賴行的表中的順序。

舉一個簡單的變通方法,你可以這樣做:

UPDATE numbers SET num = -num; 
UPDATE numbers SET num = 1 - num; 
+2

我可以看到它總體上沒有違反約束條件,但如果你考慮每個增量階段,可能會有一個點在兩個值相同的地方。 – DrYap

+4

@DYYap:我明白爲什麼存在這個錯誤。但它仍然是一個錯誤。 UPDATE是一個原子操作,這意味着只有最終狀態應該受到約束。 –

+0

我認爲你們都應該閱讀關於SQLite和它的自動提交功能:http://www.sqlite.org/atomiccommit.html。我不認爲這是一個錯誤,但它是一個可以管理的功能。有很多情況下你不希望UPDATE是原子的。 – Kitson

3

你應該能夠實現這一點:
- 求最大值max_pk
- 更新中的所有行與PK = PK + max_pk。
- 用pk = pk-max_pk + 1更新所有行

-1

該行爲是正確的。考慮num是外鍵約束中被引用列的情況,並且更新是級聯的。違反臨時更新中的唯一約束條件而通過遊標進行更新(包括允許推遲完整性檢查直至提交時間)的任何方法都會導致數據庫不一致(完整性丟失)。例如,如果的行在num = n的行更新後更新,對於任何n。有許多方法可以「更新」更新以避免這種情況,但它需要知識領域,因此引擎無法爲您做到。也不應該。

Create table numbers (num int unique); 
Create table others (a int, num int unique references numbers (num) on update cascade); 
Insert into numbers values (1), (2), (3), (4); 
Insert into others values (1,1), (2,2), (3,3), (4,4); 

那麼任何臨時更新num這違反了唯一約束會導致完整性損失和更新應該會失敗,不論行順序的。除非在SQL語句中明確聲明,否則你不應該依賴行順序。關閉約束檢查(或延遲到提交時間)將「不知道你在做什麼」的後果直接交給程序員(它所屬的)。如果您對數據庫有足夠的瞭解以禁用完整性檢查,則應該對其後果(如果有)產生影響。

在沒有這種副作用的情況下執行更新的唯一方法是確保更新沒有任何臨時(逐行)違反完整性。

在其他一些引擎,你可以使用:

update numbers set num = num + 1 from numbers order by num desc; 

通過執行更新雖然「光標的電流」,這將永遠每次獲得正確的結果,以控制該行的處理順序。

也許一個有用的增強將允許更新使用和從條款順序,從而允許這種更新直接表達。實際上,這樣的更新將變成「select」,其中不返回行,「返回一行」將被替換爲更新操作...

有些人將此稱爲可更新視圖。它確實不是。它仍然是「遊標當前行」的更新,通過允許將其他表連接到遊標中,而不是將遊標限制爲僅更新一個表,只會更新結果集中的每個有效結果行。

你可以通過創建一個具有正確排序的視圖實現這一目標目前,然後更新觸發視圖上的num列用於更新基礎表,然後進行對視圖更新:

Create table numbers (num int unique); 
Create table others (a int, num int unique references numbers (num) on update cascade); 
Insert into numbers values (1), (2), (3), (4); 
Insert into others values (1,1), (2,2), (3,3), (4,4); 
Create view updatenumbers 
as 
    select num from numbers order by num desc; 
    Create trigger updnum instead of update of num on updatenumbers 
begin 
    update numbers set num = new.num where num=old.num; 
end; 

update updatenumbers set num = num + 1; 

sqlite> select * from numbers; select * from others; 
2 
3 
4 
5 
1|2 
2|3 
3|4 
4|5 

爲了完全正確,您應該使用rowid在基礎表上執行更新。創建實現遊標的視圖以及替代觸發器來更新基於rowid的基礎表,然後成爲實現「遊標當前位置」樣式更新的通用模式。然後可以使觸發器足夠通用(如果有必要),以便它可以通過基於光標行的任何選擇來更新任何列或列的組合,並且所有參照約束仍將保留。

create table numbers (num int unique); 
create table others (a int, num int unique references numbers (num) on update cascade); 
insert into numbers values (1), (2), (3), (4); 
insert into others values (1,1), (2,2), (3,3), (4,4); 

create view updatenumbers 
as 
    select numbers.rowid, * 
     from numbers 
order by num desc; 

create trigger updnum instead of update of num on updatenumbers 
begin 
    update numbers 
     set num = new.num 
    where rowid=old.rowid; 
end; 

update updatenumbers set num = num + 1; 

select * from numbers; 
select * from others; 

2 
3 
4 
5 
1|2 
2|3 
3|4 
4|5 
+0

出於好奇,我試圖在幾個不同的引擎。大多數拒絕以這種方式更新多行(取決於行順序)。有一個非常有趣的例外:無論行順序如何,IBM DB2都允許在任一方向進行這種更新。然而,它通過簡單地不支持ON UPDATE CASCADE來獲得這種魔力(CASCADE僅支持ON DELETE),因此上述場景甚至無法在DB2中首先實現。 –