Вопрос

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

Вставить Обновить сохраненный процесс на SQL Server

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

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

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

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

Предложения?


Я не могу подтвердить этот синтаксис на сайте SQLite для СОЗДАНИЯ ТАБЛИЦЫ.Я не создавал демо-версию, чтобы протестировать ее, но, похоже, она не поддерживается..

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

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

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


Обновления в SQLite при привязке данных представляют собой завершенную транзакцию, что означает Для обновления каждой отправленной строки требуется:Инструкции Prepare /Bind /Step /Finalize в отличие от INSERT, который позволяет использовать функцию сброса

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

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

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

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

Это было полезно?

Решение

Предполагая, что в таблице 3 столбца..Идентификатор, ИМЯ, РОЛЬ


ПЛОХОЙ: Это приведет к вставке или замене всех столбцов новыми значениями для ID = 1:

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (1, 'John Foo', 'CEO');

ПЛОХОЙ: Это позволит вставить или заменить 2 столбца...столбцу NAME будет присвоено значение NULL или значение по умолчанию:

INSERT OR REPLACE INTO Employee (id, role) 
  VALUES (1, 'code monkey');

хорошо: Это обновит 2 столбца.Когда ID=1 существует, ИМЯ не будет изменено.Если ID=1 не существует, имя будет по умолчанию (NULL).

INSERT OR REPLACE INTO Employee (id, role, name) 
  VALUES (  1, 
            'code monkey',
            (SELECT name FROM Employee WHERE id = 1)
          );

Это обновит 2 столбца.Когда ID=1 существует, РОЛЬ не будет затронута.Если ID=1 не существует, роли будет присвоено значение 'Benchwarmer' вместо значения по умолчанию.

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (  1, 
            'Susan Bar',
            COALESCE((SELECT role FROM Employee WHERE id = 1), 'Benchwarmer')
          );

Другие советы

ВСТАВИТЬ ИЛИ ЗАМЕНИТЬ - это НЕ эквивалентно "UPSERT".

Допустим, у меня есть таблица Employee с полями id, name и role:

INSERT OR REPLACE INTO Employee ("id", "name", "role") VALUES (1, "John Foo", "CEO")
INSERT OR REPLACE INTO Employee ("id", "role") VALUES (1, "code monkey")

Бум, вы потеряли имя сотрудника номер 1.SQLite заменил его значением по умолчанию.

Ожидаемым результатом обновления было бы изменение роли и сохранение имени.

Ответ Эрика Би это нормально, если вы хотите сохранить только один или, возможно, два столбца из существующей строки.Если вы хотите сохранить много столбцов, это быстро становится слишком громоздким.

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

 CREATE TABLE page (
     id      INTEGER PRIMARY KEY,
     name    TEXT UNIQUE,
     title   TEXT,
     content TEXT,
     author  INTEGER NOT NULL REFERENCES user (id),
     ts      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 );

Обратите внимание, в частности, на то , что name является естественным ключом строки – id используется только для внешних ключей, поэтому смысл в том, чтобы SQLite сам выбирал значение ID при вставке новой строки.Но при обновлении существующей строки на основе ее name, Я хочу, чтобы он по-прежнему сохранял старое значение ID (очевидно!).

Я достигаю истинного UPSERT со следующей конструкцией:

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

Точная форма этого запроса может немного отличаться.Ключевым моментом является использование INSERT SELECT с помощью внешнего соединения слева, чтобы присоединить существующую строку к новым значениям.

Здесь, если строка ранее не существовала, old.id будет NULL и затем SQLite автоматически присвоит идентификатор, но если такая строка уже была, old.id будет иметь фактическое значение, и это будет использовано повторно.Это именно то, чего я хотел.

На самом деле это очень гибко.Обратите внимание, как ts колонна полностью отсутствует со всех сторон – потому что у нее есть DEFAULT значение, SQLite просто сделает правильную вещь в любом случае, так что мне не нужно заботиться об этом самому.

Вы также можете включить столбец как на new и old стороны, а затем используйте, например COALESCE(new.content, old.content) во внешнем SELECT сказать “вставьте новый контент, если таковой был, в противном случае сохраните старый контент” – напримересли вы используете фиксированный запрос и привязываете новые значения с помощью заполнителей.

Если вы обычно делаете обновления , я бы так и сделал ..

  1. Начать транзакцию
  2. Выполните обновление
  3. Проверьте количество строк
  4. Если оно равно 0, выполните вставку
  5. Зафиксировать

Если вы обычно делаете вставки, я бы

  1. Начать транзакцию
  2. Попробуйте вставить
  3. Проверка на наличие ошибки нарушения первичного ключа
  4. если мы получили сообщение об ошибке, выполните обновление
  5. Зафиксировать

Таким образом, вы избегаете выбора, и вы транзакционно исправны на Sqlite.

Этот ответ был обновлен, и поэтому приведенные ниже комментарии больше не применяются.

2018-05-18 ОСТАНОВИТЕ НАЖАТИЕ.

Поддержка UPSERT в SQLite! Синтаксис UPSERT был добавлен в SQLite с версией 3.24.0 (в ожидании) !

