Para quem usa o Mysql, a consulta que vamos fazer é semelhante ao "WITH ROLLUP".
Caso ainda não conheça a função "SUM", veja o artigo "PostgreSql SUM - Soma "
Caso tenha interesse, faça o download ou veja o script deste artigo no github.
O script está disponível para execução através do SQL Fiddle e foi testado no PostgreSql 9.1.9.
1º Exemplo
Vamos calcular as despesas com fornecedor para cada segmento de uma empresa.
Iremos exibir o subtotal por segmento: "Papelaria e informática", "Marcenaria", "Serralheria" e "Limpeza e higiêne".
Também vamos exibir no final da consulta o total geral para todos os segmentos.
Neste exemplo, utilizaremos a tabela "tb_fornecedor". Veja a imagem da tabela a seguir:
Solução
Temos duas formas de fazer esta consulta, podemos utilizar subquery ou o comando WITH, vamos mostrar as duas formas, use a que for mais fácil para você.
Primeiro vamos mostrar a solução com subquery. Veja a sentença abaixo:
SELECT
segmento,
produto,
valor
FROM
(
SELECT segmento, produto, SUM(valor) valor
FROM tb_fornecedor
GROUP BY segmento, produto
UNION ALL
SELECT segmento, NULL AS produto, SUM(valor) valor
FROM tb_fornecedor
GROUP BY segmento
UNION ALL
SELECT NULL AS segmento, NULL AS produto, SUM(valor) AS valor
FROM tb_fornecedor
) CALCULO
ORDER BY segmento, produto;
Nesta consulta, criamos uma subquery chamada CALCULO, o conteúdo da subquery está entre parenteses. A subquery contém três comandos "SELECT" com a seguinte função:
- o primeiro "SELECT" agrupa a soma por segmento e produto;
- o segundo "SELECT" agrupa a soma por segmento;
- o terceiro "SELECT" calcula a soma de todos os produtos;
Observe também que os comandos "SELECT" da subquery foram intercalados com valores nulos "NULL", para que a quantidade de colunas fossem as mesmas em cada consulta.
Após a execução da sentença teremos o resultado exibido a seguir:
Agora vamos mostrar a solução utilizando o comando "WITH". Veja a sentença abaixo:
WITH CALCULO AS
(
SELECT segmento, produto, SUM(valor) valor
FROM tb_fornecedor
GROUP BY segmento, produto
UNION
SELECT segmento, NULL AS produto, SUM(valor) valor
FROM tb_fornecedor
GROUP BY segmento
UNION
SELECT NULL AS segmento, NULL AS produto, SUM(valor) AS valor
FROM tb_fornecedor
)
SELECT
segmento,
produto,
valor
FROM calculo
ORDER BY segmento, produto;
2º Exemplo
Para facilitarmos a interpretação do resultado da consulta vamos indicar o "Total Geral" e o "Subtotal" na consulta.
Solução
Primeiro vamos mostrar a solução com subquery. Veja a sentença abaixo:
SELECT
COALESCE(segmento, 'TOTAL GERAL') AS segmento,
CASE
WHEN(segmento IS NOT NULL AND produto IS NOT NULL) THEN produto
WHEN(segmento IS NOT NULL AND produto IS NULL) THEN 'SUBTOTAL' || ' - '|| segmento
END AS produto,
valor
FROM
(
SELECT segmento, produto, SUM(valor) valor
FROM tb_fornecedor
GROUP BY segmento, produto
UNION
SELECT segmento, NULL AS produto, SUM(valor) valor
FROM tb_fornecedor
GROUP BY segmento
UNION
SELECT NULL AS segmento, NULL AS produto, SUM(valor) AS valor
FROM tb_fornecedor
) CALCULO
ORDER BY
CASE
WHEN(segmento IS NOT NULL) THEN CAST(0 AS INTEGER)
ELSE CAST(1 AS INTEGER)
END
, segmento,
CASE
WHEN(produto IS NOT NULL) THEN CAST(0 AS INTEGER)
ELSE CAST(1 AS INTEGER)
END,
produto;
Após a execução da sentença teremos o resultado exibido a seguir:
Observação: perceba que no "ORDER BY" utilizamos duas vezes o "CASE WHEN":
WITH CALCULO AS
(
SELECT segmento, produto, SUM(valor) valor
FROM tb_fornecedor
GROUP BY segmento, produto
UNION
SELECT segmento, NULL AS produto, SUM(valor) valor
FROM tb_fornecedor
GROUP BY segmento
UNION
SELECT NULL AS segmento, NULL AS produto, SUM(valor) AS valor
FROM tb_fornecedor
)
SELECT
COALESCE(segmento, 'TOTAL GERAL') AS segmento,
CASE
WHEN(segmento IS NOT NULL AND produto IS NOT NULL) THEN produto
WHEN(segmento IS NOT NULL AND produto IS NULL) THEN 'SUBTOTAL' || ' - '|| segmento
END AS produto,
valor
FROM calculo
ORDER BY
CASE
WHEN(segmento IS NOT NULL) THEN CAST(0 AS INTEGER)
ELSE CAST(1 AS INTEGER)
END,
segmento,
CASE
WHEN(produto IS NOT NULL) THEN CAST(0 AS INTEGER)
ELSE CAST(1 AS INTEGER)
END,
produto;
Deixe o seu comentário ou sugestão.
Gostou? Siga no Google + ou Facebook.
Agora vamos mostrar a solução utilizando o comando "WITH". Veja a sentença abaixo:
WITH CALCULO AS
(
SELECT segmento, produto, SUM(valor) valor
FROM tb_fornecedor
GROUP BY segmento, produto
UNION
SELECT segmento, NULL AS produto, SUM(valor) valor
FROM tb_fornecedor
GROUP BY segmento
UNION
SELECT NULL AS segmento, NULL AS produto, SUM(valor) AS valor
FROM tb_fornecedor
)
SELECT
segmento,
produto,
valor
FROM calculo
ORDER BY segmento, produto;
2º Exemplo
Para facilitarmos a interpretação do resultado da consulta vamos indicar o "Total Geral" e o "Subtotal" na consulta.
Solução
- Total Geral: colocaremos a indicação de "Total Geral" na primeira coluna, onde o valor do registro da primeira coluna apresentar valor nulo. Para fazer isso, vamos utilizar o comando "COALESCE";
- Subtotal: colocaremos a indicação de "Subtotal" na segunda coluna, onde o valor do registro da primeira coluna for diferente de nulo e o valor da segunda coluna for nulo. Para fazer isso, utilizamos o comando "CASE WHEN";
Primeiro vamos mostrar a solução com subquery. Veja a sentença abaixo:
SELECT
COALESCE(segmento, 'TOTAL GERAL') AS segmento,
CASE
WHEN(segmento IS NOT NULL AND produto IS NOT NULL) THEN produto
WHEN(segmento IS NOT NULL AND produto IS NULL) THEN 'SUBTOTAL' || ' - '|| segmento
END AS produto,
valor
FROM
(
SELECT segmento, produto, SUM(valor) valor
FROM tb_fornecedor
GROUP BY segmento, produto
UNION
SELECT segmento, NULL AS produto, SUM(valor) valor
FROM tb_fornecedor
GROUP BY segmento
UNION
SELECT NULL AS segmento, NULL AS produto, SUM(valor) AS valor
FROM tb_fornecedor
) CALCULO
ORDER BY
CASE
WHEN(segmento IS NOT NULL) THEN CAST(0 AS INTEGER)
ELSE CAST(1 AS INTEGER)
END
, segmento,
CASE
WHEN(produto IS NOT NULL) THEN CAST(0 AS INTEGER)
ELSE CAST(1 AS INTEGER)
END,
produto;
Após a execução da sentença teremos o resultado exibido a seguir:
Observação: perceba que no "ORDER BY" utilizamos duas vezes o "CASE WHEN":
- o primeiro para que o "TOTAL GERAL" fosse exibido no último registo da coluna "segmento" independente da ordem alfabética;
- o segundo para que o "Subtotal" fosse exibido no final de cada segmento da coluna "produto" independente da ordem alfabética;
WITH CALCULO AS
(
SELECT segmento, produto, SUM(valor) valor
FROM tb_fornecedor
GROUP BY segmento, produto
UNION
SELECT segmento, NULL AS produto, SUM(valor) valor
FROM tb_fornecedor
GROUP BY segmento
UNION
SELECT NULL AS segmento, NULL AS produto, SUM(valor) AS valor
FROM tb_fornecedor
)
SELECT
COALESCE(segmento, 'TOTAL GERAL') AS segmento,
CASE
WHEN(segmento IS NOT NULL AND produto IS NOT NULL) THEN produto
WHEN(segmento IS NOT NULL AND produto IS NULL) THEN 'SUBTOTAL' || ' - '|| segmento
END AS produto,
valor
FROM calculo
ORDER BY
CASE
WHEN(segmento IS NOT NULL) THEN CAST(0 AS INTEGER)
ELSE CAST(1 AS INTEGER)
END,
segmento,
CASE
WHEN(produto IS NOT NULL) THEN CAST(0 AS INTEGER)
ELSE CAST(1 AS INTEGER)
END,
produto;
Gostou? Siga no Google + ou Facebook.
Ótimas dicas. Só conhecia a solução com UNION ALL. Parabéns por disseminar seu conhecimento.
ResponderExcluirFico feliz por ter gostado. Grata pelo comentário.
ExcluirMuitíssimo obrigado de verdade. Tópico super esclarecedor, já usei o union all mas não sabia a maneira de colocar organizado. Estava com um problema aqui sem resolver há alguns meses.
ResponderExcluir