Diffs mensais do CNPJ: como transformar snapshots da Receita em sinais de prospecção

Sinais extraídos da comparação mensal do cadastro do CNPJ (filiais, CNAE, endereço, sócios) e como calculá-los em SQL sem falsos positivos.

· 8 min de leitura

O cadastro do CNPJ que a Receita publica todo mês é uma fotografia: mostra como cada empresa está, não o que mudou. Mas quem guarda as fotografias e as compara ganha algo que o arquivo sozinho não oferece: eventos. Uma filial aberta, uma troca de atividade principal, uma mudança de cidade ou a entrada de um novo sócio são acontecimentos que, muitas vezes, vêm acompanhados de novas necessidades, e portanto de novas compras.

Este artigo apresenta um catálogo de sinais que dá para extrair da comparação mensal, a leitura comercial de cada um e uma implementação em SQL (ClickHouse) que evita os falsos positivos mais comuns.

Por que diffs, e não só filtros

Um filtro responde "quais empresas são assim?". Um diff responde "quais empresas ficaram assim agora?". Para prospecção, a segunda pergunta costuma valer mais:

  • o momento da mudança é quando a empresa está revendo fornecedores, sistemas e processos;
  • uma lista de eventos recentes é pequena, específica e fácil de trabalhar com abordagem personalizada;
  • o próprio evento é um bom motivo de contato ("vi que vocês abriram uma unidade em Campinas").

O custo é de engenharia: guardar os snapshots, normalizar os campos e calcular as diferenças com cuidado. Lembre também que o ritmo é mensal. O dump de outubro de 2026, por exemplo, foi publicado em 13 de outubro, então um evento que aconteceu no começo de setembro pode só aparecer agora.

O catálogo de sinais

Sinal Como detectar Leitura comercial Ruído típico
Nova empresa Raiz que não existia no mês anterior Demanda por tudo: contabilidade, banco, software, equipamentos Empresas de fachada, MEIs (se não forem o seu público)
Nova filial Novo CNPJ com raiz já existente Expansão: TI, telecom, mobiliário, segurança, recrutamento Filiais abertas e baixadas em sequência
Mudança de CNAE principal Valor diferente entre os meses Mudança de modelo de negócio, novos produtos ou serviços Correções cadastrais sem mudança real
Novo CNAE secundário Código presente agora e ausente antes Diversificação, nova linha de receita Inclusões "por garantia"
Mudança de município Município diferente Mudança de sede: novos fornecedores locais Recodificação de tabelas, ajustes de CEP
Mudança de endereço na mesma cidade CEP ou logradouro diferente Mudança para espaço maior ou menor Variações de grafia ("R." e "RUA")
Saída do Simples Nacional Data de exclusão preenchida Crescimento acima do teto, ou pendência fiscal Exclusões por débito (sinal de risco)
Entrada ou saída de sócio Linha de sócio nova ou removida Sucessão, investimento, nova gestão Ajustes de qualificação
Mudança de natureza jurídica Ltda que vira S.A., por exemplo Preparação para investimento ou governança Raro, mas relevante
Mudança de situação Ativa para inapta, suspensa ou baixada Risco: limpar da base, rever crédito Reativações após regularização

Nenhum desses sinais é uma intenção de compra declarada. São gatilhos para olhar a empresa com mais atenção, e funcionam melhor combinados com o ICP: uma nova filial de uma empresa fora do seu perfil continua fora do perfil.

A base: snapshots versionados

O primeiro requisito é nunca sobrescrever o mês anterior. Uma tabela única, particionada por mês de referência, resolve isso de forma simples:

SQL
CREATE TABLE estab_snapshot
(
    referencia         Date,
    cnpj               FixedString(14),
    cnpj_basico        FixedString(8),
    situacao           LowCardinality(String),
    cnae_principal     LowCardinality(String),
    cnaes_secundarios  Array(LowCardinality(String)),
    uf                 LowCardinality(String),
    municipio          LowCardinality(String),
    cep                String,
    logradouro         String,
    numero             String,
    impressao          UInt64 MATERIALIZED cityHash64(
        situacao, cnae_principal, arraySort(cnaes_secundarios),
        uf, municipio, cep, upper(trim(logradouro)), upper(trim(numero)))
)
ENGINE = MergeTree
PARTITION BY referencia
ORDER BY (cnpj, referencia);

Dois detalhes fazem diferença:

  • Particionar por mês permite descartar meses antigos com DROP PARTITION e comparar dois meses lendo só as partições necessárias.
  • A coluna impressao é um hash dos campos monitorados, calculado na inserção. Comparar um inteiro por linha é muito mais barato do que comparar dez colunas, e só as linhas com hash diferente seguem para a análise campo a campo.

Normalize antes de comparar

A maior fonte de falsos positivos não é a Receita mudar dados, e sim a mesma informação aparecer escrita de outro jeito. Por isso o hash acima já aplica:

  • upper(trim(...)) nos campos de texto livre, para ignorar maiúsculas e espaços;
  • arraySort(...) na lista de CNAEs secundários, para que a ordem não gere diferença.

Outras normalizações úteis, conforme o campo:

  • expandir abreviações de logradouro ("R" para "RUA", "AV" para "AVENIDA");
  • tratar S/N, SN e vazio como o mesmo número;
  • remover pontuação do CEP;
  • mapear códigos antigos quando uma tabela de domínio for recodificada.

Novas empresas e novas filiais