UPSERT - это специальное синтаксическое дополнение к INSERT, которое заставляет ВСТАВКУ вести себя как ОБНОВЛЕНИЕ или как отказ от операции, если ВСТАВКА нарушает ограничение уникальности.UPSERT - это не стандартный SQL.UPSERT в SQLite следует синтаксису, установленному PostgreSQL.

enter image description here

альтернативно:

Другим совершенно другим способом сделать это является:В моем приложении я установил для своего идентификатора строки в памяти значение long.Максимальное значение при создании строки в памяти.(MaxValue никогда не будет использоваться в качестве идентификатора, который вы не проживете достаточно долго....Затем, если rowID не является этим значением, то оно уже должно быть в базе данных, поэтому нуждается в ОБНОВЛЕНИИ, если это MaxValue, то ему нужна вставка.Это полезно только в том случае, если вы можете отслеживать идентификаторы строк в своем приложении.

Я понимаю, что это старый поток, но в последнее время я работал в sqlite3 и придумал этот метод, который лучше соответствовал моим потребностям в динамической генерации параметризованных запросов:

insert or ignore into <table>(<primaryKey>, <column1>, <column2>, ...) values(<primaryKeyValue>, <value1>, <value2>, ...); 
update <table> set <column1>=<value1>, <column2>=<value2>, ... where changes()=0 and <primaryKey>=<primaryKeyValue>; 

Это все еще 2 запроса с предложением where в обновлении, но, похоже, делает свое дело.У меня также есть такое видение в моей голове, что sqlite может полностью оптимизировать инструкцию update, если вызов changes() больше нуля.Делает это на самом деле или нет, находится за пределами моего знания, но человек может мечтать, не так ли?;)

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

select case changes() WHEN 0 THEN last_insert_rowid() else <primaryKeyValue> end;

Вот решение, которое на самом деле представляет собой UPSERT (ОБНОВЛЕНИЕ или ВСТАВКУ) вместо INSERT ИЛИ REPLACE (что во многих ситуациях работает по-разному).

Это работает следующим образом:
1.Попробуйте обновить, существует ли запись с таким же идентификатором.
2.Если обновление не изменило ни одной строки (NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)), затем вставьте запись.

Таким образом, либо была обновлена существующая запись, либо будет выполнена вставка.

Важной деталью является использование SQL-функции changes() для проверки, попала ли инструкция update в какие-либо существующие записи, и выполнение инструкции insert только в том случае, если она не попала ни в одну запись.

Следует отметить, что функция changes() не возвращает изменения, выполняемые триггерами более низкого уровня (см. http://sqlite.org/lang_corefunc.html#changes), так что обязательно примите это во внимание.

Вот SQL...

Тестовое обновление:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 2;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 2, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

Тестовая вставка:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 3;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 3, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

Вы действительно можете выполнить upsert в SQLite, просто это выглядит немного иначе, чем вы привыкли.Это выглядело бы примерно так:

INSERT INTO table name (column1, column2) 
VALUES ("value12", "value2") WHERE id = 123 
ON CONFLICT DO UPDATE 
SET column1 = "value1", column2 = "value2" WHERE id = 123

Расширяясь на Ответ Аристотеля вы можете ВЫБРАТЬ из фиктивной таблицы 'singleton' (таблица вашего собственного создания с одной строкой).Это позволяет избежать некоторого дублирования.

Я также сохранил перенос примера в MySQL и SQLite и использовал столбец 'date_added' в качестве примера того, как вы могли бы задать столбец только в первый раз.

 REPLACE INTO page (
   id,
   name,
   title,
   content,
   author,
   date_added)
 SELECT
   old.id,
   "about",
   "About this site",
   old.content,
   42,
   IFNULL(old.date_added,"21/05/2013")
 FROM singleton
 LEFT JOIN page AS old ON old.name = "about";

Начиная с версии 3.24.0 SQLite поддерживает UPSERT.

Из Документация:

UPSERT - это специальное синтаксическое дополнение к INSERT, которое заставляет ВСТАВКУ вести себя как ОБНОВЛЕНИЕ или как отказ от операции, если ВСТАВКА нарушает ограничение уникальности.UPSERT - это не стандартный SQL.UPSERT в SQLite следует синтаксису, установленному PostgreSQL. Синтаксис UPSERT был добавлен в SQLite с версией 3.24.0 (в ожидании).

UPSERT - это обычный оператор INSERT, за которым следует специальное предложение ON CONFLICT

enter image description here

Источник изображения: https://www.sqlite.org/images/syntax/upsert-clause.gif

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

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

--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
                );

Если кто-то хочет прочитать мое решение для 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, пожалуйста, обратитесь к официальному Ссылка

Я думаю, это может быть тем, что вы ищете: О КОЛЛИЗИОННОЙ оговорке.

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

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

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

Хтх...

Этот метод ремиксирует несколько других методов из ответа на этот вопрос и включает в себя использование 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 на то, какими должны быть значения.

Следующие Аристотель Пагальцис и идея о 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

Только что прочитав эту тему и будучи разочарован тем, что было нелегко просто ответить на это "ОТКЛОНЕНИЕ", я продолжил расследование...

На самом деле вы можете сделать это напрямую и легко в SQLITE.

Вместо того, чтобы использовать: INSERT INTO

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

Это делает именно то, что вы хотите, чтобы это делалось!

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;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top