SQLite - UPSERT * не * Вставить или заменить - PullRequest
494 голосов
/ 07 января 2009

http://en.wikipedia.org/wiki/Upsert

Вставить обновление сохраненного процесса на SQL Server

Есть ли какой-нибудь умный способ сделать это в SQLite, о котором я не думал?

В основном я хочу обновить три из четырех столбцов, если запись существует, Если он не существует, я хочу вставить запись со значением по умолчанию (NUL) для четвертого столбца.

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

(я пытаюсь избежать издержек SELECT, чтобы определить, нужно ли мне ОБНОВИТЬ или ВСТАВИТЬ, очевидно)

Предложения


Я не могу подтвердить, что синтаксис на сайте SQLite для TABLE CREATE. Я не создал демо для его тестирования, но, похоже, он не поддерживается ..

Если бы это было так, у меня есть три столбца, поэтому на самом деле это выглядело бы так:

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    Blob1 BLOB ON CONFLICT REPLACE, 
    Blob2 BLOB ON CONFLICT REPLACE, 
    Blob3 BLOB 
);

но первые два двоичных объекта не вызовут конфликта, только идентификатор Так что я asusme Blob1 и Blob2 не будет заменен (по желанию)


ОБНОВЛЕНИЯ в SQLite, когда привязка данных является полной транзакцией, что означает Каждая отправляемая строка, подлежащая обновлению, требует: Подготовить / Связать / Шаг / Завершить операторы в отличие от INSERT, который позволяет использовать функцию сброса

Жизнь объекта оператора выглядит примерно так:

  1. Создать объект с помощью sqlite3_prepare_v2 ()
  2. Привязка значений к параметрам хоста с использованием интерфейсов sqlite3_bind_.
  3. Запустите SQL, вызвав sqlite3_step ()
  4. Сбросьте инструкцию с помощью sqlite3_reset (), затем вернитесь к шагу 2 и повторите.
  5. Уничтожить объект оператора с помощью sqlite3_finalize ().

ОБНОВЛЕНИЕ Я предполагаю, что медленнее по сравнению с INSERT, но как это сравнить с SELECT с использованием первичного ключа?

Возможно, мне следует использовать select для чтения 4-го столбца (Blob3), а затем использовать REPLACE для записи новой записи, смешивая исходный 4-й столбец с новыми данными для первых 3 столбцов?

Ответы [ 17 ]

3 голосов
/ 07 января 2009

Лучший из известных мне подходов - сделать обновление, а затем вставить. «Затраты на выбор» необходимы, но это не страшное бремя, поскольку вы выполняете поиск по первичному ключу, который является быстрым.

Вы сможете изменить приведенные ниже операторы, указав имена таблиц и полей, чтобы делать то, что вы хотите.

