SQLite:批量更新没有光标的字段

时间:2011-05-10 12:39:07

标签: sql performance 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)

然后我更新记录表中的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缺少此功能:(

有更快的方法吗?

提前致谢!

2 个答案:

答案 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;