Fala galera, no dia 06/07/2012 fui palestrar no TDC2012 em São Paulo na trilha de Banco de Dados, e, assistindo a apresentação do Wagner Crivelini (Artigos SQL Server Central | Artigos DevMedia | Twitter) sobre Geração de Relatórios com Linguagem SQL, baseado no padrão ISO, percebi que algumas pessoas não conheciam as instruções de CUBE ou ROLLUP que ele apresentou. Mesmo com a boa explicacão do Wagner, e seus exemplos práticos, o pessoal parecia ter dúvidas.
Por esse motivo, decidi escrever esse post explicando estes recursos dentro do SQL Server e mostrando códigos T-SQL.
Um ponto importante sobre o CUBE e o ROLLUP é que são aplicados ao SQL Server exclusivamente com a cláusula GROUP BY, como acontece com o HAVING.
Group By
Esta função é conhecida por todos (assim espero) e não vou “chover no molhado” explicando seu funcionamento. O que vou fazer aqui é colocar o código T-SQL que usei e o resultado obtido, para servir de comparação com os outros códigos que, aí sim, é o objetivo do artigo.
Este código T-SQL será base para os exemplos e faz parte da tabela PurchaseOrderDetail do database de exemplo AdventureWorks2012. Este código representa o somatório de cada pedido, agrupado pelo mês e ano.
1: -- 34 registros
2: select sum(LineTotal) [Total], month(DueDate) [Mes], year(DueDate) [Ano]
3: from [Purchasing].[PurchaseOrderDetail]
4: where year(DueDate) >= 2005
5: group by month(DueDate), year(DueDate)
6: order by [Ano],[Mes]
Sendo o resultado:
Total | Mes | Ano |
9491,518 | 5 | 2005 |
94404,3 | 6 | 2005 |
299240 | 1 | 2006 |
50781,36 | 2 | 2006 |
927072,2 | 3 | 2006 |
649928,9 | 4 | 2006 |
346917,6 | 5 | 2006 |
106491 | 6 | 2006 |
171578,4 | 7 | 2006 |
483826,6 | 8 | 2006 |
249488,5 | 9 | 2006 |
375543,6 | 10 | 2006 |
281325,4 | 11 | 2006 |
283939,4 | 12 | 2006 |
131485,8 | 3 | 2007 |
123167,8 | 5 | 2007 |
749096,6 | 6 | 2007 |
719047,5 | 7 | 2007 |
117332 | 8 | 2007 |
2756843 | 9 | 2007 |
4956873 | 10 | 2007 |
2211744 | 11 | 2007 |
1925624 | 12 | 2007 |
4066161 | 1 | 2008 |
4031461 | 2 | 2008 |
4739842 | 3 | 2008 |
4829902 | 4 | 2008 |
5373580 | 5 | 2008 |
5459623 | 6 | 2008 |
5790441 | 7 | 2008 |
7400999 | 8 | 2008 |
4077396 | 9 | 2008 |
327,1575 | 10 | 2008 |
1020 | 11 | 2008 |
Função Rollup
Esta função gera resultados de subtotal baseado nos grupos das colunas definidos no Group By, resultando mais algumas linhas ao resultset. Estas linhas extras são os subtotais que foram calculados, sendo uma linha para cada subtotal e uma linha para o total final (somatório de todos os subtotais). Neste exemplo de código, o Group By convencional retornou 34 registros. Já com o Rollup o retorno foi de 47. Isso ocorre, porque o calculo foi baseado na coluna Mes que adicionou 12 subtotais ao resultset + 1 coluna calculando o total de todos os subtotais calculados.
1: -- 47 registros
2: select sum(LineTotal) [Total], month(DueDate) [Mes], year(DueDate) [Ano]
3: from [Purchasing].[PurchaseOrderDetail]
4: where year(DueDate) >= 2005
5: group by rollup (month(DueDate), year(DueDate))
O resultado é parecido com o do Group By, porém possui valores NULL inseridos em determinadas linhas. Esses valores NULL indicam que naquela linha do resultado existe um subtotal calculado, e um onde pode-se encontrar dois valores NULL na linha, significa que é o somatório do resultado de todos os subtotais.
Repare que onde existe o valor NULL na coluna Ano, a coluna Mes está preenchida com o valor do mês, isso significa que o resultado da coluna Total é o somatório de todos os meses referentes ao valor da coluna Mes, independente do ano.
Total | Mes | Ano |
299240 | 1 | 2006 |
4066161 | 1 | 2008 |
4365401 | 1 | NULL |
50781,36 | 2 | 2006 |
4031461 | 2 | 2008 |
4082243 | 2 | NULL |
927072,2 | 3 | 2006 |
131485,8 | 3 | 2007 |
4739842 | 3 | 2008 |
5798400 | 3 | NULL |
649928,9 | 4 | 2006 |
4829902 | 4 | 2008 |
5479831 | 4 | NULL |
9491,518 | 5 | 2005 |
346917,6 | 5 | 2006 |
123167,8 | 5 | 2007 |
5373580 | 5 | 2008 |
5853157 | 5 | NULL |
94404,3 | 6 | 2005 |
106491 | 6 | 2006 |
749096,6 | 6 | 2007 |
5459623 | 6 | 2008 |
6409615 | 6 | NULL |
171578,4 | 7 | 2006 |
719047,5 | 7 | 2007 |
5790441 | 7 | 2008 |
6681067 | 7 | NULL |
483826,6 | 8 | 2006 |
117332 | 8 | 2007 |
7400999 | 8 | 2008 |
8002157 | 8 | NULL |
249488,5 | 9 | 2006 |
2756843 | 9 | 2007 |
4077396 | 9 | 2008 |
7083727 | 9 | NULL |
375543,6 | 10 | 2006 |
4956873 | 10 | 2007 |
327,1575 | 10 | 2008 |
5332744 | 10 | NULL |
281325,4 | 11 | 2006 |
2211744 | 11 | 2007 |
1020 | 11 | 2008 |
2494090 | 11 | NULL |
283939,4 | 12 | 2006 |
1925624 | 12 | 2007 |
2209563 | 12 | NULL |
63791995 | NULL | NULL |
Função Cube
Esta função gera ainda mais algumas linhas de subtotais que a Rollup, pois ela calcula todos os subtotais possíveis para o agrupamento, e não somente o subtotal de uma coluna. No caso do Rollup acima, foi calculado o subtotal dos meses, já no Cube foi calculado o subtotal dos meses e também o subtotal dos anos. Totalizando 51 registros (34 registros do Group By + 12 registros dos subtotais de meses + 4 subtotais de anos + 1 subtotal de TUDO = 51 registros)
1: -- 51 registros
2: select sum(LineTotal) [Total], month(DueDate) [Mes], year(DueDate) [Ano]
3: from [Purchasing].[PurchaseOrderDetail]
4: where year(DueDate) >= 2005
5: group by cube (month(DueDate), year(DueDate))
O resultado é parecido com o do Rollup porém com mais subtotais, calculando não somente os subtotais dos meses como também os subtotais dos anos, e também o somatório de todos os subtotais. Na mesma explicação que temos no Rollup, quando existem valores NULL na coluna Mes significa que o valor existente na coluna Total é relativo ao ano representado na coluna Ano. Mesma situação continua se aplicado às colunas Mes, quando se encontra valor NULL na coluna Ano.
Total | Mes | Ano |
9491,518 | 5 | 2005 |
94404,3 | 6 | 2005 |
103895,8 | NULL | 2005 |
299240 | 1 | 2006 |
50781,36 | 2 | 2006 |
927072,2 | 3 | 2006 |
649928,9 | 4 | 2006 |
346917,6 | 5 | 2006 |
106491 | 6 | 2006 |
171578,4 | 7 | 2006 |
483826,6 | 8 | 2006 |
249488,5 | 9 | 2006 |
375543,6 | 10 | 2006 |
281325,4 | 11 | 2006 |
283939,4 | 12 | 2006 |
4226133 | NULL | 2006 |
131485,8 | 3 | 2007 |
123167,8 | 5 | 2007 |
749096,6 | 6 | 2007 |
719047,5 | 7 | 2007 |
117332 | 8 | 2007 |
2756843 | 9 | 2007 |
4956873 | 10 | 2007 |
2211744 | 11 | 2007 |
1925624 | 12 | 2007 |
13691214 | NULL | 2007 |
4066161 | 1 | 2008 |
4031461 | 2 | 2008 |
4739842 | 3 | 2008 |
4829902 | 4 | 2008 |
5373580 | 5 | 2008 |
5459623 | 6 | 2008 |
5790441 | 7 | 2008 |
7400999 | 8 | 2008 |
4077396 | 9 | 2008 |
327,1575 | 10 | 2008 |
1020 | 11 | 2008 |
45770752 | NULL | 2008 |
63791995 | NULL | NULL |
4365401 | 1 | NULL |
4082243 | 2 | NULL |
5798400 | 3 | NULL |
5479831 | 4 | NULL |
5853157 | 5 | NULL |
6409615 | 6 | NULL |
6681067 | 7 | NULL |
8002157 | 8 | NULL |
7083727 | 9 | NULL |
5332744 | 10 | NULL |
2494090 | 11 | NULL |
2209563 | 12 | NULL |
Repare que o crescimento de linhas no resultset é exponencial, baseado na quantidade de agrupadores que são gerados pelo Group By. Isso significa que você precisa se preocupar com os dados, pois o processamento destes subtotais cruzados (como acontece na função Cube) pode impactar negativamente a performance da sua consulta, uma vez que o SQL Server deverá cruzar os somatórios de todas as possibilidades existentes em todas as colunas.