Database
 sql >> Base de Dados >  >> RDS >> Database

SQL GROUP BY- 3 dicas fáceis para agrupar resultados como um profissional


O agrupamento é um recurso importante que ajuda a organizar e organizar os dados. Há muitas maneiras de fazer isso, e um dos métodos mais eficazes é a cláusula SQL GROUP BY.

Você pode usar SQL GROUP BY para dividir linhas em resultados em grupos com uma função de agregação . Parece fácil somar, calcular a média ou contar registros com ele.

Mas você está fazendo certo?

“Certo” pode ser subjetivo. Quando é executado sem erros críticos com uma saída correta, é considerado bom. No entanto, também precisa ser rápido.

Neste artigo, a velocidade também será considerada. Você verá muita análise de consultas usando leituras lógicas e planos de execução em todos os pontos.

Vamos começar.

1. Filtrar antecipadamente


Se você está confuso sobre quando usar WHERE e HAVING, este é para você. Porque dependendo da condição que você fornecer, ambos podem dar o mesmo resultado.

Mas são diferentes.

HAVING filtra os grupos usando as colunas na cláusula SQL GROUP BY. WHERE filtra as linhas antes que ocorram agrupamentos e agregações. Portanto, se você filtrar usando a cláusula HAVING, o agrupamento ocorrerá para todos linhas retornadas.

E isso é ruim.

Por quê? A resposta curta é:é lento. Vamos provar isso com 2 consultas. Confira o código abaixo. Antes de executá-lo no SQL Server Management Studio, pressione Ctrl-M primeiro.
SET STATISTICS IO ON
GO

-- using WHERE
SELECT
 MONTH(soh.OrderDate) AS OrderMonth
,YEAR(soh.OrderDate) AS OrderYear
,p.Name AS Product
,SUM(sod.LineTotal) AS ProductSales
FROM Sales.SalesOrderHeader soh
INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
INNER join Production.Product p ON sod.ProductID = p.ProductID
WHERE soh.OrderDate BETWEEN '01/01/2012' AND '12/31/2012'
GROUP BY p.Name, YEAR(soh.OrderDate), MONTH(soh.OrderDate)
ORDER BY Product, OrderYear, OrderMonth;

-- using HAVING
SELECT
 MONTH(soh.OrderDate) AS OrderMonth
,YEAR(soh.OrderDate) AS OrderYear
,p.Name AS Product
,SUM(sod.LineTotal) AS ProductSales
FROM Sales.SalesOrderHeader soh
INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
INNER join Production.Product p ON sod.ProductID = p.ProductID
GROUP BY p.Name, YEAR(soh.OrderDate), MONTH(soh.OrderDate)
HAVING YEAR(soh.OrderDate) = 2012 
ORDER BY Product, OrderYear, OrderMonth;

SET STATISTICS IO OFF
GO

Análise


As 2 instruções SELECT acima retornarão as mesmas linhas. Ambos estão corretos em devolver pedidos de produtos por mês no ano de 2012. Mas o primeiro SELECT demorou 136ms. para rodar no meu laptop, enquanto outro levou 764ms.!

Por quê?

Vamos verificar primeiro as leituras lógicas na Figura 1. O STATISTICS IO retornou esses resultados. Em seguida, colei-o em StatisticsParser.com para obter a saída formatada.

Figura 1 . Leituras lógicas de filtragem antecipada usando WHERE vs. filtragem tardia usando HAVING.

Veja o total de leituras lógicas de cada um. Para entender esses números, quanto mais leituras lógicas forem necessárias, mais lenta será a consulta. Então, isso prova que usar HAVING é mais lento, e filtrar antecipadamente com WHERE é mais rápido.

Claro, isso não significa que TER seja inútil. Uma exceção é ao usar HAVING com um agregado como HAVING SUM(sod.Linetotal)> 100000 . Você pode combinar uma cláusula WHERE e uma cláusula HAVING em uma consulta.

Veja o plano de execução na Figura 2.

Figura 2 . Planos de execução de filtragem antecipada versus filtragem tardia.

Ambos os planos de execução pareciam semelhantes, exceto os marcados em vermelho. A filtragem inicial usava o operador Index Seek enquanto outro usava Index Scan. As buscas são mais rápidas do que as varreduras em tabelas grandes.

Não te: Filtrar cedo tem menos custo do que filtrar tarde. Portanto, o resultado final é filtrar as linhas antecipadamente para melhorar o desempenho.

2. Agrupe primeiro, junte-se depois


Unir algumas das tabelas necessárias posteriormente também pode melhorar o desempenho.

Digamos que você queira ter vendas mensais de produtos. Você também precisa obter o nome, o número e a subcategoria do produto na mesma consulta. Essas colunas estão em outra tabela. E todos eles precisam ser adicionados na cláusula GROUP BY para ter uma execução bem sucedida. Aqui está o código.
SET STATISTICS IO ON
GO

SELECT
 p.Name AS Product
,p.ProductNumber
,ps.Name AS ProductSubcategory
,SUM(sod.LineTotal) AS ProductSales
FROM Sales.SalesOrderHeader soh
INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
INNER JOIN Production.Product p ON sod.ProductID = p.ProductID
INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID
WHERE soh.OrderDate BETWEEN '01/01/2012' AND '12/31/2012'
GROUP BY p.name, p.ProductNumber, ps.Name
ORDER BY Product

SET STATISTICS IO OFF
GO