Com os snapshots no lugar, novos estabelecimentos são os CNPJs do mês atual que não existiam no anterior. A raiz diz se é uma empresa nova ou uma filial de uma empresa existente:

SQL
SELECT
    n.cnpj,
    n.uf,
    n.cnae_principal,
    if(n.cnpj_basico IN (SELECT cnpj_basico FROM estab_snapshot WHERE referencia = '2026-09-01'),
       'nova_filial', 'nova_empresa') AS tipo
FROM (SELECT * FROM estab_snapshot WHERE referencia = '2026-10-01') AS n
LEFT ANTI JOIN (SELECT cnpj FROM estab_snapshot WHERE referencia = '2026-09-01') AS a
    ON a.cnpj = n.cnpj;

O LEFT ANTI JOIN devolve apenas as linhas da esquerda sem correspondência na direita, sem precisar de NOT IN sobre dezenas de milhões de valores.

Mudanças campo a campo

Para as linhas que existem nos dois meses e cujo hash mudou, transformamos cada campo monitorado numa tupla (campo, antes, depois) e mantemos só as que diferem:

SQL
SELECT cnpj, m.1 AS campo, m.2 AS antes, m.3 AS depois
FROM
(
    SELECT
        n.cnpj AS cnpj,
        [
            ('situacao',          a.situacao,       n.situacao),
            ('cnae_principal',    a.cnae_principal, n.cnae_principal),
            ('municipio',         a.municipio,      n.municipio),
            ('cnaes_secundarios', arrayStringConcat(arraySort(a.cnaes_secundarios), ','),
                                  arrayStringConcat(arraySort(n.cnaes_secundarios), ','))
        ] AS mudancas
    FROM (SELECT *, impressao FROM estab_snapshot WHERE referencia = '2026-10-01') AS n
    INNER JOIN (SELECT *, impressao FROM estab_snapshot WHERE referencia = '2026-09-01') AS a
        ON a.cnpj = n.cnpj
    WHERE a.impressao != n.impressao
)
ARRAY JOIN mudancas AS m
WHERE m.2 != m.3
ORDER BY cnpj, campo;

Repare no SELECT *, impressao: colunas MATERIALIZED não entram no * e precisam ser pedidas explicitamente.

O resultado é uma tabela de eventos com uma linha por mudança, pronta para gravar numa tabela eventos com a data de referência. O mesmo padrão vale para a tabela de empresas (capital social, natureza jurídica, porte), para o Simples (datas de exclusão) e para os sócios, comparando a chave composta de raiz, nome, documento mascarado e qualificação.

Um freio de segurança antes de publicar

Às vezes o arquivo de origem muda de layout, uma tabela de domínio é recodificada ou uma parte do dump chega incompleta. Se o pipeline publicar alertas nessas condições, os usuários recebem milhares de "mudanças" falsas de uma vez. Uma checagem simples evita isso:

SQL
SELECT
    countIf(a.impressao != n.impressao)        AS alterados,
    count()                                     AS comparados,
    round(alterados / comparados * 100, 2)      AS pct_alterados
FROM (SELECT cnpj, impressao FROM estab_snapshot WHERE referencia = '2026-10-01') AS n
INNER JOIN (SELECT cnpj, impressao FROM estab_snapshot WHERE referencia = '2026-09-01') AS a
    ON a.cnpj = n.cnpj;

Guarde esse percentual a cada carga. Se ele ficar muito acima do histórico, segure a publicação dos eventos e investigue. A mesma lógica vale para o número de novos estabelecimentos e de mudanças por campo.

Do evento à ação

Uma tabela de eventos só gera valor quando chega a quem vende. Algumas formas de entrega, da mais simples à mais integrada:

  1. Relatório mensal por segmento: novas filiais e mudanças de CNAE dentro do ICP, numa planilha.
  2. Alertas por lista monitorada: o vendedor acompanha suas contas e recebe um e-mail quando algo muda.
  3. Webhooks e integração com CRM: o evento cria uma tarefa ou atualiza um campo no CRM, com o motivo do contato.
  4. Pontuação dinâmica: eventos recentes aumentam a prioridade da conta por algumas semanas.

Em todos os casos, mostre o evento com contexto: o que mudou, de quanto para quanto e em qual mês de referência. "Mudou de São Paulo para Campinas no cadastro de outubro" é acionável; "houve uma alteração" não é.

Cuidados finais

  • Eventos não são intenção. Use-os para priorizar e personalizar, não para disparar mensagens automáticas em massa.
  • Sócios são pessoas. Entrada e saída de sócio pessoa física envolve dado pessoal; trate com os cuidados da LGPD e evite expor nomes onde não for necessário.
  • Defasagem. Deixe claro que o evento foi detectado no cadastro de um mês específico, não na data em que aconteceu.

Conclusão

Guardar os snapshots mensais, normalizar antes de comparar, calcular um hash por linha e analisar campo a campo só o que mudou transforma um arquivo estático em um fluxo de sinais. Com um freio de segurança antes da publicação e uma entrega que chegue ao CRM, esses eventos viram motivos concretos de conversa com as empresas certas, no momento certo.

Para acompanhar mudanças em empresas monitoradas sem manter esse pipeline, experimente o monitoramento da CNPJpedia.

Experimente a busca na CNPJpedia

Filtre empresas por CNAE, região, porte e situação cadastral na base da Receita Federal. Comece pelo plano grátis.

Criar conta grátis