--first, update any matches
UPDATE DESTINATION_TABLE DT
SET
  MY_FIELD1 = (
              SELECT MY_FIELD1
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
 ,MY_FIELD2 = (
              SELECT MY_FIELD2
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
WHERE EXISTS(
            SELECT ST2.PRIMARY_KEY
            FROM
              SOURCE_TABLE ST2
             ,DESTINATION_TABLE DT2
            WHERE ST2.PRIMARY_KEY = DT2.PRIMARY_KEY
            );

--second, insert any non-matches
INSERT INTO DESTINATION_TABLE(
  MY_FIELD1
 ,MY_FIELD2
)
SELECT
  ST.MY_FIELD1
 ,NULL AS MY_FIELD2  --insert NULL into this field
FROM
  SOURCE_TABLE ST
WHERE NOT EXISTS(
                SELECT DT2.PRIMARY_KEY
                FROM DESTINATION_TABLE DT2
                WHERE DT2.PRIMARY_KEY = ST.PRIMARY_KEY
                );
3 голосов
/ 31 января 2017

Если кто-то хочет прочитать мое решение для SQLite в Кордове, я получил этот общий метод js благодаря ответу @david выше.

function    addOrUpdateRecords(tableName, values, callback) {
get_columnNames(tableName, function (data) {
    var columnNames = data;
    myDb.transaction(function (transaction) {
        var query_update = "";
        var query_insert = "";
        var update_string = "UPDATE " + tableName + " SET ";
        var insert_string = "INSERT INTO " + tableName + " SELECT ";
        myDb.transaction(function (transaction) {
            // Data from the array [[data1, ... datan],[()],[()]...]:
            $.each(values, function (index1, value1) {
                var sel_str = "";
                var upd_str = "";
                var remoteid = "";
                $.each(value1, function (index2, value2) {
                    if (index2 == 0) remoteid = value2;
                    upd_str = upd_str + columnNames[index2] + "='" + value2 + "', ";
                    sel_str = sel_str + "'" + value2 + "', ";
                });
                sel_str = sel_str.substr(0, sel_str.length - 2);
                sel_str = sel_str + " WHERE NOT EXISTS(SELECT changes() AS change FROM "+tableName+" WHERE change <> 0);";
                upd_str = upd_str.substr(0, upd_str.length - 2);
                upd_str = upd_str + " WHERE remoteid = '" + remoteid + "';";                    
                query_update = update_string + upd_str;
                query_insert = insert_string + sel_str;  
                // Start transaction:
                transaction.executeSql(query_update);
                transaction.executeSql(query_insert);                    
            });
        }, function (error) {
            callback("Error: " + error);
        }, function () {
            callback("Success");
        });
    });
});
}

Итак, сначала подберите имена столбцов с помощью этой функции:

function get_columnNames(tableName, callback) {
myDb.transaction(function (transaction) {
    var query_exec = "SELECT name, sql FROM sqlite_master WHERE type='table' AND name ='" + tableName + "'";
    transaction.executeSql(query_exec, [], function (tx, results) {
        var columnParts = results.rows.item(0).sql.replace(/^[^\(]+\(([^\)]+)\)/g, '$1').split(','); ///// RegEx
        var columnNames = [];
        for (i in columnParts) {
            if (typeof columnParts[i] === 'string')
                columnNames.push(columnParts[i].split(" ")[0]);
        };
        callback(columnNames);
    });
});
}

Затем создайте транзакции программно.

«Значения» - это массив, который вы должны построить раньше, и он представляет строки, которые вы хотите вставить или обновить в таблице.

"remoteid" - это идентификатор, который я использовал в качестве ссылки, поскольку я синхронизируюсь с моим удаленным сервером.

Для использования плагина SQLite Cordova, пожалуйста, обратитесь к официальной ссылке

1 голос
/ 30 декабря 2017

Вслед за Аристотелем Пагальцисом и идеей COALESCE из ответа Эрика Б , здесь есть вариант upsert для обновления только нескольких столбцов или вставки полной строки, если она не существует .

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

ПРИМЕЧАНИЕ id принудительно устанавливается в NULL, когда INSERT, как предполагается, является автоинкрементом. Если это просто сгенерированный первичный ключ, тогда можно использовать COALESCE (см. Комментарий Аристотеля Пагальциса ).

WITH new (id, name, title, content, author)
     AS ( VALUES(100, 'about', 'About this site', 'Whatever new content here', 42) )
INSERT OR REPLACE INTO page (id, name, title, content, author)
SELECT
     old.id, COALESCE(old.name, new.name),
     new.title, new.content,
     COALESCE(old.author, new.author)
FROM new LEFT JOIN page AS old ON new.name = old.name;

Таким образом, общее правило: если вы хотите сохранить старые значения, используйте COALESCE, когда вы хотите обновить значения, используйте new.fieldname

1 голос
/ 29 января 2016

Этот метод смешивает несколько других методов из ответа на этот вопрос и включает в себя использование CTE (Common Table Expressions). Я представлю запрос и объясню, почему я сделал то, что сделал.

Я хотел бы изменить фамилию сотрудника 300 на ДЭВИС, если есть сотрудник 300. В противном случае я добавлю нового сотрудника.

Название таблицы: сотрудники Столбцы: идентификатор, имя, фамилия, фамилия

Запрос:

INSERT OR REPLACE INTO employees (employee_id, first_name, last_name)
WITH registered_employees AS ( --CTE for checking if the row exists or not
    SELECT --this is needed to ensure that the null row comes second
        *
    FROM (
        SELECT --an existing row
            *
        FROM
            employees
        WHERE
            employee_id = '300'

        UNION

        SELECT --a dummy row if the original cannot be found
            NULL AS employee_id,
            NULL AS first_name,
            NULL AS last_name
    )
    ORDER BY
        employee_id IS NULL --we want nulls to be last
    LIMIT 1 --we only want one row from this statement
)
SELECT --this is where you provide defaults for what you would like to insert
    registered_employees.employee_id, --if this is null the SQLite default will be used
    COALESCE(registered_employees.first_name, 'SALLY'),
    'DAVIS'
FROM
    registered_employees
;

По сути, я использовал CTE, чтобы уменьшить количество раз, когда оператор select должен использоваться для определения значений по умолчанию. Поскольку это CTE, мы просто выбираем нужные столбцы из таблицы, и оператор INSERT использует это.

Теперь вы можете решить, какие значения по умолчанию вы хотите использовать, заменив пустые значения в функции COALESCE на значения, которые должны быть.

1 голос
/ 07 января 2009

Я думаю, это может быть то, что вы ищете: ON CONFLICT предложение .

Если вы определите свою таблицу следующим образом:

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    field1 TEXT 
); 

Теперь, если вы делаете INSERT с уже существующим идентификатором, SQLite автоматически выполняет UPDATE вместо INSERT.

Hth ...

0 голосов
/ 07 октября 2010

Только что прочитав эту ветку и разочаровавшись в том, что это просто "UPSERT" было нелегко, я продолжил расследование ...

Вы действительно можете легко и просто сделать это в SQLITE.

Вместо использования: INSERT INTO

Использование: INSERT OR REPLACE INTO

Это именно то, что вы хотите!

0 голосов
/ 02 мая 2014
SELECT COUNT(*) FROM table1 WHERE id = 1;

если COUNT(*) = 0

INSERT INTO table1(col1, col2, cole) VALUES(var1,var2,var3);

иначе, если COUNT(*) > 0

UPDATE table1 SET col1 = var4, col2 = var5, col3 = var6 WHERE id = 1;
...