Tenho a seguinte consulta e, devido a muitas SUM
chamadas de função, minha consulta está muito lenta. Tenho muitos registros no meu banco de dados e gostaria de obter um relatório do ano atual e do ano passado (Últimos 30 dias, Últimos 90 dias e últimos 365 dias) para cada um:
SELECT
b.id as [ID]
,d.[Title] as [Title]
,e.Class as [Class]
,Sum(CASE WHEN a.DateCol >= DATEADD(MONTH,-1,GETDATE()) THEN a.col1 ELSE 0 END) as [Current - Last 30 Days Col1]
,Sum(CASE WHEN a.DateCol >= DATEADD(MONTH,-1,GETDATE()) THEN a.col2 ELSE 0 END) as [Current - Last 30 Days Col2]
,Sum(CASE WHEN a.DateCol >= DATEADD(QUARTER,-1,GETDATE()) THEN a.col1 ELSE 0 END) as [Current - Last 90 Days Col1]
,Sum(CASE WHEN a.DateCol >= DATEADD(QUARTER,-1,GETDATE()) THEN a.col2 ELSE 0 END) as [Current - Last 90 Days Col2]
,Sum(CASE WHEN a.DateCol >= DATEADD(YEAR,-1,GETDATE()) THEN a.col1 ELSE 0 END) as [Current - Last 365 Days Col1]
,Sum(CASE WHEN a.DateCol >= DATEADD(YEAR,-1,GETDATE()) THEN a.col2 ELSE 0 END) as [Current - Last 365 Days Col2]
,Sum(CASE WHEN a.DateCol >= DATEADD(MONTH,-13,GETDATE()) and a.DateCol <= DATEADD(MONTH,-12,GETDATE()) THEN a.col1 ELSE 0 END) as [Last year - Last 30 Days Col1]
,Sum(CASE WHEN a.DateCol >= DATEADD(MONTH,-13,GETDATE()) and a.DateCol <= DATEADD(MONTH,-12,GETDATE()) THEN a.col2 ELSE 0 END) as [Last year - Last 30 Days Col2]
,Sum(CASE WHEN a.DateCol >= DATEADD(QUARTER,-5,GETDATE()) and a.DateCol <= DATEADD(QUARTER,-4,GETDATE()) THEN a.col1 ELSE 0 END) as [Last year - Last 90 Days Col1]
,Sum(CASE WHEN a.DateCol >= DATEADD(QUARTER,-5,GETDATE()) and a.DateCol <= DATEADD(QUARTER,-4,GETDATE()) THEN a.col2 ELSE 0 END) as [Last year - Last 90 Days Col2]
,Sum(CASE WHEN a.DateCol >= DATEADD(YEAR,-2,GETDATE()) and a.DateCol <= DATEADD(YEAR,-1,GETDATE()) THEN a.col1 ELSE 0 END) as [Last year - Last 365 Days Col1]
,Sum(CASE WHEN a.DateCol >= DATEADD(YEAR,-2,GETDATE()) and a.DateCol <= DATEADD(YEAR,-1,GETDATE()) THEN a.col2 ELSE 0 END) as [Last year - Last 365 Days Col2]
FROM
tb1 a
INNER JOIN
tb2 b on a.id=b.fid and a.col3 = b.col4
INNER JOIN
tb3 c on b.fid = c.col5
INNER JOIN
tb4 d on c.id = d.col6
INNER JOIN
tb5 e on c.col7 = e.id
GROUP BY
b.id, d.Title, e.Class
Alguém tem alguma idéia de como posso melhorar minha consulta para executar mais rapidamente?
EDIT: fui encorajado a mover a DATEADD
chamada de função para a where
instrução e carregar os dois primeiros anos primeiro e depois filtrá-los em colunas, mas não tenho certeza de que a resposta sugerida seja executada e funcione, ela pode ser encontrada aqui: https: // stackoverflow. com / a / 59944426/12536284
Se você concorda com a solução acima, mostre-me como posso aplicá-la na minha consulta atual?
Apenas para sua informação, estou usando esse SP em C #, Entity Framework (DB-First), algo como isto:
var result = MyDBEntities.CalculatorSP();
Execution Plan
. Por favor, publique-oRespostas:
Como já foi mencionado, o plano de execução será realmente útil neste caso. Com base no que você mostrou, parece que extraiu 12 colunas do total de 15 colunas
tb1 (a)
, para que você possa tentar executar sua consulta sem nenhuma junção e apenas contra otb1
para ver se sua consulta está funcionando conforme o esperado. Como não vejo nada de errado com suas chamadas de função SUM, meu melhor palpite é que você tem um problema com suas junções, sugiro que você faça o seguinte. Você pode começar por excluindo o último juntar-se, por exemplo,INNER JOIN tb5 e on c.col7 = e.id
e qualquer uso relacionado nisso comoe.Class as [Class]
ee.Class
no seu grupo por declaração. Não vamos excluí-lo completamente, este é apenas um teste para garantir se o problema está relacionado com isso ou não, se sua consulta for melhor e conforme o esperado, você pode tentar usar uma tabela temporária como solução alternativa em vez da última associação , algo assim:Na verdade, tabelas temporárias são tabelas que existem temporariamente no SQL Server. As tabelas temporárias são úteis para armazenar os conjuntos de resultados imediatos que são acessados várias vezes. Você pode ler mais sobre isso aqui https://www.sqlservertutorial.net/sql-server-basics/sql-server-temporary-tables/ E aqui https://codingsight.com/introduction-to-temporary-tables-in -servidor SQL/
Também recomendo vivamente que, se você estiver usando o Stored Procedure, defina o
NOCOUNT
comoON
, ele também pode fornecer um aumento significativo no desempenho, porque o tráfego de rede é bastante reduzido:Baseado em nisso :
fonte
tb5
da#Temp
tabela e ingressar na tabela temporária funciona mais rápido do que entrartb5
diretamente? seguramente eles contêm os mesmos dados (e#Temp
pode estar faltando um índice se ele existissetb5
). Realmente não consigo entender por que isso é mais eficiente (pelo que sei, deve ser menos eficiente copiar todos os dados e ingressar).tb5
estiver localizado em outro servidor? Nesse caso, o uso de uma tabela temporária é definitivamente mais rápido que a associação direta a outro servidor. Essa foi apenas uma sugestão para testar e verificar se alguma coisa foi alterada. Tive uma situação semelhante no passado e, felizmente, parece que a tabela temporária também ajudou o OP nesse caso.A melhor abordagem é inserir em uma variável de tabela / tabela de hash (se a contagem de linhas for pequena, use uma variável de tabela ou use uma tabela de hash se a contagem de linhas for muito grande). Atualize a agregação e, finalmente, selecione na variável da tabela ou na tabela de hash. É necessário analisar o plano de consulta.
fonte
Presumo que TB1 seja uma tabela grande (relativa a TB2, TB3, TB4 e TB5).
Nesse caso, faz sentido restringir a seleção dessa tabela (com uma cláusula WHERE).
Se apenas uma pequena parte de tb1 for usada, por exemplo, porque as junções com tb2, tb3, tb4 e tb5 reduzem as linhas necessárias para apenas alguns por cento, verifique se as tabelas estão indexadas nas colunas usadas nas junções .
Se uma grande parte de tb1 for usada, pode fazer sentido agrupar seus resultados antes de associá-los a tb2, tb3, tb4 e tb5. Abaixo está um exemplo disso.
fonte
Basta usar colunas computadas
Exemplo
Especificar colunas computadas em uma tabela
fonte
Para otimizar tais cálculos, considere pré-calcular alguns dos valores. A idéia dos pré-cálculos é reduzir o número de linhas que precisam ser lidas ou prosseguidas.
Uma maneira de conseguir isso é usar uma exibição indexada e deixar o mecanismo para fazer os cálculos sozinho. Como esse tipo de visualização tem algumas limitações, você cria uma tabela simples e executa os cálculos. Basicamente, isso depende das necessidades do negócio.
Assim, no exemplo abaixo estou criando uma tabela com
RowID
eRowDatetime
colunas e inserir 1 milhão de linhas. Eu estou usando uma exibição indexada para contar as entidades por dias, então, em vez de consultar 1 milhão de linhas por ano, consultarei 365 linhas por ano para contar essas métricas.O sucesso dessa solução depende muito de como os dados são distribuídos e de quantas linhas você possui. Por exemplo, se você tiver uma entrada por dia para cada dia do ano, a visualização e a tabela terão a mesma correspondência de linhas, portanto, as operações de E / S não serão reduzidas.
Além disso, o acima é apenas um exemplo de materialização e leitura dos dados. No seu caso, pode ser necessário adicionar mais colunas à definição da visualização.
fonte
Eu usaria uma tabela "Datas" da tabela de pesquisa para associar meus dados a um índice em DatesId. Uso as datas como um filtro quando quero procurar dados históricos. A junção é rápida e, portanto, a filtragem como DatesId é o índice primário em cluster (chave primária). Adicione a coluna de data (como coluna incluída) para sua tabela de dados também.
A tabela de datas possui as seguintes colunas:
DatasId, Data, Ano, Trimestre, AnoQuarter, MonthNum, MonthNameShort, YearWeek, WeekNum, DayOfYear, DayOfMonth, DayNumOfWeek, DayName
Dados de exemplo: 20310409 2031-04-09 2031 2 2031-Q2 4 de abril de 2031_15 15 99 9 3 quarta-feira
Você pode me enviar uma MP se quiser um csv disso para poder importá-lo para o banco de dados, mas tenho certeza de que pode encontrar facilmente algo assim on-line e criar o seu.
Também adiciono uma coluna de identidade para que você possa obter um número inteiro para cada data. Isso facilita um pouco o trabalho, mas não é um requisito.
Isso me permite voltar facilmente a um determinado período. É muito fácil criar seus próprios pontos de vista sobre isso. Obviamente, você pode usar a função ROW_NUMBER () para fazer isso por anos, semanas etc. também.
Depois de obter a data pretendida, associo-me aos dados. Funciona muito rápido!
fonte
Como você está sempre agrupando valores com base em um número inteiro de meses, eu primeiro agruparia por mês em uma subconsulta na cláusula from. Isso é semelhante ao uso de uma tabela temporária. Não tenho certeza se isso realmente aceleraria sua consulta.
fonte
Para melhorar a velocidade da consulta SQL, você deve adicionar índices. Para cada tabela ingressada, você precisa adicionar um índice.
Como este exemplo de código para o oracle:
fonte