Técnicas Avançadas de Formatação e Estruturação de Relatórios em SQL

  1. Transposição de Registros para Colunas Únicas

A transformação de linhas dispersas em colunas consolidadas é um requisito comum ao gerar dashboards e relatórios resumidos. Em vez de exibir uma lista vertical de setores com suas respectivas contagens, o objetivo é apresentar todas as métricas em uma única linha.

Cenário: Deseja-se obter o total de colaboradores por departamento (IDs 10, 20 e 30) em formato horizontal.

Solução: Aplica-se agregação condicional utilizando a cláusula CASE combinada com funções de soma. Cada coluna deriva de um filtro interno que retorna 1 quando a condição coincide e 0 caso contrário.

SELECT 
    SUM(CASE WHEN departamento_id = 10 THEN 1 ELSE 0 END) AS qtde_depart_10,
    SUM(CASE WHEN departamento_id = 20 THEN 1 ELSE 0 END) AS qtde_depart_20,
    SUM(CASE WHEN departamento_id = 30 THEN 1 ELSE 0 END) AS qtde_depart_30
FROM folha_pagamento.funcionarios;
  1. Distribuição Vertical de Dados (Pivot Multiplo)

Diferente do método anterior, esta técnica visa distribuir os valores em múltiplas linhas, mantendo cada categoria como uma coluna dedicada. Isso exige um índice sequencial para alinhar corretamente os registros após a rotação.

Cenário: Exibir nomes dos colaboradores organizados por cargo, onde cada ocupação ocupa uma coluna fixa e as linhas são preenchidas sequencialmente.

Solução: Utiliza-se a função de janela ROW_NUMBER() particionada pelo cargo para gerar um identificador único por sequência. Após a aplicação do CASE, realiza-se o agrupamento por esse índice usando MAX() para consolidar os nomes sem duplicatas.

SELECT 
    MAX(CASE WHEN cargo_funcao = 'Analista' THEN nome_colaborador ELSE '' END) AS col_analista,
    MAX(CASE WHEN cargo_funcao = 'Gerente' THEN nome_colaborador ELSE '' END) AS col_gerente,
    MAX(CASE WHEN cargo_funcao = 'Comercial' THEN nome_colaborador ELSE '' END) AS col_comercial
FROM (
    SELECT nome_colaborador, cargo_funcao,
           ROW_NUMBER() OVER(PARTITION BY cargo_funcao ORDER BY nome_colaborador ASC) AS idx_sequencial
    FROM folha_pagamento.funcionarios
) derivado
GROUP BY idx_sequencial;
  1. Desnormalização de Colunas em Registros (Unpivot)

Para converter estruturas horizontais de volta para formatos tabulares verticais, é necessário criar uma tabela temporária que atue como ponte entre as chaves e os valores originais, efetivamente realizando um produto cartesiano controlado.

Cenário: Reverter o resultado obtido na Seção 1 para um formato padrão de banco de dados (duas colunas: identificação e total).

Solução: Constrói-se um Common Table Expression (CTE) recursivo ou estático com as chaves correspondentes às colunas originais. Realiza-se o JOUX com os dados consolidados e aplica-se lógica CASE para mapear cada chave ao seu respectivo valor.

WITH mapeamento_chaves AS (
    SELECT 1 AS chave UNION ALL SELECT 2 UNION ALL SELECT 3
)
SELECT 
    chave * 10 AS codigo_departamento,
    CASE chave
        WHEN 1 THEN total_dep_10
        WHEN 2 THEN total_dep_20
        WHEN 3 THEN total_dep_30
    END AS quantidade_registros
FROM mapeamento_chaves k
CROSS JOIN (
    SELECT 
        SUM(CASE WHEN departamento_id = 10 THEN 1 ELSE 0 END) AS total_dep_10,
        SUM(CASE WHEN departamento_id = 20 THEN 1 ELSE 0 END) AS total_dep_20,
        SUM(CASE WHEN departamento_id = 30 THEN 1 ELSE 0 END) AS total_dep_30
    FROM folha_pagamento.funcionarios
) base; 
  1. Concatenação de Campos em Uma Coluna Simples

Existem situações onde múltiplas propriedades devem ser empilhadas verticalmente para uma entidade específica, separadas visualmente.

Cenário: Para o departamento 10, listar Nome, Cargo e Salário um abaixo do outro, com uma linha divisória entre os grupos.

Solução: Gera-se uma matriz de mapeamento com IDs progressivos. O produto cartesico multiplica cada colaborador por essa matriz, permitindo o uso de CASE para extrair o campo desejado conforme o ID.

