Eu tenho uma consulta que é usada para preencher uma tabela agregada para fins de relatório. A consulta vem de outro desenvolvedor aqui da empresa em que trabalho, mas é meu trabalho fazê-la rodar rápido. Até agora, todas as minhas primeiras tentativas falharam. Eu tentei várias coisas com esta consulta e é aqui que estou no momento. Já cortei cerca de meia hora do tempo de carregamento, mas estou preso e pensando que provavelmente terei que refazer tudo. Espero que alguém aqui possa ver se estou perdendo alguma coisa e me dê algumas dicas sobre como corrigir essa consulta.
SELECT P.CompanyID,
P.CompanyName,
P.StoreID,
P.StoreName,
P.ReportDate,
Isnull((SELECT Sum(FT.GrossSales - Isnull(FP.PaymentAmount, 0)) AS PullNet
FROM FactSalesTransaction AS FT
LEFT JOIN (SELECT TransactionID,
DimStoreID,
DimBusinessDateID,
Sum(PaymentAmount) AS PaymentAmount
FROM FactSalesPayment
WHERE DimPaymentTypeID <> 2
AND ModStatusFlg <> 'D'
GROUP BY TransactionID,
DimStoreID,
DimBusinessDateID) AS FP
ON FP.TransactionID = FT.TransactionID
AND FP.DimStoreID = FT.DimStoreID
INNER JOIN DimCalendar AS C
ON FT.DimBusinessDateID = C.DimCalendarID
AND FP.DimBusinessDateID = C.DimCalendarID AND C.CalendarDate >= '12/4/2012'
WHERE FT.DimStoreID = P.DimStoreID
AND FT.DimBusinessDateID = P.DimBusinessDateID
AND FT.ModStatusFlg <> 'D'), 0) AS StoreCash,
SR.CashDeposit AS StoreResp,
SN.StoreNet,
P.DimEmployeeID AS EmpID,
P.EmpName,
P.RegisterID,
P.PullNumber,
Isnull((SELECT Sum(FT.GrossSales - Isnull(FP.PaymentAmount, 0)) AS PullNet
FROM FactSalesTransaction AS FT
LEFT JOIN (SELECT TransactionID,
DimStoreID,
DimBusinessDateID,
Sum(PaymentAmount) AS PaymentAmount
FROM FactSalesPayment
WHERE DimPaymentTypeID <> 2
AND ModStatusFlg <> 'D'
GROUP BY TransactionID,
DimStoreID,
DimBusinessDateID) AS FP
ON FP.TransactionID = FT.TransactionID
AND FP.DimStoreID = FT.DimStoreID
INNER JOIN DimCalendar AS C
ON FT.DimBusinessDateID = C.DimCalendarID
AND FP.DimBusinessDateID = C.DimCalendarID AND C.CalendarDate >= '12/4/2012'
WHERE FT.DimStoreID = P.DimStoreID
AND FT.DimRegisterID = P.DimRegisterID
AND FT.TransactionDateTime BETWEEN P.PullDrawerStartTime AND P.PullDrawerEndTime
AND FT.ModStatusFlg <> 'D'), 0) AS PullCash,
P.PullResp + Isnull((SELECT Sum(SkimAmount)
FROM FactSkims
WHERE DimStoreID = P.DimStoreID
AND DimRegisterID = P.DimRegisterID
AND SkimDateTime BETWEEN P.PullDrawerStartTime AND P.PullDrawerEndTime), 0) AS PullResp,
Isnull((SELECT Sum(NetSales) AS PullNet
FROM FactSalesTransaction AS FT
INNER JOIN DimCalendar AS C
ON FT.DimBusinessDateID = C.DimCalendarID AND C.CalendarDate >= '12/4/2012'
WHERE DimStoreID = P.DimStoreID
AND DimRegisterID = P.DimRegisterID
AND TransactionDateTime BETWEEN P.PullDrawerStartTime AND P.PullDrawerEndTime
AND ModStatusFlg <> 'D'), 0) AS PullNet
FROM (SELECT C.CompanyID,
C.CompanyName,
S.StoreID,
S.StoreName,
F.DimEmployeeID,
E.FirstName + ' ' + E.LastName AS EmpName,
CASE
WHEN F.PullDrawerStartTime <> '1900-01-01' THEN F.PullDrawerStartTime
ELSE Isnull(Cast((SELECT TOP 1 Dateadd(SECOND, 1, PullDrawerEndTime)
FROM FactPullDrawer
WHERE PullDrawerEndTime < F.PullDrawerEndTime
AND DimStoreID = F.DimStoreID
AND DimRegisterID = F.DimRegisterID
AND DimBusinessDateID = F.DimBusinessDateID
ORDER BY PullDrawerEndTime DESC) AS DATETIME), BD.CalendarDate + Isnull(Cast(Cast(ST.SiteSettingValue AS TIME) AS DATETIME), Cast('4:00:00 AM' AS DATETIME)))
END AS PullDrawerStartTime,
F.PullDrawerEndTime,
BD.CalendarDate AS ReportDate,
R.RegisterID,
R.DimRegisterID,
(SELECT Count(PullDrawerEndTime)
FROM FactPullDrawer
WHERE PullDrawerEndTime < F.PullDrawerEndTime
AND DimStoreID = F.DimStoreID
AND DimRegisterID = F.DimRegisterID
AND DimBusinessDateID = F.DimBusinessDateID) + 1 AS PullNumber,
Isnull(F.Amount, 0) AS PullResp,
F.DimStoreID,
F.DimBusinessDateID
FROM FactPullDrawer AS F
INNER JOIN DimCompany AS C
ON C.DimCompanyID = F.DimCompanyID
INNER JOIN DimStore AS S
ON S.DimStoreID = F.DimStoreID
INNER JOIN DimCalendar AS BD
ON BD.DimCalendarID = F.DimBusinessDateID
AND BD.CalendarDate >= '12/4/2012'
INNER JOIN DimEmployee AS E
ON F.DimEmployeeID = E.DimEmployeeID
INNER JOIN DimRegister AS R
ON R.DimRegisterID = F.DimRegisterID
LEFT JOIN DimSiteSettings AS ST
ON S.StoreID = ST.StoreID
AND C.CompanyID = ST.CompanyID
AND ST.SiteSettingFieldID = 1412) AS P
INNER JOIN (SELECT DimStoreID,
DimBusinessDateID,
Sum(NetSales) AS StoreNet
FROM FactSalesTransaction
WHERE ModStatusFlg <> 'D'
GROUP BY DimStoreID,
DimBusinessDateID) AS SN
ON SN.DimStoreID = P.DimStoreID
AND SN.DimBusinessDateID = P.DimBusinessDateID
INNER JOIN (SELECT CompanyID,
StoreID,
ReportDate,
Sum(ValTotal) AS CashDeposit
FROM AgtAccountingReport
WHERE ReportCatOrder = 7
AND ReportElementOrder < 100
AND ReportElementOrder NOT IN ( 7, 9, 10, 16,17, 18, 19, 20, 21 )
AND ReportDate >= '10/28/2012'
GROUP BY CompanyID,
StoreID,
ReportDate) AS SR
ON SR.CompanyID = P.CompanyID
AND SR.StoreID = P.StoreID
AND SR.ReportDate = P.ReportDate
Estou pensando em todos os aninhados SELECT
, e é por isso que pensei em começar do zero. Qualquer ajuda seria apreciada.
Ok, então foi isso que eu realmente fiz para fazer essa consulta que costumava ser executada em cerca de uma hora e meia em menos de um minuto. Primeiro eu fiz um pouco mais de escavação para ver exatamente o que isso estava fazendo. Existem algumas subseleções importantes na parte mais externa da consulta. Eles se parecem com isso.
Essas instruções select estão juntando duas das minhas maiores tabelas. FactSalesTransaction(105 milhões de registros) para FactSalesPayment(102 milhões de registros) e está fazendo isso em um select dentro de um select. O que significa essencialmente que, para cada linha retornada, está executando esta consulta. Bem, essa consulta geralmente é executada por cerca de sete dias de dados e, portanto, retorna cerca de 19.000 registros. Isso significa que as 3 sub-seleções para essas tabelas massivas precisam ser executadas 19.000 vezes. Bingo, acho que descobri onde estava minha perda de desempenho. Então mudei essas consultas para uma junção à esquerda. Nada complicado apenas fez com que eles tivessem que entrar apenas uma vez. A junção esquerda para substituir se parece com isso.
Como você pode ver, eu realmente não mudei muito no select em si, apenas o alterei para não ser executado 19.000 vezes. A próxima coisa que fiz foi alterar a consulta para um procedimento armazenado em que pega o intervalo de datas do usuário ou, neste caso, o processo ETL fornece (geralmente 7 dias atrás) Selecione em DimCalendar o DimCalendarID para esse dia para que a consulta esteja usando números inteiros de data e hora e, em geral, tem menos registros para ingressar. Fazendo a consulta final ficar assim.
After doing a test for the speed increase to see if it was worth the index space. I added two Non-Clustered indexes on ModStatusFlag and DimBusinessDateID that included the other columns requested by this query. On both FactSalesTransaction and FactSalesPayment. I could probably do a little more to clean this up and make it run faster however the performance gain would be minimal and there are much bigger fish to fry at the moment. Long story short be careful with your sub-select Statements.
Eu recomendaria fortemente tabelas e índices temporários. Eu recomendaria basicamente o seguinte.
Usar tabela temporária
A tabela temporária otimiza extremamente o desempenho da sua consulta. Use int no local onde você está usando a cláusula WHERE em uma tabela grande ou no local da subconsulta.
Criar índices
Esp nas colunas que você está usando. Fará as coisas rapidamente.
Executar subconsultas individuais
Descubra qual é relativamente mais lento
Participe de subconsultas e verifique o desempenho ao ingressar
Descubra qual JOIN cria o problema. Geralmente se resume a um JOIN que leva o tempo todo. Além disso, quando você usa AND com JOIN, isso pode demorar muito em tabelas maiores. Por exemplo, você pode precisar reescrever esta parte de sua consulta da seguinte maneira.
Depois de encontrar o JOIN que retarda seu desempenho
Use tabela temporária lá para otimizar. Tabelas temporárias funcionam melhor se você estiver usando, digamos, 3k registros em uma tabela de 100k.
Essas são apenas algumas dicas que podem te ajudar.