Ответ Эрика Б в порядке, если вы хотите сохранить один или два столбца из существующей строки. Если вы хотите сохранить много столбцов, он становится слишком громоздким.
Вот подход, который хорошо масштабируется для любого количества столбцов с обеих сторон. Для иллюстрации приведу следующую схему:
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 сам выбирает значение идентификатора при вставке новой строки. Но при обновлении существующей строки на основе ее name
я хочу, чтобы она продолжала иметь старое значение идентификатора (очевидно!).
Я достиг истинного 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
, чтобы сказать «вставьте новый контент, если таковой был, иначе сохраните старый контент» - например, если вы используете фиксированный запрос и связываете новые значения с заполнителями.