Решения для INSERT OR UPDATE в SQL Server
Проблема: Обновление или вставка записи в таблице
Предположим, у нас есть структура таблицы MyTable(KEY, datafield1, datafield2...)
.
Я часто сталкиваюсь с необходимостью либо обновить существующую запись, либо вставить новую, если запись с таким ключом не существует.
Основная логика выглядит так:
IF (ключ существует)
выполнить команду обновления
ELSE
выполнить команду вставки
Какой самый эффективный способ реализовать это?
5 ответ(ов)
Не забывайте о транзакциях. Производительность, безусловно, хороша, но простой подход (IF EXISTS..) может быть очень опасен.
Когда несколько потоков пытаются выполнить операцию "вставить или обновить" (Insert-or-update), вы легко можете столкнуться с нарушением ограничения первичного ключа (PK violation).
Решения, предложенные @Beau Crawford и @Esteban, показывают общий подход, но могут быть подвержены ошибкам.
Чтобы избежать взаимных блокировок (deadlocks) и нарушений первичного ключа, вы можете использовать что-то вроде следующего:
begin tran
if exists (select * from table with (updlock, serializable) where key = @key)
begin
update table set ...
where key = @key
end
else
begin
insert into table (key, ...)
values (@key, ...)
end
commit tran
или
begin tran
update table with (serializable) set ...
where key = @key
if @@rowcount = 0
begin
insert into table (key, ...) values (@key, ...)
end
commit tran
Эти подходы помогут вам безопаснее обрабатывать операции вставки и обновления в многопоточной среде.
Чтобы выполнить операцию UPSERT в SQL, вы можете использовать следующий подход, который состоит из двух частей: сначала выполняем UPDATE
, а затем проверяем, была ли обновлена хотя бы одна строка. Если обновленных строк нет, выполняем INSERT
. Пример кода:
UPDATE MyTable SET FieldA = @FieldA WHERE Key = @Key;
IF @@ROWCOUNT = 0
INSERT INTO MyTable (Key, FieldA) VALUES (@Key, @FieldA);
В этом коде:
- Сначала мы обновляем значение
FieldA
для строки, гдеKey
соответствует заданному параметру@Key
. - Затем с помощью
IF @@ROWCOUNT = 0
проверяем, было ли обновлено хотя бы одно значение. Если@@ROWCOUNT
равно 0, значит, строка с указаннымKey
неExists, и мы выполняемINSERT
, чтобы добавить новую запись.
Этот метод гарантирует, что если строка уже существует, она будет обновлена, а если нет — добавится новая строка.
Также стоит отметить, что в некоторых СУБД могут существовать более элегантные или оптимизированные способы выполнения UPSERT, такие как использование команды MERGE
. Обязательно ознакомьтесь с документацией к вашей конкретной СУБД для более эффективных решений.
Ваш запрос на SQL можно перевести и переформулировать так:
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, propertyTwo = propTwo, ...
ELSE
INSERT INTO [Table] (propertyOne, propertyTwo, ...) VALUES (propOne, propTwo, ...)
Однако, как вы правильно заметили в своем комментарии (Edit), решения, которые избегают использования SELECT
в этом контексте, действительно могут оказаться более эффективными, так как они выполняют задачу с меньшим количеством шагов.
Для более эффективного подхода вы можете рассмотреть использование конструкции MERGE
, если ваша СУБД это поддерживает. Например:
MERGE INTO [Table] AS target
USING (SELECT rowID AS ID, propOne, propTwo FROM dual) AS source
ON target.ID = source.ID
WHEN MATCHED THEN
UPDATE SET propertyOne = source.propOne, propertyTwo = source.propTwo, ...
WHEN NOT MATCHED THEN
INSERT (propertyOne, propertyTwo, ...) VALUES (source.propOne, source.propTwo, ...);
Таким образом, вы сможете обновить или вставить запись в зависимости от того, существует ли она уже, за один шаг.
Хотя сейчас уже поздно комментировать это, я хотел бы добавить более полный пример использования MERGE.
Такие операции вставки и обновления обычно называют "Upsert" и могут быть реализованы с помощью MERGE в SQL Server.
Хороший пример можно найти по следующей ссылке:
http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
В приведенном примере также объясняются сценарии блокировок и конкуренции.
Вот сам пример для справки:
ALTER PROCEDURE dbo.Merge_Foo2
@ID int
AS
SET NOCOUNT, XACT_ABORT ON;
MERGE dbo.Foo2 WITH (HOLDLOCK) AS f
USING (SELECT @ID AS ID) AS new_foo
ON f.ID = new_foo.ID
WHEN MATCHED THEN
UPDATE
SET f.UpdateSpid = @@SPID,
UpdateTime = SYSDATETIME()
WHEN NOT MATCHED THEN
INSERT
(
ID,
InsertSpid,
InsertTime
)
VALUES
(
new_foo.ID,
@@SPID,
SYSDATETIME()
);
RETURN @@ERROR;
Этот код представляет собой хранимую процедуру, которая выполняет операцию MERGE, обновляя запись, если совпадение найдено, или вставляя новую запись, если совпадений нет. Обратите внимание на использование HOLDLOCK для предотвращения конфликтов в многопользовательской среде.
Вы можете использовать следующий код для выполнения операции MERGE в SQL Server, изменяя имена таблиц и полей согласно вашим требованиям:
/*
CREATE TABLE MyTable (
id INT IDENTITY(0,1) NOT NULL,
field1 INT NOT NULL,
field2 INT NOT NULL,
isActive BIT NULL,
CONSTRAINT PK_MyTable PRIMARY KEY (id)
)
GO
--*/
DECLARE @field1 INT = 100, @field2 INT = 200, @isActive BIT = 1;
MERGE dbo.MyTable WITH (HOLDLOCK) AS target
-- задаем источник данных с одной строкой
USING (VALUES (@field1, @field2, @isActive))
AS source (field1, field2, isActive)
-- здесь условие соединения
ON target.field1 = source.field1 AND target.field2 = source.field2
WHEN MATCHED THEN
UPDATE
-- здесь укажите списки полей для обновления
SET target.isActive = source.isActive
WHEN NOT MATCHED THEN
-- вставляем новую строку из источника
INSERT (field1, field2, isActive)
VALUES (source.field1, source.field2, source.isActive);
GO
Замените MyTable
, field1
, field2
, и isActive
на актуальные имена таблиц и полей в вашей базе данных. Убедитесь, что условие в секции ON
правильно отражает логику сопоставления ваших данных. Также задайте корректные значения и типы данных для переменных в секции DECLARE
.
Вставка нескольких строк в одном SQL-запросе?
Сброс начального значения идентификатора после удаления записей в SQL Server
Как вставить несколько строк в SQLite?
Как восстановить дамп-файл из mysqldump?
Как вставить перенос строки в строке VARCHAR/NVARCHAR SQL Server