SQLite: массовое обновление поля без курсора - PullRequest
3 голосов
/ 10 мая 2011

У меня есть следующая таблица:

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

И наконец я создаю новый индекс в моей таблице записей

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

Проблема в том, что в таблице содержится около 500 тыс. Записей, и все это занимает около 2 минут. Вероятно, было бы намного быстрее инициализировать SortIndex с помощью курсора, но SQLite не имеет этой возможности: (

Есть ли более быстрый способ сделать это?

Заранее спасибо!

Ответы [ 2 ]

1 голос
/ 11 мая 2011

Вместо того, чтобы делать UPDATE с коррелированным подзапросом, вы должны рассмотреть возможность INSERT OR REPLACE SQLite, которая будет выполнять 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 голосов
/ 10 мая 2011

Основной ответ

Я думаю, что невозможно быстро вставить в SQLlite ~ 500 тыс. Записей с индексами (и многими индексами в будущем).

Надеюськто-то изобретет здесь новое колесо.


Марк, я думаю, вам следует избегать динамически добавляемых индексов этого типа и просто добавлять другие классические индексы, независимо от того, насколько они вам нужны.

Также курсоры не всегда хорошая идея в любой DMBS - только если нам нужна сложная логика, но здесь, в простом порядке, я думаю, это слишком много.

Просто добавьте классические индексы - даже если онине уникальный.

Или опубликуйте здесь более подробную информацию о том, почему именно вы заполняете, что вам следует выбрать какой-то динамический способ.

Также sqlite, как я вижу , поддерживает смещение.


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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...