WITH mapa_atributos AS (
    SELECT 1 AS id, 'Nome' AS tipo UNION ALL
    SELECT 2, 'Funcao' UNION ALL
    SELECT 3, 'Remuneracao' UNION ALL
    SELECT 4, '--'
)
SELECT 
    CASE id
        WHEN 1 THEN nome_colaborador
        WHEN 2 THEN cargo_funcao
        WHEN 3 THEN CAST(salario_bruto AS CHAR)
        WHEN 4 THEN '----------------'
    END AS campo_unificado
FROM folha_pagamento.funcionarios f
JOIN mapa_atributos m ON 1=1
WHERE departamento_id = 10
ORDER BY nome_colaborador, id;
  1. Supressão de Valores Repetidos em Listas Agrupadas

Em relatórios hierárquicos, é prática comum exibir o rótulo da categoria principal apenas na primeira ocorrência, ocultando repetições subsequentes dentro do mesmo bloco.

Solução: A função LAG() compara o valor atual com o registro imediatamente anterior ordenado pela coluna de agrupamento. Quando coincidirem, retorna uma string vazia; caso contrário, apresenta o identificador.

SELECT 
    CASE 
        WHEN LAG(departamento_id) OVER(ORDER BY departamento_id, nome_colaborador) = departamento_id THEN ''
        ELSE CAST(departamento_id AS CHAR)
    END AS departamento_id,
    nome_colaborador
FROM folha_pagamento.funcionarios;
  1. Normalização de Métricas para Comparação Direta

Cálculos comparativos entre múltiplas dimensões tornam-se mais eficientes quando as agregações são transformadas em campos horizontais, facilitando operações aritméticas diretas.

Cenário: Determinar quanto o total salarial dos departamentos 20 e 30 difere do valor acumulado no departamento 10.

Solução: Realiza-se o pivoteamento das somas para uma única linha interna. Na consulta externa, subtrai-se a coluna referência pelas demais para obter as diferenças.

SELECT 
    basico - alvo_20 AS diferenca_20_10,
    basico - alvo_30 AS diferenca_30_10
FROM (
    SELECT 
        SUM(CASE WHEN departamento_id = 10 THEN salario_bruto END) AS basico,
        SUM(CASE WHEN departamento_id = 20 THEN salario_bruto END) AS alvo_20,
        SUM(CASE WHEN departamento_id = 30 THEN salario_bruto END) AS alvo_30
    FROM folha_pagamento.funcionarios
) metricas;
  1. Classificação em Blocos de Tamanho Fixo

A divisão de um conjunto em partições iguais, definidas pelo volume de itens e não pela quantidade de grupos, é conhecida como binning de tamanho constante.

Solução: Atribui-se um número sequencial aos reigstros e divide-se pelo tamanho do bloco desejado. Arredondando o resultado para cima, obtém-se o número da faixa.

SELECT 
    ROW_NUMBER() OVER(ORDER BY nome_colaborador) AS seq_linha,
    CEIL(ROW_NUMBER() OVER(ORDER BY nome_colaborador) / 5.0) AS bloco_5_itens,
    nome_colaborador
FROM folha_pagamento.funcionarios;
  1. Distribuição em Quantidade Predeterminada de Classes

Quando o foco é dividir os dados em N faixas equilibradas, independentemente do volume bruto por classe, utilizam-se técnicas modulares ou funções nativas de particionamento.

Solução (Abordagem Modular): Utiliza-se o operador módulo sobre a numeração sequencial. O resíduo adicionado de 1 define o número do grupo, garantindo distribuição cíclica.

SELECT 
    nome_colaborador,
    (ROW_NUMBER() OVER(ORDER BY nome_colaborador) % 4) + 1 AS classe_quatro_grupos,
    ROW_NUMBER() OVER(ORDER BY nome_colaborador) AS indexacao
FROM folha_pagamento.funcionarios
ORDER BY classe_quatro_grupos, nome_colaborador;

Alternativamente, motores modernos suportam NTILE(n), que distribui automaticamente os registros nas faixas solicitadas, priorizando os primeiros lotes em caso de sobra.

  1. Representação Gráfica Horizontal (Histogramas ASCII)

Relatórios técnicos podem utilizar caracteres para gerar barras de progresso ou contagens visuais diretamente no console.

Solução: Combina-se COUNT() com funções de repetição de strings. Cada unidade de contagem gera um caractere (*), formando a barra proporcional.

SELECT 
    cargo_funcao,
    REPEAT('*', COUNT(*) ) AS barra_historico
FROM folha_pagamento.funcionarios
GROUP BY cargo_funcao;
  1. Representação Gráfica Vertical