Isso vai correr bem. Mas há uma maneira melhor e mais rápida. Isso não exigirá que você adicione as 3 colunas para nome do produto, número e subcategoria na cláusula GROUP BY. No entanto, isso exigirá um pouco mais de pressionamentos de tecla. Aqui está.
SET STATISTICS IO ON
GO

;WITH Orders2012 AS 
(
SELECT
 sod.ProductID
,SUM(sod.LineTotal) AS ProductSales
FROM Sales.SalesOrderHeader soh
INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
WHERE soh.OrderDate BETWEEN '01/01/2012' AND '12/31/2012'
GROUP BY sod.ProductID
)
SELECT
 P.Name AS Product
,P.ProductNumber
,ps.Name AS ProductSubcategory
,o.ProductSales
FROM Orders2012 o
INNER JOIN Production.Product p ON o.ProductID = p.ProductID
INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID
ORDER BY Product;

SET STATISTICS IO OFF
GO

Análise


Por que isso é mais rápido? As junções ao Produto e ProductSubcategory são feitos posteriormente. Ambos não estão envolvidos na cláusula GROUP BY. Vamos provar isso por números no STATISTICS IO. Consulte a Figura 4.

Figura 3 . A união antecipada e o agrupamento consumiam mais leituras lógicas do que as uniões posteriores.

Veja essas leituras lógicas? A diferença é grande, e o vencedor é óbvio.

Vamos comparar o plano de execução das 2 consultas para ver o motivo dos números acima. Primeiro, veja a Figura 4 para ver o plano de execução da consulta com todas as tabelas unidas quando agrupadas.

Figura 4 . Plano de execução quando todas as tabelas são unidas.

E temos as seguintes observações:
  • GROUP BY e SUM foram feitos no final do processo após a junção de todas as tabelas.
  • Muitas linhas e setas mais grossas – isso explica as 1.277 leituras lógicas.
  • As 2 consultas combinadas formam 100% do custo da consulta. Mas o plano desta consulta tem um custo de consulta mais alto (56%).

Agora, aqui está um plano de execução quando agrupamos primeiro e nos juntamos ao Produto e ProductSubcategory mesas mais tarde. Confira a Figura 5.

Figura 5 . Plano de execução quando o grupo primeiro, ingressar depois é concluído.

E temos as seguintes observações na Figura 5.
  • GROUP BY e SUM terminaram mais cedo.
  • Menor número de linhas grossas e setas – isso explica apenas as 348 leituras lógicas.
  • Menor custo de consulta (44%).

3. Agrupar uma coluna indexada


Sempre que SQL GROUP BY é feito em uma coluna, essa coluna deve ter um índice. Você aumentará a velocidade de execução assim que agrupar a coluna com um índice. Vamos modificar a consulta anterior e usar a data de envio em vez da data do pedido. A coluna de data de envio não tem índice em SalesOrderHeader .
SET STATISTICS IO ON
GO

SELECT
 MONTH(soh.ShipDate) AS ShipMonth
,YEAR(soh.ShipDate) AS ShipYear
,p.Name AS Product
,SUM(sod.LineTotal) AS ProductSales
FROM Sales.SalesOrderHeader soh
INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
INNER join Production.Product p ON sod.ProductID = p.ProductID
WHERE soh.ShipDate BETWEEN '01/01/2012' AND '12/31/2012'
GROUP BY p.Name, YEAR(soh.ShipDate), MONTH(soh.ShipDate)
ORDER BY Product, ShipYear, ShipMonth;

SET STATISTICS IO OFF
GO

Pressione Ctrl-M e execute a consulta acima no SSMS. Em seguida, crie um índice não clusterizado em ShipDate coluna. Observe as leituras lógicas e o plano de execução. Por fim, execute novamente a consulta acima em outra guia de consulta. Observe as diferenças nas leituras lógicas e nos planos de execução.

Aqui está a comparação das leituras lógicas na Figura 6.

Figura 6 . Leituras lógicas do nosso exemplo de consulta com e sem índice em ShipDate.

Na Figura 6, há leituras lógicas mais altas da consulta sem um índice em ShipDate .

Agora vamos ter o plano de execução quando não houver índice em ShipDate existe na Figura 7.

Figura 7 . Plano de execução ao usar GROUP BY em ShipDate não indexado.

A Verificação de Índice O operador usado no plano da Figura 7 explica as leituras lógicas mais altas (475). Aqui está um plano de execução após indexar o ShipDate coluna.

Figura 8 . Plano de execução ao usar GROUP BY em ShipDate indexado.

Em vez de Index Scan, um Index Seek é usado após indexar o ShipDate coluna. Isso explica as leituras lógicas inferiores na Figura 6.

Portanto, para melhorar o desempenho ao usar GROUP BY, considere indexar as colunas que você usou para agrupar.

Apreciações no uso do SQL GROUP BY


SQL GROUP BY é fácil de usar. Mas você precisa dar o próximo passo para ir além de resumir os dados para relatórios. Aqui estão os pontos novamente:
  • Filtrar com antecedência . Remova as linhas que você não precisa resumir usando a cláusula WHERE em vez da cláusula HAVING.
  • Agrupe primeiro, participe depois . Às vezes, haverá colunas que você precisa adicionar além das colunas que você está agrupando. Em vez de incluí-los na cláusula GROUP BY, divida a consulta com um CTE e junte outras tabelas posteriormente.
  • Use GROUP BY com colunas indexadas . Essa coisa básica pode ser útil quando o banco de dados é tão rápido quanto um caracol.

Espero que isso ajude você a melhorar seu jogo nos resultados de agrupamento.

Se você gostou deste post, compartilhe-o em suas redes sociais favoritas.