我有下表:
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)
然后我更新记录表中的SortIndex字段:
UPDATE Records
SET SortIndex =
(SELECT SortIdx
FROM Sort
WHERE RecordIdx = RecordIndex)
然后我放下临时表:
DROP TABLE Sort
最后我在Records表上创建了一个新索引
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 :(得分: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 shell中尝试这个
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 shell中:
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
答案 1 :(得分:0)
主要答案
我认为,这不可能快速插入带有索引的SQLlite~500k记录(以及将来的许多索引)。
我希望有人能在这里发明新车轮。
Mark,我认为你应该避免使用这种类型的动态添加索引,只需添加其他经典索引,无论你需要它们多少。
在任何DMBS中,游标也不总是好主意 - 只有当我们需要复杂的逻辑时,但是按照简单的顺序,我认为它是在过度。
只需添加经典索引 - 即使它们不是唯一的。
或者在这里发布更多关于你为什么要填写的细节,你应该选择一些动态的方式。
同样,sqlite,因为我see,支持偏移。
SQL for tests
-- 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;