Criei uma tabela de calendário de pesquisa "Daylight Savings" para a região GMT. A função que estou usando para consultar a tabela para retornar a data e hora local de uma data e hora UTC está tendo um desempenho ruim.
Qualquer ajuda para melhorar isso, incluindo mudar a forma como o TVF é codificado, seria apreciada.
A função será usada em consultas que podem retornar mais de 1 milhão de linhas com frequência. A função é usada ao consultar as tabelas do armazém que contêm dados de viagem.
As datas de início e término das viagens são armazenadas em UTC e a função acima é usada para convertê-las em hora local. Um desenvolvedor, há muito afastado da empresa, escreveu uma função escalar que converte a hora UTC para a hora local. Fui encarregado de reescrever essa função usando uma tabela de calendário e um TVF, pois o TVF deve ter um desempenho melhor do que as funções escalares
Sem a função:
SQL Server Execution Times: CPU time = 4633 ms, elapsed time = 4909 ms.
Com a função:
SQL Server Execution Times: CPU time = 20795 ms, elapsed time = 21176 ms.
Aqui está um exemplo de saída da tabela
CREATE TABLE dbo.DSTLookup
(
[Id] int,
[Tzid] int,
[DT_WhenSwitch] datetime,
[DSTOffSetSeconds] int,
[GMTOffSetSeconds] int
)
INSERT INTO dbo.DSTLookup
VALUES (29, 2, N'2014-03-30T01:00:00', 3600, 0),
(30, 2, N'2014-10-26T02:00:00', 0, 0),
(31, 2, N'2015-03-29T01:00:00', 3600, 0),
(32, 2, N'2015-10-25T02:00:00', 0, 0),
(33, 2, N'2016-03-27T01:00:00', 3600, 0),
(34, 2, N'2016-10-30T02:00:00', 0, 0),
(35, 2, N'2017-03-26T01:00:00', 3600, 0),
(36, 2, N'2017-10-29T02:00:00', 0, 0),
(37, 2, N'2018-03-25T01:00:00', 3600, 0),
(38, 2, N'2018-10-28T02:00:00', 0, 0)
Este é o TVF:
CREATE FUNCTION dbo.FN_GetLocalTime_FromUTC_BasedOnTZId
(@StartDateTime DATETIME, @EndDateTime DATETIME, @Tzid INT)
/*=========================================================================
* 2017-03-27
* Returns local time from UTC time based on timeZoneId
*
==========================================================================*/
RETURNS TABLE
AS
RETURN
(
WITH cteStartDate AS
(
SELECT
RN = ROW_NUMBER() OVER (ORDER BY D.Id DESC),
D.DSTOffSetSeconds 's_DST_OffSet',
D.GMTOffSetSeconds 's_GMT_OffSet'
FROM
dbo.DSTLookup D
WHERE
D.DT_WhenSwitch <= @StartDateTime
AND D.Tzid = @Tzid
),
cteEndDate AS
(
SELECT
RN = ROW_NUMBER() OVER (ORDER BY D.Id DESC),
D.DSTOffSetSeconds 'e_DST_OffSet',
D.GMTOffSetSeconds 'e_GMT_OffSet'
FROM
dbo.DSTLookup D
WHERE
D.DT_WhenSwitch <= @EndDateTime
AND D.Tzid = @Tzid
),
cteConvertStartDate AS
(
SELECT
DATEADD(SECOND, (COALESCE(S.s_DST_OffSet, 0) + COALESCE(S.s_GMT_OffSet, 0)), @StartDateTime) 'LocalStartDateTime'
FROM
cteStartDate S
WHERE
S.RN = 1
),
cteConvertEndDate AS
(
SELECT
DATEADD(SECOND, (COALESCE(E.e_DST_OffSet, 0) + COALESCE(E.e_GMT_OffSet, 0)), @EndDateTime) 'LocalEndDateTime'
FROM
cteEndDate E
WHERE
E.RN = 1
)
SELECT
S.LocalStartDateTime, E.LocalEndDateTime
FROM
cteConvertStartDate S, cteConvertEndDate E
);
GO
Para consultar o TVF:
SELECT *
FROM dbo.FN_GetLocalTime_FromUTC_BasedOnTzId
('2017-03-27 10:00:30', '2017-03-27 10:15:54', 2);
Plano de execução seguindo as recomendações de Max para incluir a chave primária.
Se
Tzid
eDT_WhenSwitch
definir uma linha exclusiva, recomendo agrupar adbo.DSTLookup
tabela por essas duas colunas. Você pode tornar essas colunas a chave primária, se desejar, ou apenas torná-las o índice clusterizado.A razão para fazer isso é permitir pesquisas de linha individuais muito rápidas. Para ambas as consultas na tabela que você deseja filtrar
[Tzid]
e encontrar o primeiro[DT_WhenSwitch]
valor em ordem decrescente. Com o índice clusterizado correto, obter essa linha pode ser uma única busca de índice clusterizado.Para chegar ao plano que eu quero vou simplificar um pouco o TVF com as operadoras
APPLY
and .TOP
Também quero deixar bem óbvio para o otimizador que recebo apenas uma linha de cada vez. Aqui está uma implementação:Aqui está o plano de consulta para sua consulta de exemplo na pergunta:
Como esperado, fazemos apenas duas buscas no índice clusterizado:
Não consegui testar no SQL Server 2008, mas acho que essa sintaxe funcionará nessa plataforma. db violino para SQL Server 2014.
Torne sua função uma função com valor de tabela vinculada ao esquema adicionando
WITH SCHEMABINDING
àRETURNS TABLE
cláusula.Então:
Isso permite que o processador de consultas "in-line" a função. Isso permite várias otimizações, entre elas a capacidade de entender corretamente as estatísticas dos objetos referenciados na função.
Adicione um índice clusterizado à
dbo.DSTLookup
tabela. Isso permite que a consulta execute uma pesquisa em vez de uma verificação. Para o número de linhas em seus dados de amostra, isso provavelmente não fará uma grande diferença, mas para sua tabela real, pode fazer uma grande diferença.Como você tem uma
Id
coluna que parece ser um número inteiro monotonicamente crescente, talvez essa seja uma boa chave candidata a ser usada como chave primária clusterizada:Eu consideraria adicionar o seguinte índice com base no seu TVF: