Capa do Ebook gratuito SQL para Análise de Dados no Dia a Dia: Consultas, Relatórios e Insights com Dados Reais

SQL para Análise de Dados no Dia a Dia: Consultas, Relatórios e Insights com Dados Reais

Novo curso

26 páginas

Validação de resultados e checagens de consistência para evitar erros em relatórios

Capítulo 18

Tempo estimado de leitura: 0 minutos

+ Exercício

Validar resultados em SQL significa aplicar checagens sistemáticas para confirmar que uma consulta está correta, completa e consistente com as regras de negócio antes de virar um relatório, dashboard ou decisão. Na prática, a maioria dos erros em relatórios não vem de “SQL que não roda”, e sim de SQL que roda e entrega números plausíveis, porém errados: duplicidades silenciosas, filtros incompletos, períodos desalinhados, mudanças de definição, registros fora do escopo, ou dados faltantes que passam despercebidos.

Neste capítulo, o foco é criar um repertório de validações rápidas e repetíveis. A ideia é tratar cada entrega como um pequeno processo de auditoria: você não precisa “desconfiar de tudo”, mas precisa ter um conjunto padrão de testes que detecte as falhas mais comuns com o menor esforço possível.

O que validar: tipos de consistência que evitam erros

1) Consistência de escopo (o que entra e o que fica de fora)

Todo relatório tem um escopo: período, unidade de negócio, status elegíveis, canais, países, produtos. Um erro frequente é o escopo ficar implícito na cabeça de quem escreve a query, mas não estar corretamente implementado (ou estar implementado de forma diferente do combinado). Validação de escopo é provar que o conjunto de linhas analisado é exatamente o esperado.

2) Consistência de granularidade (o nível de detalhe)

Granularidade é o “tamanho do grão” da tabela final: por dia? por pedido? por cliente? por item do pedido? Muitos números errados surgem quando você calcula uma métrica em um grão e exibe em outro, ou quando mistura tabelas em grãos diferentes sem perceber. Validar granularidade é garantir que cada linha do resultado representa uma entidade única e que as métricas foram agregadas no nível correto.

3) Consistência de chaves e unicidade (sem duplicidades silenciosas)

Mesmo que você já tenha cuidado com duplicidades em junções, a validação aqui é operacional: provar com testes que a chave que você está usando é única no resultado e que não houve multiplicação de linhas. Isso inclui checar se uma dimensão tem mais de um registro “ativo” para a mesma chave, se há versões, se há histórico, etc.

Continue em nosso aplicativo

Você poderá ouvir o audiobook com a tela desligada, ganhar gratuitamente o certificado deste curso e ainda ter acesso a outros 5.000 cursos online gratuitos.

ou continue lendo abaixo...
Download App

Baixar o aplicativo

4) Consistência temporal (datas, fusos, janelas e cortes)

Relatórios quebram com facilidade quando há datas em colunas diferentes (criação, pagamento, envio), fusos horários, ou quando o período é definido por “mês fechado” versus “mês corrente”. Validar consistência temporal é garantir que o recorte de datas está alinhado com a definição da métrica e que não há vazamentos (linhas fora do período) nem buracos (dias faltando).

5) Consistência de regras de negócio (definições e exceções)

Uma métrica como “receita” pode excluir cancelamentos, incluir frete, considerar apenas pagamentos aprovados, ou reconhecer receita na data de faturamento. A validação aqui é transformar a regra em testes: quantos registros estão em cada status? há valores negativos? há pedidos sem pagamento? há itens sem preço?

6) Consistência estatística (sanity checks)

São checagens de plausibilidade: totais não podem ser negativos (em geral), taxas não podem passar de 100%, ticket médio não pode ser maior do que o máximo observado, contagens não podem exceder o número de entidades possíveis. Não é “prova formal”, mas detecta rapidamente erros grosseiros.

Estratégia prática: um checklist de validação em camadas

Uma forma eficiente de validar é usar camadas, do mais simples ao mais específico. Você executa as checagens na ordem; se uma falhar, você corrige antes de avançar.

  • Camada A — Escopo e volume: contagens básicas, datas mínimas/máximas, distribuição por status/canal.
  • Camada B — Unicidade e duplicidade: chave do resultado é única? houve multiplicação de linhas?
  • Camada C — Reconciliação de totais: totais batem com uma fonte de referência ou com uma query alternativa?
  • Camada D — Regras de negócio e exceções: casos borda, valores nulos, negativos, outliers.
  • Camada E — Reprodutibilidade: a query é estável, documentada e fácil de revalidar no próximo mês?

Passo a passo prático: validando um relatório de vendas (exemplo fictício)

Suponha um cenário comum: você precisa entregar um relatório mensal com receita, número de pedidos e ticket médio por canal e dia. Você já tem uma query que gera a tabela final. Agora você vai validar.

Vamos assumir tabelas fictícias: orders (pedido), order_items (itens), payments (pagamentos), channels (dimensão de canal). A validação não depende do nome exato, e sim do método.

Passo 1 — Fixe o contrato do relatório (definição explícita)

Antes de testar, escreva (nem que seja em comentário interno) a definição do que você está medindo. Exemplo de contrato:

  • Período: mês calendário (00:00 do dia 1 até 23:59 do último dia) na data de pagamento aprovado.
  • Receita: soma do valor pago aprovado (sem estornos), em moeda local.
  • Pedidos: contagem de pedidos com pelo menos um pagamento aprovado no período.
  • Canal: canal do pedido no momento da criação.

Esse contrato guia as checagens: se você filtrar por created_at em vez de paid_at, a validação temporal deve acusar divergência.

Passo 2 — Checagens de escopo e volume (sanity checks iniciais)

Comece verificando se o período está correto e se o volume parece razoável. Mesmo sem histórico, você consegue detectar erros como “puxei 2 anos em vez de 1 mês”.

SELECT  MIN(paid_at) AS min_paid_at, MAX(paid_at) AS max_paid_at, COUNT(*) AS rows_payments_approved FROM payments WHERE status = 'approved' AND paid_at >= DATE '2025-01-01' AND paid_at < DATE '2025-02-01';

O que observar:

  • min/max devem cair dentro do mês esperado.
  • count não deve ser absurdamente alto/baixo comparado ao normal (se você tiver referência).

Uma checagem complementar é ver a distribuição por status para garantir que você não está excluindo algo indevidamente:

SELECT status, COUNT(*) AS qtd FROM payments WHERE paid_at >= DATE '2025-01-01' AND paid_at < DATE '2025-02-01' GROUP BY status ORDER BY qtd DESC;

Passo 3 — Valide a granularidade do resultado final

Se o relatório final é “por dia e canal”, então a chave natural do resultado é (dia, canal). Você deve garantir que não existem duplicatas dessa chave no output.

WITH report AS (  SELECT    CAST(p.paid_at AS DATE) AS dia,    o.channel_id,    SUM(p.amount) AS receita,    COUNT(DISTINCT o.order_id) AS pedidos  FROM payments p  JOIN orders o ON o.order_id = p.order_id  WHERE p.status = 'approved'    AND p.paid_at >= DATE '2025-01-01'    AND p.paid_at < DATE '2025-02-01'  GROUP BY 1,2) SELECT dia, channel_id, COUNT(*) AS linhas_por_chave FROM report GROUP BY 1,2 HAVING COUNT(*) > 1;

Resultado esperado: zero linhas. Se aparecer algo, o seu GROUP BY não está alinhado com o grão ou você está juntando dimensões que duplicam.

Passo 4 — Teste de multiplicação de linhas (antes e depois de juntar)

Um erro comum é juntar orders com order_items e depois somar valores do pedido, inflando a receita pelo número de itens. Mesmo que você saiba disso, a validação é criar uma checagem que detecte multiplicação.

Compare contagens de pedidos antes e depois de uma junção suspeita:

WITH base AS (  SELECT DISTINCT o.order_id  FROM orders o  JOIN payments p ON p.order_id = o.order_id  WHERE p.status = 'approved'    AND p.paid_at >= DATE '2025-01-01'    AND p.paid_at < DATE '2025-02-01'), joined AS (  SELECT o.order_id  FROM base b  JOIN orders o ON o.order_id = b.order_id  JOIN order_items i ON i.order_id = o.order_id) SELECT  (SELECT COUNT(*) FROM base) AS pedidos_base,  (SELECT COUNT(*) FROM joined) AS linhas_apos_join,  (SELECT COUNT(DISTINCT order_id) FROM joined) AS pedidos_distintos_apos_join;

Interpretação:

  • Se linhas_apos_join for muito maior que pedidos_distintos_apos_join, a junção com itens multiplicou linhas (o que pode ser correto, mas exige agregação no nível certo).
  • Se o seu cálculo de receita usa uma coluna do pedido e você juntou itens, há risco de inflar. A validação serve para acender o alerta.

Passo 5 — Reconciliação de totais por duas abordagens independentes

Uma técnica poderosa é calcular o mesmo total de duas formas diferentes e comparar. Se bater, aumenta a confiança; se divergir, você investiga.

Abordagem A: receita total direto de pagamentos aprovados.

SELECT SUM(amount) AS receita_total FROM payments WHERE status = 'approved' AND paid_at >= DATE '2025-01-01' AND paid_at < DATE '2025-02-01';

Abordagem B: receita total a partir do relatório agregado (somando o que você vai reportar).

WITH report AS (  SELECT    CAST(p.paid_at AS DATE) AS dia,    o.channel_id,    SUM(p.amount) AS receita  FROM payments p  JOIN orders o ON o.order_id = p.order_id  WHERE p.status = 'approved'    AND p.paid_at >= DATE '2025-01-01'    AND p.paid_at < DATE '2025-02-01'  GROUP BY 1,2) SELECT SUM(receita) AS receita_total_report FROM report;

Os dois totais devem ser iguais (ou diferir apenas por arredondamento, se houver). Se não bater:

  • Você pode ter perdido pagamentos por causa de junção (ex.: pedidos sem canal válido).
  • Você pode estar duplicando pagamentos (ex.: múltiplas linhas por transação).
  • Você pode estar filtrando datas em colunas diferentes.

Passo 6 — Checagens de integridade referencial (dimensões faltantes)

Quando você segmenta por canal, produto, região, é comum “sumir” parte do total porque algumas linhas não encontram correspondência na dimensão (ou porque o join foi feito como INNER quando deveria preservar linhas). Uma validação simples é medir quantos registros ficam sem dimensão.

SELECT  COUNT(*) AS pagamentos,  SUM(CASE WHEN o.channel_id IS NULL THEN 1 ELSE 0 END) AS sem_canal FROM payments p LEFT JOIN orders o ON o.order_id = p.order_id WHERE p.status = 'approved' AND p.paid_at >= DATE '2025-01-01' AND p.paid_at < DATE '2025-02-01';

Se sem_canal > 0, você decide: o relatório deve agrupar em “Canal desconhecido”? Deve corrigir a origem? Deve excluir explicitamente e documentar?

Passo 7 — Checagens de consistência de métricas derivadas (taxas e médias)

Métricas como ticket médio e taxa de conversão são especialmente sensíveis a erros de denominador. Valide limites e coerência:

  • Ticket médio > 0 (na maioria dos contextos).
  • Taxas entre 0 e 1 (ou 0% e 100%).
  • Pedidos ≤ pagamentos (dependendo do modelo) ou pelo menos coerente com a regra.

Exemplo de checagem para ticket médio por canal no mês:

WITH report AS (  SELECT    o.channel_id,    SUM(p.amount) AS receita,    COUNT(DISTINCT o.order_id) AS pedidos  FROM payments p  JOIN orders o ON o.order_id = p.order_id  WHERE p.status = 'approved'    AND p.paid_at >= DATE '2025-01-01'    AND p.paid_at < DATE '2025-02-01'  GROUP BY 1) SELECT  channel_id,  receita,  pedidos,  CASE WHEN pedidos = 0 THEN NULL ELSE receita * 1.0 / pedidos END AS ticket_medio FROM report WHERE pedidos = 0 OR receita < 0 OR (receita * 1.0 / NULLIF(pedidos,0)) <= 0;

Se aparecerem linhas, investigue: pode haver estornos incluídos, pedidos com pagamento zero, ou erro de filtro.

Passo 8 — Procure buracos e duplicações no tempo (dias faltando)

Relatórios diários podem “pular” dias por ausência de dados ou por erro de filtro. Uma checagem útil é listar dias presentes e comparar com o calendário esperado (mesmo sem uma tabela calendário, você pode ao menos verificar min/max e contagem de dias distintos).

SELECT  COUNT(DISTINCT CAST(paid_at AS DATE)) AS dias_com_pagamento,  MIN(CAST(paid_at AS DATE)) AS primeiro_dia,  MAX(CAST(paid_at AS DATE)) AS ultimo_dia FROM payments WHERE status = 'approved' AND paid_at >= DATE '2025-01-01' AND paid_at < DATE '2025-02-01';

Se você espera um mês completo e só aparecem poucos dias, pode ser:

  • Filtro de data incorreto (coluna errada, timezone, limites inclusivos/exclusivos).
  • Dados ainda não carregados (pipeline atrasado).
  • Status aprovado não existe para parte do período (mudança operacional).

Passo 9 — Testes de casos borda (amostras e drill-down)

Depois das checagens agregadas, valide com “drill-down”: pegue um dia e um canal com valor alto e confira os registros base. O objetivo é garantir que o agregado é explicável por linhas reais.

-- Escolha um dia/canal suspeito e liste alguns pedidos e pagamentos SELECT  o.order_id,  p.payment_id,  p.amount,  p.paid_at,  o.channel_id FROM payments p JOIN orders o ON o.order_id = p.order_id WHERE p.status = 'approved' AND CAST(p.paid_at AS DATE) = DATE '2025-01-15' AND o.channel_id = 3 ORDER BY p.amount DESC LIMIT 50;

O que procurar:

  • Pagamentos duplicados para o mesmo pedido (parcelas? retentativas?).
  • Valores inesperados (muito altos/baixos).
  • Datas fora do esperado (pagamento em outro mês mas caindo no filtro por conversão de fuso).

Checagens reutilizáveis (templates) para o dia a dia

Template 1 — Unicidade de chave no resultado

Use sempre que o resultado final tem uma chave definida (ex.: dia+canal, mês+produto, cliente+mês).

WITH final AS (  -- sua query final aqui ) SELECT  chave1, chave2, COUNT(*) AS qtd FROM final GROUP BY 1,2 HAVING COUNT(*) > 1;

Template 2 — Reconciliação: total do relatório vs total da fonte

WITH final AS (  -- sua query final aqui ), fonte AS (  -- cálculo direto na fonte, o mais simples possível ) SELECT  (SELECT SUM(metrica) FROM final) AS total_final,  (SELECT SUM(metrica) FROM fonte) AS total_fonte,  (SELECT SUM(metrica) FROM final) - (SELECT SUM(metrica) FROM fonte) AS diff;

Template 3 — Distribuição por categorias críticas (status, tipo, origem)

Ótimo para detectar filtros errados e mudanças de processo.

SELECT categoria, COUNT(*) AS qtd, SUM(valor) AS soma_valor FROM base WHERE periodo_filtro GROUP BY categoria ORDER BY qtd DESC;

Template 4 — Detecção de outliers e valores inválidos

SELECT * FROM base WHERE valor < 0 OR valor IS NULL OR valor > limite_esperado;

Erros comuns e como as validações os capturam

Filtro de data no campo errado

Sintoma: total do mês parece “ok”, mas distribuição diária fica estranha, ou o fechamento não bate com o financeiro. Checagens que pegam: min/max de datas, dias distintos, reconciliação por fonte (pagamentos vs pedidos).

Perda de linhas por junção com dimensão

Sintoma: total segmentado é menor que o total geral. Checagens que pegam: contagem de registros sem dimensão, comparação de total antes/depois de juntar dimensão, reconciliação de totais.

Duplicidade por relacionamento 1:N não tratado

Sintoma: receita inflada, contagens maiores que o possível. Checagens que pegam: teste de multiplicação de linhas, unicidade da chave no resultado, comparação de totais por duas abordagens.

Mudança silenciosa de definição (status, regra de elegibilidade)

Sintoma: quebra de série histórica ou salto repentino. Checagens que pegam: distribuição por status, comparação de volumes por categoria, amostras (drill-down) em dias de pico.

Como incorporar validação no fluxo de trabalho (sem virar burocracia)

Para a validação ser sustentável, ela precisa ser rápida e repetível. Algumas práticas simples ajudam:

  • Crie uma seção “checks” no seu arquivo de query: mantenha consultas de validação logo abaixo da query principal, prontas para rodar.
  • Padronize 5 a 8 checagens para a maioria dos relatórios: escopo, unicidade, reconciliação, dimensões faltantes, outliers, distribuição por status.
  • Registre números de controle: total do mês, total por canal, contagem de pedidos. Na próxima atualização, compare rapidamente.
  • Separe “cálculo” de “apresentação”: quando possível, valide primeiro uma tabela base (fato) e depois as segmentações.
  • Documente exceções: se você decidiu excluir um status raro, ou agrupar “desconhecido”, deixe explícito para evitar regressões.

Mini-roteiro de validação para qualquer relatório

Use este roteiro como sequência padrão:

  • (1) Escopo: min/max de datas, contagem de linhas, contagem de entidades principais.
  • (2) Granularidade: chave do resultado é única?
  • (3) Duplicidade: contagem antes/depois de junções críticas.
  • (4) Reconciliação: total do relatório bate com total calculado direto na fonte?
  • (5) Dimensões: quantos registros ficam sem correspondência em dimensões?
  • (6) Sanity: limites (taxas 0–100%), valores negativos, nulos em campos essenciais.
  • (7) Drill-down: amostra de um ponto alto e um ponto baixo para explicar o agregado.

Agora responda o exercício sobre o conteúdo:

Ao validar um relatório segmentado por dia e canal, qual checagem ajuda diretamente a detectar duplicidades silenciosas e problemas de granularidade no resultado final?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Checar a unicidade da chave do output (por exemplo, dia e canal) confirma a granularidade e evidencia multiplicação de linhas. Se houver mais de uma linha por chave, há duplicidade ou grão incorreto na agregação/junções.

Próximo capitúlo

Boas práticas de legibilidade: alias, formatação, nomes e estrutura de query

Arrow Right Icon
Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.