Практика SQL Server

SQL Server OUTPUT: получение результатов изменения

Получайте ключи и значения до и после изменения через OUTPUT с учётом порядка строк, триггеров, ошибок транзакции и повторов.

Изменение строк с последующим повторным SELECT отвечает на два разных вопроса: какие строки изменила команда и что сейчас содержат подходящие строки? При конкурентной работе ответы могут различаться. OUTPUT связывает результат непосредственно с изменением, позволяя точно получать созданные ключи и значения до и после операции.

Сохранение однозначной связи

Пример использует временные таблицы и изменяет две строки остатков. Он получает ключ, прежнее количество и новое количество из одного UPDATE. Финальный ORDER BY задаёт порядок представления, не полагаясь на физическую последовательность обновлений.

IF @@TRANCOUNT <> 0
    THROW 50000, 'This example owns its transaction.', 1;
SET XACT_ABORT ON;
CREATE TABLE #Stock (ItemId int PRIMARY KEY, Qty int NOT NULL);
INSERT #Stock VALUES (1, 12), (2, 20);
CREATE TABLE #Changed (ItemId int, OldQty int, NewQty int);
BEGIN TRY
    BEGIN TRAN;
    UPDATE #Stock
    SET Qty = Qty - 2
    OUTPUT inserted.ItemId, deleted.Qty, inserted.Qty
        INTO #Changed(ItemId, OldQty, NewQty)
    WHERE ItemId IN (1, 2) AND Qty >= 2;
    COMMIT;
    SELECT ItemId, OldQty, NewQty FROM #Changed ORDER BY ItemId;
END TRY
BEGIN CATCH
    IF XACT_STATE() <> 0 ROLLBACK;
    THROW;
END CATCH;

Обязательно сохраняйте ключ в результате. Если возвращать только количества, клиенту придётся угадывать соответствие исходным строкам. При массовой вставке храните уникальный корреляционный идентификатор клиента и возвращайте его вместе с созданной identity. Первая полученная identity не обязана соответствовать первой входной записи.

Захватывайте только нужные столбцы. OUTPUT inserted.* привязывает API ко всей структуре таблицы и может ненужно копировать большие значения. Явный список делает изменения типов заметными и уменьшает вероятность случайной выдачи внутренних полей.

Результат команды и успешная фиксация

Граница транзакции остаётся определяющей. OUTPUT не подтверждает окончательную фиксацию бизнес-операции. Последующая команда может завершиться ошибкой, транзакция может откатиться, а соединение потеряться во время подтверждения commit. Приложение должно обработать полный исход команды, включая ошибки, прежде чем считать данные признаком успеха.

Образец сохраняет выходные строки во временную таблицу, фиксирует транзакцию и только потом возвращает результат. При ошибке CATCH откатывает и повторно выдаёт исключение вместо успешного SELECT. Проверка запрещает внешнюю транзакцию: иначе вызывающий код мог бы отменить работу уже после получения положительного ответа.

Такой порядок не решает потерю ответа. Если commit прошёл, но клиент не получил результат, повтор должен найти прежний исход по постоянному идентификатору операции. Временная таблица исчезает вместе с сеансом и для этого непригодна. Если API обязан безопасно повторять запись, сохраняйте корреляцию и итог постоянно.

Триггеры и смысл аудита

Значения inserted в OUTPUT отражают изменение до выполнения AFTER-триггеров. Если триггер затем нормализует поле, полученное значение может отличаться от окончательно сохранённого. Определите, какой именно результат обещан клиенту. Для окончательных значений можно сначала сохранить ключи, затем организовать дополнительное чтение с подходящей изоляцией внутри понятного транзакционного контракта.

Прямой OUTPUT также имеет ограничения при включённых триггерах соответствующего действия. OUTPUT INTO часто подходит лучше, но у его получателя собственные ограничения по триггерам и ограничениям целостности. Проверяйте реальную схему, а не только пустую демонстрационную таблицу.

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

Наконец, измеряйте стоимость захвата при больших изменениях. Миллионы значений до и после способны значительно увеличить нагрузку на память, журнал и сеть. Узкий ответ из ключа и статуса часто лучше подходит API. Проверьте успех, принудительную ошибку, изменение поля триггером и соответствие нескольких входных строк. Особенно важно проверить клиент, который уже прочитал часть результата перед получением ошибки: он не должен публиковать эти строки как подтверждённые изменения. Дополнительно потеряйте ответ после успешной фиксации и убедитесь, что постоянная корреляция позволяет вернуть прежний итог без повторного обновления. Эти проверки превращают удобный синтаксис в надёжный договор записи.

Техническая документация: Microsoft Learn: OUTPUT · Microsoft Learn: TRY CATCH.

Вопрос по статье

Есть вопрос по этой теме?

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

Inquiries are not enabled in this preview.

Задать вопрос по этой статье