O histograma vertical requer a transposição de categorias para colunas e o alinhamento descendente das marcas até atingir a altura máxima necessária.

Solução: Define-se uma posição vertical através de ROW_NUMBER() particionado pelo departamento. Aplicam-se filtros CASE para inserir marcadores (*) ou vazios. O agrupamento final elimina lacunas e mantém a estrutura descending.

SELECT 
    MAX(Dep_10) AS vis_dep10,
    MAX(Dep_20) AS vis_dep20,
    MAX(Dep_30) AS vis_dep30
FROM (
    SELECT 
        ROW_NUMBER() OVER(PARTITION BY departamento_id ORDER BY nome_colaborador) AS pos_vert,
        CASE departamento_id WHEN 10 THEN '*' ELSE '' END AS Dep_10,
        CASE departamento_id WHEN 20 THEN '*' ELSE '' END AS Dep_20,
        CASE departamento_id WHEN 30 THEN '*' ELSE '' END AS Dep_30
    FROM folha_pagamento.funcionarios
    ORDER BY departamento_id
) rotacao
GROUP BY pos_vert
ORDER BY pos_vert DESC;
  1. Extração de Colunas Não-Agregadas Junto a Group By

Normas SQL padrão impedem a seleção de atributos únicos fora do contexto de agregação. Para burlar essa limitação mantendo a conformidade, empregam-se funções de janela para calcular limites e filtrar resultados.

Cenário: Listar colaboradores que detenham os maiores e menores salários dentro de seus respectivos departamentos e cargos, exibindo suas informações completas.

Solução: Calculam-se os picos máximos e mínimos via OVER(PARTITION BY). Uma subconsulta filtra apenas os registros cujos salários batem exatamente contra esses patamares calculados.

SELECT 
    nome_colaborador, departamento_id, cargo_funcao, salario_bruto,
    CASE 
        WHEN salario_bruto = teto_dept THEN 'Máximo Departamental'
        WHEN salario_bruto = piso_dept THEN 'Mínimo Departamental'
    END AS analise_dept,
    CASE 
        WHEN salario_bruto = teto_cargo THEN 'Máximo Profissional'
        WHEN salario_bruto = piso_cargo THEN 'Mínimo Profissional'
    END AS analise_cargo
FROM (
    SELECT 
        nome_colaborador, departamento_id, cargo_funcao, salario_bruto,
        MAX(salario_bruto) OVER(PARTITION BY departamento_id) AS teto_dept,
        MIN(salario_bruto) OVER(PARTITION BY departamento_id) AS piso_dept,
        MAX(salario_bruto) OVER(PARTITION BY cargo_funcao) AS teto_cargo,
        MIN(salario_bruto) OVER(PARTITION BY cargo_funcao) AS piso_cargo
    FROM folha_pagamento.funcionarios
) escalonado
WHERE salario_bruto IN (teto_dept, piso_dept, teto_cargo, piso_cargo);
  1. Geração de Subtotais e Totais Gerais (ROLLUP)

A extensão ROLLUP na cláusula GROUP BY automatiza a geração de hierarquias de agregação, desde granularidades específicas até o consolidado global.

Solução: Aplica-se ROLLUP(coluna) e utiliza-se COALESCE() para substituir os nulos gerados nas linhas de resumo geral por rótulos interpretáveis.

SELECT 
    COALESCE(cargo_funcao, 'Consolidado_Geral') AS faixa_profissional,
    SUM(salario_bruto) AS valor_acumulado
FROM folha_pagamento.funcionarios
GROUP BY ROLLUP(cargo_funcao);
  1. Análise Multidimensional de Agregações (CUBE)

Enquanto o ROLLUP opera em hierarquias lineares, o CUBE calcula todas as combinações possíveis de agrupamento entre duas ou mais colunas, incluindo totais cruzados e gerais.

Solução: Utiliza-se GROUP BY CUBE(coluna_A, coluna_B) em conjunto com a função GROUPING() para identificar quando uma partição foi abandonada na soma, permitindo a formatação adequada das labels.

SELECT 
    CASE WHEN GROUPING(departamento_id) = 1 THEN 'Total_Deptoral' ELSE CAST(departamento_id AS CHAR) END AS ref_depto,
    CASE WHEN GROUPING(cargo_funcao) = 1 THEN 'Total_Profissional' ELSE cargo_funcao END AS ref_cargo,
    SUM(salario_bruto) AS acumulador_mensal
FROM folha_pagamento.funcionarios
GROUP BY CUBE(departamento_id, cargo_funcao)
ORDER BY ref_depto, ref_cargo;

Tags: SQL Window Functions Data Pivoting Aggregate Queries rollup

Publicado em 9-21 06:23