Я не совсем уверен, но у меня складывается впечатление, что этот вопрос действительно касается upsert, который является следующей атомарной операцией:
- Если строка существует и в источнике, и в цели,
UPDATE
цель;
- Если строка существует только в источнике,
INSERT
строка в цели;
- (Необязательно) Если строка существует в цели, но не источник,
DELETE
строка из цели.
Разработчики, ставшие администраторами баз данных, часто наивно пишут это построчно, например:
-- For each row in source
IF EXISTS(<target_expression>)
IF @delete_flag = 1
DELETE <target_expression>
ELSE
UPDATE target
SET <target_columns> = <source_values>
WHERE <target_expression>
ELSE
INSERT target (<target_columns>)
VALUES (<source_values>)
Это самое худшее, что вы можете сделать по нескольким причинам:
Состояние гонки. Строка может исчезнуть между IF EXISTS
и последующими DELETE
или UPDATE
.
Это расточительно. Для каждой транзакции выполняется дополнительная операция; может быть, это тривиально, но это полностью зависит от того, насколько хорошо вы проиндексировали.
Хуже всего - это следовать итерационной модели, думая об этих проблемах на уровне одной строки. Это будет иметь самое большое (худшее) влияние из всех на общую производительность.
Одна очень незначительная (и я подчеркиваю незначительная) оптимизация - это просто попытаться все равно UPDATE
; если строка не существует, @@ROWCOUNT
будет 0, и вы можете "безопасно" вставить:
-- For each row in source
BEGIN TRAN
UPDATE target
SET <target_columns> = <source_values>
WHERE <target_expression>
IF (@@ROWCOUNT = 0)
INSERT target (<target_columns>)
VALUES (<source_values>)
COMMIT
В худшем случае, при этом все равно будут выполняться две операции для каждой транзакции, но, по крайней мере, есть шанс выполнить только одну, и это также устраняет условие гонки (вид).
Но реальная проблема заключается в том, что это все еще делается для каждой строки в источнике.
До SQL Server 2008 вам приходилось использовать неудобную трехэтапную модель, чтобы справиться с этим на заданном уровне (все же лучше, чем строка за строкой):
BEGIN TRAN
INSERT target (<target_columns>)
SELECT <source_columns> FROM source s
WHERE s.id NOT IN (SELECT id FROM target)
UPDATE t SET <target_columns> = <source_columns>
FROM target t
INNER JOIN source s ON t.d = s.id
DELETE t
FROM target t
WHERE t.id NOT IN (SELECT id FROM source)
COMMIT
Как я уже сказал, производительность была довольно паршивой, но все же намного лучше, чем подход «один ряд за раз». Однако в SQL Server 2008 наконец-то появился синтаксис MERGE , поэтому теперь все, что вам нужно сделать, это:
MERGE target
USING source ON target.id = source.id
WHEN MATCHED THEN UPDATE <target_columns> = <source_columns>
WHEN NOT MATCHED THEN INSERT (<target_columns>) VALUES (<source_columns>)
WHEN NOT MATCHED BY SOURCE THEN DELETE;
Вот и все. Одно утверждение. Если вы используете SQL Server 2008 и вам нужно выполнить любую последовательность INSERT
, UPDATE
и DELETE
в зависимости от того, существует ли уже строка - , даже если это только одна строка - там нет извините, чтобы не использовать MERGE
.
Вы можете даже OUTPUT
строк, затронутых MERGE
в табличной переменной, если вам нужно выяснить, что было сделано позже. Просто, быстро и без риска. Сделай это.