Практика SQL Server

Temporal history SQL Server: что означает AS OF

Читайте прежние версии строк, различая системное время, бизнес-периоды, начало транзакции и дополнительные требования к регистрации изменений.

При изменении цены с 10 на 12 обычная таблица сохраняет новое значение, если приложение отдельно не пишет историю. Системно-версионируемая temporal-таблица может автоматически сохранять предыдущие версии. Это полезно для расследования, но разные исторические вопросы не становятся одинаковыми.

Различайте значение по правилам системного времени SQL Server, предполагаемую бизнес-дату действия и человека, разрешившего изменение. Temporal history непосредственно отвечает на первый вопрос. Остальным нужны дополнительные данные и процесс.

Наблюдаем текущую и прежнюю версии

Пример SQL Server 2016 и новее создаёт постоянные учебные таблицы. Запускайте его с autocommit без внешней транзакции.

-- Use a disposable database, autocommit, and no enclosing transaction.
IF @@TRANCOUNT <> 0 THROW 50001, 'Use a separate practice connection.', 1;
CREATE TABLE dbo.PriceTemporalDemo (
    ProductId int NOT NULL PRIMARY KEY,
    Price decimal(12,2) NOT NULL,
    ValidFrom datetime2(7) GENERATED ALWAYS AS ROW START NOT NULL,
    ValidTo datetime2(7) GENERATED ALWAYS AS ROW END NOT NULL,
    PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
) WITH (SYSTEM_VERSIONING = ON (
    HISTORY_TABLE = dbo.PriceTemporalDemoHistory
));
INSERT dbo.PriceTemporalDemo(ProductId, Price) VALUES (1, 10.00);
DECLARE @BeforeChange datetime2(7) = SYSUTCDATETIME();
WAITFOR DELAY '00:00:01';
UPDATE dbo.PriceTemporalDemo SET Price = 12.00 WHERE ProductId = 1;
SELECT ProductId, Price FROM dbo.PriceTemporalDemo WHERE ProductId = 1;
SELECT ProductId, Price, ValidFrom, ValidTo
FROM dbo.PriceTemporalDemo FOR SYSTEM_TIME AS OF @BeforeChange
WHERE ProductId = 1;

Текущий запрос возвращает 12,00, а AS OF @BeforeChange показывает 10,00. Temporal-синтаксис сам обращается к подходящим текущим и историческим версиям, поэтому вручную объединять таблицы не требуется.

Период содержит UTC datetime2. AS OF выбирает начало не позднее заданного момента и конец строго после него. Верхняя граница исключена. Передавайте правильно преобразованный параметр UTC вместо местных часов с предполагаемым смещением.

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

Следующий запрос показывает интервалы версий.

SELECT ProductId, Price, ValidFrom, ValidTo
FROM dbo.PriceTemporalDemo FOR SYSTEM_TIME ALL
WHERE ProductId = 1
ORDER BY ValidFrom, ValidTo;

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

Понимаем часы транзакции

Границы периода определяются началом транзакции, а не COMMIT. Длинная транзакция способна добавить версию, чьё системное начало предшествует первой возможности другого соединения увидеть подтверждённое значение. AS OF следует именно этим правилам, а не записывает точный вид каждого конкурентного читателя.

Несколько изменений одной строки внутри транзакции могут создать версии нулевой длительности. Temporal-клаузы исключают их из результата; прямое чтение исторической таблицы способно показать дополнительные записи. Не каждый отсутствующий промежуточный вариант означает потерю.

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

Одинаковый AS OF для нескольких temporal-таблиц упрощает исторические соединения. Но проверяйте участие неверсионируемых справочников. Старый заказ с сегодняшним изменяемым названием категории даёт смешанный во времени отчёт.

Обслуживаем историю как реальные данные

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

Хранение и очистка требуют определённых правил. Удалённые версии нельзя восстановить историческим запросом. Документируйте доступный горизонт и контролируйте механизм очистки установленной версии.

Temporal history не заменяет резервную копию и неизменяемый аудит. Она автоматически не записывает человека и причину, а привилегированное обслуживание способно изменить конфигурацию. Нужную атрибуцию храните отдельно с соответствующим доступом.

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

Проверьте обновление, удаление, длинную транзакцию, повторные изменения одной строки и точный момент перехода версии. Дополнительно убедитесь, что приложение корректно сериализует UTC-параметр без двойного преобразования. Полезный дизайн позволяет отличить системную запись истории от бизнес-смысла времени.

Техническая документация: Microsoft Learn: Query temporal data · Microsoft Learn: Temporal considerations.

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

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

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

Inquiries are not enabled in this preview.

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