Fundo
Aqui está um exemplo próximo do que estou trabalhando:
CREATE TABLE sandboxTesting.TemporalTest (
GroupNumber VARCHAR(25) NOT NULL,
StartEffectiveWhen DATE NOT NULL,
EndEffectiveWhen DATE NULL,
ModifiedWhen DATETIME NULL,
IsReady BIT NOT NULL DEFAULT 0,
RowValidFrom DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
RowValidTo DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME (RowValidFrom, RowValidTo),
CONSTRAINT PK_TemporalTest PRIMARY KEY CLUSTERED
(
GroupNumber, StartEffectiveWhen
)
) WITH (SYSTEM_VERSIONING=ON (HISTORY_TABLE=sandboxTesting.TemporalTestHistory))
GO
CREATE TRIGGER sandboxTesting.OnModify ON sandboxTesting.TemporalTest AFTER UPDATE AS
BEGIN
UPDATE temporalTst
SET temporalTst.IsReady = 0,
temporalTst.ModifiedWhen = GETDATE()
FROM sandboxTesting.TemporalTest temporalTst
JOIN deleted del
ON del.GroupNumber = temporalTst.GroupNumber
AND del.StartEffectiveWhen = temporalTst.StartEffectiveWhen
WHERE -- All business columns go here with OR statements in between them.
-- The idea is that if anything changes except the IsReady flag, then we
-- set the IsReady back to false. (IsReady has to be set by itself)
del.EndEffectiveWhen <> temporalTst.EndEffectiveWhen
OR (del.EndEffectiveWhen IS NULL AND temporalTst.EndEffectiveWhen IS NOT NULL)
OR (del.EndEffectiveWhen IS NOT NULL AND temporalTst.EndEffectiveWhen IS NULL)
END
GO
-- Insert new test
INSERT INTO [sandboxTesting].[TemporalTest] ([GroupNumber], [StartEffectiveWhen], [EndEffectiveWhen], [ModifiedWhen])
VALUES ('12345', '2024-01-1', NULL, NULL)
GO
-- Set is as ready
UPDATE sandboxTesting.TemporalTest
SET IsReady = 1
WHERE GroupNumber = '12345' AND StartEffectiveWhen = '2024-01-1'
GO
-- Change the End date
UPDATE sandboxTesting.TemporalTest
SET EndEffectiveWhen = '2024-09-02'
WHERE GroupNumber = '12345' AND StartEffectiveWhen = '2024-01-1'
-- Set the new end date as ready for billing.
UPDATE sandboxTesting.TemporalTest
SET IsReady = 1
WHERE GroupNumber = '12345' AND StartEffectiveWhen = '2024-01-1'
GO
-- Select the Data
SELECT * FROM sandboxTesting.TemporalTest for SYSTEM_TIME ALL
ORDER BY GroupNumber, StartEffectiveWhen desc, RowValidFrom DESC, RowValidTo DESC, ModifiedWhen desc
-- Select the Raw Data (for comparison)
SELECT * FROM sandboxTesting.TemporalTest
UNION ALL
SELECT * FROM sandboxTesting.TemporalTestHistory
ORDER BY GroupNumber, StartEffectiveWhen desc, RowValidFrom DESC, RowValidTo DESC, ModifiedWhen desc
Quando executo isso, este é o primeiro resultado:
Número do grupo | InícioEfetivoQuando | FimEfetivoQuando | ModificadoQuando | Está pronto | LinhaVálidaDe | LinhaVálidaPara |
---|---|---|---|---|---|---|
12345 | 2024-01-01 | 2024-09-02 | 2024-08-29 17:15:28.587 | 1 | 2024-08-29 23:15:28.5764223 | 9999-12-31 23:59:59.9999999 |
12345 | 2024-01-01 | NULO | NULO | 1 | 2024-08-29 23:15:28.5295658 | 2024-08-29 23:15:28.5764223 |
12345 | 2024-01-01 | NULO | NULO | 0 | 2024-08-29 23:15:28.4826980 | 2024-08-29 23:15:28.5295658 |
E o segundo conjunto de saída se parece com isto:
Número do grupo | InícioEfetivoQuando | FimEfetivoQuando | ModificadoQuando | Está pronto | LinhaVálidaDe | LinhaVálidaPara |
---|---|---|---|---|---|---|
12345 | 2024-01-01 | 2024-09-02 | 2024-08-29 17:15:28.587 | 1 | 2024-08-29 23:15:28.5764223 | 9999-12-31 23:59:59.9999999 |
12345 | 2024-01-01 | 2024-09-02 | 2024-08-29 17:15:28.587 | 0 | 2024-08-29 23:15:28.5764223 | 2024-08-29 23:15:28.5764223 |
12345 | 2024-01-01 | 2024-09-02 | NULO | 1 | 2024-08-29 23:15:28.5764223 | 2024-08-29 23:15:28.5764223 |
12345 | 2024-01-01 | NULO | NULO | 1 | 2024-08-29 23:15:28.5295658 | 2024-08-29 23:15:28.5764223 |
12345 | 2024-01-01 | NULO | NULO | 0 | 2024-08-29 23:15:28.4826980 | 2024-08-29 23:15:28.5295658 |
Isso é diferente porque o primeiro resultado da consulta usa a for SYSTEM_TIME ALL
cláusula, enquanto o segundo apenas consulta os dados brutos.
A diferença é que, no primeiro conjunto de dados, a segunda e a terceira linhas do segundo conjunto de dados foram filtradas. Elas foram removidas porque a segunda e a terceira linhas têm uma data de início e uma data de término que são as mesmas. (Basicamente dizendo que essas linhas nunca estiveram realmente em vigor.)
Pergunta
O que preciso saber é se posso confiar que essa "diferença de tempo zero" sempre estará presente para dados que foram atualizados por meio de um AFTER
gatilho? (Preciso escrever algumas consultas que falharão se esse não for o caso.)
O que quero dizer com isso é o seguinte: se meu servidor estivesse sendo bombardeado por milhares de consultas, todas realizando quantidades absurdas de E/S e cálculos, os valores RowValidFrom
e RowValidTo
para a segunda e terceira linhas do segundo conjunto de dados ainda teriam uma diferença de 0?
Em outras palavras, esses valores são os mesmos por causa da lógica da transação? Ou são os mesmos porque meu servidor é rápido e não está realmente sob nenhuma pressão agora?
No cenário que você construiu, NÃO, a 2ª linha não pode ter certeza de que foi atualizada pelo
AFTER
gatilho. Mas a 3ª linha pode ser tratada como uma ação que dispara o gatilho.Executei seu script no meu laptop lento, o resultado é este . O tempo de¶ da 2ª linha não é o mesmo. Mas a 3ª linha tem o mesmo tempo de¶, isso ocorre porque uma linha
TemporalTest
foi modificada várias vezes na mesma transação. No seu exemplo, fase 1: atualizaçãoEndEffectiveWhen
, isso foi registrado na 3ª linha; fase 2:AFTER TRIGGER
foi disparado para atualizaçãoIsReady
, isso foi registrado na 2ª linha. A tabela de histórico marca esses registros com o mesmoValidFrom
eValidTo
tempo para identificar que as atualizações foram realizadas na mesma PK dentro da mesma transação e registra o tempo de término da transação nas últimas linhas do históricoValidTo
. Você pode verificar a descrição no documento .Não. Linhas com um período de validade zero aparecem sempre que você atualiza a mesma linha várias vezes dentro da mesma transação (mesmo que nenhum valor de coluna seja realmente alterado). Apenas a alteração de linha final (efetiva) dentro de uma transação pode ter um período de validade diferente de zero.
No seu caso, o gatilho faz a alteração final, então seu período de validade será diferente de zero, em geral. Ele ainda pode ter duração zero se toda a transação e uma alteração subsequente forem concluídas abaixo da resolução do timer em uso.
Lembre-se de que um gatilho é executado na transação da declaração de gatilho. Mesmo que não haja transação explícita ou implícita, sempre há a transação autocommit iniciada pela atualização de origem.
Quando executo seu exemplo como um único lote com planos de execução desativados para minimizar atrasos entre instruções, vejo:
A
ASOF
consulta retorna apenas duas linhas porque as instruções são concluídas tão rapidamente que três das entradas do histórico têm períodos de validade zero.Em particular, alterar a data de término, a execução subsequente do gatilho e, finalmente, definir IsReady como 1 parecem ser concluídos instantaneamente. Como resultado, a
ASOF
visualização faz parecer que IsReady nunca foi definido como zero.Você pode evitar esse tipo de resultado em seus testes introduzindo um pequeno
WAITFOR
atraso no gatilho ou ativando planos de execução reais no SSMS. Em qualquer caso, o atraso extra evitará que operações muito rápidas sejam registradas com períodos de validade zero.Com os planos de execução em andamento, vejo:
Os atrasos significam que a
ASOF
consulta agora mostra ambas as transições IsReady de 0 para 1.Você também pode executar suas instruções de teste uma de cada vez, em vez de todas de uma vez em um único lote.
Nada disso alterará o fato de que as alterações feitas por um gatilho ocorrem dentro da transação da instrução de gatilho, serão a alteração final em uma linha específica dentro dessa transação e, portanto, o único evento com um período de validade diferente de zero.