Eu tenho uma tabela com dados históricos sobre carros AutoData
com chave agrupada combinada Cas
(DateTime) + GCom
(Car ID). Um registro contém vários indicadores, como nível de combustível, estado do veículo etc.
Os intervalos entre os registros individuais de um carro na AutoData
tabela são irregulares, às vezes são 120 segundos, às vezes poucos segundos, às vezes horas etc. Preciso normalizar os registros para visualização, para que mostre um registro a cada 30 segundos.
Tenho o seguinte script:
DECLARE @GCom int = 2563,
@Od DateTime2(0) = '20170210',
@Do DateTime2(0) = '20170224'
--Create a table with intervals by 30 seconds
declare @temp Table ([cas] datetime2(0))
INSERT @temp([cas])
SELECT d
FROM
(
SELECT
d = DATEADD(SECOND, (rn - 1)*30, @Od)
FROM
(
SELECT TOP (DATEDIFF(MINUTE, @Od, @Do)*2)
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM
sys.all_objects AS s1
CROSS JOIN
sys.all_objects AS s2
ORDER BY
s1.[object_id]
) AS x
) AS y;
--Create temp table
CREATE TABLE #AutoData (
[Cas] [datetime2](0) NOT NULL PRIMARY KEY,
[IDProvozniRezim] [tinyint] NOT NULL,
[IDRidic] [smallint] NULL,
[Stav] [tinyint] NOT NULL,
[Klicek] [bit] NOT NULL,
[Alarm] [bit] NOT NULL,
[MAlarm] [tinyint] NOT NULL,
[DAlarm] [bit] NOT NULL,
[Bypass] [bit] NOT NULL,
[Lat] [real] NULL,
[Lon] [real] NULL,
[ObjemAktualni] [real] NOT NULL,
[RychlostMaxV1] [real] NOT NULL,
[RychlostV2] [real] NOT NULL,
[Otacky] [smallint] NOT NULL,
[Nadspotreba] [real] NOT NULL,
[Vzdalenost] [real] NOT NULL,
[Motor] [smallint] NOT NULL
)
--Populate the temp table selecting only relevant AutoData records
INSERT INTO #AutoData
SELECT [Cas]
,[IDProvozniRezim]
,[IDRidic]
,[Stav]
,[Klicek]
,[Alarm]
,[MAlarm]
,[DAlarm]
,[Bypass]
,[Lat]
,[Lon]
,[ObjemAktualni]
,[RychlostMaxV1]
,[RychlostV2]
,[Otacky]
,[Nadspotreba]
,[Vzdalenost]
,[Motor]
FROM AutoData a
WHERE a.GCom = @GCom AND a.cas BETWEEN @Od AND @do
--Select final data
SELECT t.cas, ad.malarm, ad.IDProvoznirezim, ad.Otacky, ad.motor, ad.objemAktualni, ad.Nadspotreba
FROM @temp t
OUTER APPLY (
SELECT TOP 1 stav, malarm, otacky,motor, objemAktualni, Nadspotreba, IDProvoznirezim FROM #AutoData a
WHERE DATEDIFF(SECOND, a.cas, t.cas)<=CASE WHEN Motor>120 THEN Motor ELSE 120 END
AND DATEDIFF(SECOND, a.cas, t.cas)>-30
ORDER BY CASE WHEN DATEDIFF(SECOND, a.cas, t.cas)>0 THEN DATEDIFF(SECOND, a.cas, t.cas) ELSE (DATEDIFF(SECOND, a.cas, t.cas)*-1) +120 END
) ad
DROP TABLE #AutoData
A princípio tentei escrever o script com apenas uma variável de tabela @temp colocando a condição WHERE a.GCom = @GCom AND a.cas BETWEEN @Od AND @do
na última seleção. O script levou 39 segundos para ser executado.
Quando eu usei #AutoData
a tabela temporária para pré-carregar o subconjunto de dados em uma tabela temporária como é mostrado no script acima, ele caiu para 5 segundos.
Então eu tentei usar uma variável de tabela @AutoData
em vez de #AutoData
- mas levou muito mais tempo - 22 segundos.
@temp
table tem 40320 registros e #AutoData
table tem 1904 registros para este exemplo. Mas, surpreendentemente, apenas usar #temp
tabela em vez de @temp
variável tornou a execução lenta novamente.
Fiquei surpreso ao ver essas diferenças usando ou não a tabela/variável temporária. Aparentemente, o SQL Server não pôde por si só otimizar o interior da cláusula OUTER APPLY.
Mas por que há uma diferença tão grande usando variáveis de tabela versus tabelas temporárias? Existe alguma outra maneira de saber, o que usar e não apenas tentar?
Plano de execução com tabela temporária #AutoData:
https://www.brentozar.com/pastetheplan/?id=B1y2x2Zcg
A chave está nesta parte da sua pergunta:
No plano de execução, passe o mouse sobre a varredura da tabela @temp. Compare o número estimado de linhas com o número real de linhas. (Se você quiser postar o plano em http://PasteThePlan.com , podemos fornecer detalhes mais específicos. Isenção de responsabilidade: esse é o site da minha empresa.)
Você verá que o número estimado de linhas é muito baixo.
O SQL Server estima que 1-3 linhas retornarão de uma variável de tabela (dependendo da sua versão do SQL Server, estimador de cardinalidade, sinalizadores de rastreamento etc.) Isso, por sua vez, fornece um plano de execução muito ruim porque o SQL Server subestima a quantidade de trabalho precisará de outras tabelas, quanta memória reservar, etc.
Aqui estão as duas maneiras mais populares de obter uma estimativa mais precisa:
Para me ver fazendo isso ao vivo, assista ao Watch Brent Tune Queries de 1 hora (disclaimer: sou eu, linkando para um vídeo meu) onde pego uma consulta do Stack Overflow que usa uma variável de tabela e a ajusto ao vivo na frente de um audiência no SQL Rally Noruega.
O planejador de consultas é mais eficiente com #temp. Em uma variável de tabela, ela considera apenas as primeiras linhas.
Sua variável de tabela (e #temp se você usar uma) provavelmente se beneficiaria de declarar uma chave primária.
Coloque uma chave em #AutoData e preencha apenas as linhas necessárias.
Classifique por chave à medida que adiciona linhas .
Eu suspeito que abaixo pode ser otimizado com um row_number()
Esta é uma tentativa como row_number()