2011-05-10 16 views
3

我有如下表:SQLite的:大規模更新場而不光標

CREATE TABLE Records (
RecordIndex INTEGER NOT NULL, 
... 
Some other fields 
... 
Status1 INTEGER NOT NULL, 
Status2 INTEGER NOT NULL, 
UpdateDate DATETIME NOT NULL, 
CONSTRAINT PK_Records PRIMARY KEY (RecordIndex ASC)) 

和索引:

CREATE INDEX IDX_Records_Status ON ClientRecords 
    (Status1 ASC, Status2 ASC, RecordIndex ASC) 

我需要逐個獲取的一定地位的記錄,所以我用這個語句:

SELECT * 
FROM RECORDS 
WHERE RecordIndex > @PreviousIndex 
AND Status1 = @Status1 
AND Status2 = @Status2 
LIMIT 1 

但現在我需要獲取由另一個字段排序的記錄,但該字段不爲每個記錄獨特的,所以我不能以相同的方式使用它。所以我決定在我的表格中添加一個新的SortIndex字段。

由於SQLite中沒有遊標,我正在執行以下操作以初始化SortIndex的值。
首先,我創建一個臨時表:

CREATE TEMP TABLE Sort (
SortIdx INTEGER PRIMARY KEY AUTOINCREMENT, 
RecordIdx INTEGER) 

然後我填這個表中正確排序順序:

INSERT INTO Sort 
    SELECT NULL, RecordIndex 
    FROM Records 
    ORDER BY SomeField ASC, RecordIndex ASC 

然後,我創建臨時表上的索引:

CREATE INDEX IDX_Sort_RecordIdx ON Sort (RecordIdx ASC) 

然後我更新我的Records表中的SortIndex字段:

UPDATE Records 
SET SortIndex = 
    (SELECT SortIdx 
    FROM Sort 
    WHERE RecordIdx = RecordIndex) 

然後我把臨時表:

DROP TABLE Sort 

而且finaly我在我的記錄表上創建新的索引

CREATE INDEX IDX_Records_Sort ON Records 
    (Status1 ASC, Status2 ASC, SortIndex ASC) 

這讓我做了以下選擇

SELECT * 
FROM Records 
WHERE SortIndex > @PreviousSortIndex 
AND Status1 = @Status1 
AND Status2 = @Status2 
LIMIT 1 

問題是,由於表格包含大約500K條記錄,整個事情大約需要2分鐘。它很可能已經快了很多與遊標初始化SortIndex,但SQLite的缺乏這種功能:(

有一個更快的方法來做到這一點?

提前感謝!

回答

0

主要答案

我想,這不可能有使用索引(和許多指標在未來)在SQLlite〜500K記錄快速插入。

我希望有人會發明新的w ^腳跟在這裏。


馬克,我想你應該避免這種類型的動態添加的指標,只是添加等經典指標,不管多少錢,你需要他們。

另外遊標並不總是在任何DMBS中的好主意 - 只有當我們需要複雜的邏輯時,但在這裏按照簡單的順序,我認爲它已超過了。

只需添加經典索引 - 即使它們不是唯一的。

或者在這裏發佈更多關於你爲什麼填寫的細節,你應該選擇一些動態的方式。

也sqlite,因爲我see,支持抵消。


SQL用於測試

-- init 
CREATE TABLE IF NOT EXISTS `records` (
    `RecordID` int(10) default NULL, 
    `Status` int(10) default NULL, 
    `SomeField` char(50) default NULL, 
    `RecordIndex` int(11) default NULL 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

truncate `records`; 
INSERT INTO `records` (`RecordID`, `Status`, `SomeField`, `RecordIndex`) VALUES 
    (1, 1, 'a', 35), 
    (2, 1, 'b', 20), 
    (3, 1, 'c', 42); 

-- 1st select 
SELECT * FROM records WHERE Status = 1 ORDER BY SomeField ASC, RecordIndex ASC LIMIT 1 OFFSET 0; 

-- update 
update records set `Status` = 2 where RecordID = 1; 

-- select next 
SELECT * FROM records WHERE Status = 1 ORDER BY SomeField ASC, RecordIndex ASC LIMIT 1 OFFSET 1; 
+0

首先,索引IDX_Records_Sort不是動態的。該表被填充一次,以後不再添加任何記錄,因此SortIndex字段只需要「計算」一次,並且索引是在填充表後立即創建的。其次,選擇「SELECT * FROM Records WHERE Status1 = @ Status1 AND St​​atus2 = @ Status2 ORDER BY SomeField ASC,ClientIndex ASC LIMIT 1 OFFSET @PreviousIndex」將不起作用,因爲兩種狀態都可以更改,在這種情況下,偏移量不會再正確無誤。 – Marc 2011-05-11 07:10:56

+0

@Marc,這個官方化發生的頻率如何?什麼意思是「一次」? 你也可以描述你的奇怪的邏輯:爲什麼在閱讀過程中可能會發生變化? – gaRex 2011-05-11 08:01:21

+0

@Alexander,表格填充一次,使用批量插入,除2個狀態字段外,大多數字段不會更改(包括用於排序的字段)。用戶希望按照排序字段的順序在屏幕上查看具有特定狀態的記錄。他將依次審查每條記錄並最終更新狀態。然後他會顯示「下一個」記錄。因此,儘管他通過2個狀態值選擇了一條記錄,但他可以在選擇下一條記錄之前更新狀態值。這就是爲什麼偏移方法不起作用。謝謝。 – Marc 2011-05-11 09:07:41

1

而是與相關子查詢做一個UPDATE的,你應該考慮的SQLite INSERT OR REPLACE功能,該功能將執行整排的UPDATE當主鍵是重複的:

UPDATE Records 
    SET SortIndex = 
     (SELECT SortIdx 
      FROM Sort 
     WHERE RecordIdx = RecordIndex) 

變成

INSERT OR REPLACE INTO Records (RecordIndex, SortIndex, ...) 
SELECT RecordIndex, SortIdx, ... FROM another_temporary_table_containing_all_columns. 

,而不使用包含所有列,你當然可以使用SELECT它連接舊錶和新的臨時表:試試這個SQLite的外殼

CREATE TABLE original (id INTEGER PRIMARY KEY, content TEXT); 

BEGIN TRANSACTION; 
INSERT INTO original(id, content) VALUES(1, 'foo'); 
INSERT INTO original(id, content) VALUES(2, 'bar'); 
INSERT INTO original(id, content) VALUES(3, 'baz'); 
COMMIT TRANSACTION; 

CREATE TABLE id_remap(old_id INTEGER, new_id INTEGER); 

BEGIN TRANSACTION; 
INSERT INTO id_remap(old_id, new_id) VALUES(2,3); 
INSERT INTO id_remap(old_id, new_id) VALUES(3,2); 
COMMIT TRANSACTION; 

INSERT OR REPLACE INTO original (id, content) 
SELECT b.new_id, a.content 
    FROM original a 
INNER JOIN id_remap b 
    ON b.old_id = a.id; 

SELECT * FROM original; 

結果裏面:

1|foo 
2|baz 
3|bar 

如果您需要進行批量更新但不希望相關子查詢的另一個選項是在視圖中執行加入,並在該視圖上創建一個觸發INSTEAD OF UPDATE。一個問題是,您不能在過程中遇到失敗的約束。我想每個行都會檢查約束條件,這可能會很慢。

在SQLite的殼:

CREATE TABLE original (id INTEGER PRIMARY KEY, content TEXT); 

BEGIN TRANSACTION; 
INSERT INTO original(id, content) VALUES(1, 'foo'); 
INSERT INTO original(id, content) VALUES(2, 'bar'); 
INSERT INTO original(id, content) VALUES(3, 'baz'); 
COMMIT TRANSACTION; 

CREATE TABLE id_remap(old_id INTEGER, new_id INTEGER); 

BEGIN TRANSACTION; 
INSERT INTO id_remap(old_id, new_id) VALUES(3,6); 
COMMIT TRANSACTION; 

CREATE TEMPORARY VIEW tmp_id_mapping 
    AS 
SELECT a.content, b.old_id, b.new_id 
    FROM original a 
INNER JOIN id_remap b 
    ON b.old_id = a.id; 

CREATE TEMPORARY TRIGGER IF NOT EXISTS tmp_trig_id_remap 
INSTEAD OF UPDATE OF content ON tmp_id_mapping 
    FOR EACH ROW 
    BEGIN 
    UPDATE original 
     SET id = new.new_id 
    WHERE id = new.old_id; 
    END; 

UPDATE tmp_id_mapping 
    SET content = 'hello'; 

SELECT * FROM original; 

結果:

所有的
1|foo 
2|bar 
6|baz 
+0

謝謝,我不知道。但是這並不是真正可行的,因爲表格非常大,它包含500k +記錄和很多列,所以創建包含所有列的臨時表格需要大量內存。但無論如何我會嘗試一下,看看它是否真的快得多。謝謝。 – Marc 2011-05-11 15:20:43

+0

謝謝你增加了更多的想法和例子。我嘗試了你的第一個想法,但是當我試圖將整個表複製到臨時表(SQLite查詢工具是什麼?)時,SQLiteSpy似乎會掛起。然後我試着用加入的想法。由於我不想更改我的主鍵,但只需添加一個新的SortIndex字段,我保留了我的Sort臨時表,並以與以前相同的方式填充它。然後我啓動更新INSERT或REPLACE INTO記錄(RecordIndex,...,UpdateDate,SortIndex)SELECT RecordIndex,...,UpdateDate,SortIdx)FROM記錄INNER JOIN Sort ON RecordIdx = RecordIndex' ... – Marc 2011-05-12 08:01:26

+0

...並且它只花了我第一次更新SortIndex字段兩次(見最初的問題)。除非我做錯了什麼,否則這似乎也不是解決方案。但謝謝你的想法! – Marc 2011-05-12 08:04:28