Ir para o conteúdo

Guias de engenharia

Como verificar uma importação de CSV no PostgreSQL

Confira mais do que contagens de linhas após uma importação de cliente. Compare chaves aceitas, valores transformados, escopo de tenant e resultados não resolvidos com SQL do PostgreSQL.

Mohamed Atef · ImportFlow
Última revisão

Compare os registros de origem aceitos com os registros confirmados no destino usando chaves estáveis e depois verifique valores importantes e invariantes de negócio. Mantenha exclusões, gravações com falha e resultados não resolvidos separados. Uma contagem correspondente é uma primeira verificação útil, mas não consegue mostrar se os registros certos chegaram.

Uma forma resumida de dizer isso é: a inserção comprova a inserção; a reconciliação comprova a correção. A segunda parte é condicional: a reconciliação estabelece a correção apenas em relação às comparações e aos invariantes que você realmente escolheu. Ela não consegue comprovar uma regra de negócio não declarada.

Os exemplos abaixo expõem uma importação errada que passa na verificação de contagem de linhas e depois mostram como detectá-la.

Escopo e premissas. PostgreSQL 17. Tabelas temporárias com dados sintéticos demonstram a reconciliação de um conjunto aceito no escopo de um tenant; elas não se conectam a dados de clientes.

Validação, inserção e reconciliação respondem a perguntas diferentes

EtapaPerguntaLimite
Validação préviaEstes dados propostos atendem às regras que verificamos?Pode não enxergar o estado atual, alterações concorrentes ou o comportamento real dos triggers
Evidência de inserção / commitEstas gravações no banco de dados foram confirmadas?Não estabelece a identidade da origem nem o significado no domínio
ReconciliaçãoO resultado observado está de acordo com a origem aceita e os invariantes escolhidos?Abrange apenas o escopo, o snapshot e as comparações realizadas

Mantenha essas distinções na interface e na documentação de entrega. “O CSV foi interpretado” não é “a importação passou na validação”. “O servidor recebeu” não é “o banco de dados confirmou”. “200 inseridos” não é “todos os dados do cliente estão corretos”.

Um verificador de DDL entra antes dessas verificações de resultados reais. O PG Import Check pode expor requisitos declarados e lacunas de análise no SQL fornecido. Ele não valida registros CSV, não consulta o destino nem realiza reconciliação.

Fixe o que você espera encontrar

Antes da execução, preserve um conjunto aceito revisado. Ele deve identificar os registros de origem que serão gravados, seus valores transformados aprovados, o tenant e o namespace relevante do sistema de origem. Guarde junto dele o mapeamento e a versão das regras.

Registre as exclusões explicitamente. Se um arquivo de 205 registros tiver cinco contatos intencionalmente excluídos, o conjunto esperado de inserção é 200. Se esses cinco foram perdidos porque um join não encontrou um registro pai, são perdas sem explicação até que alguém aprove seu tratamento.

Escolha uma chave estável. Ela pode ser uma referência externa de contato no escopo do tenant, um mapa de IDs gerados no destino ou um registro de importação que associe registros de origem a identidades de destino. Um endereço de e-mail não é automaticamente uma boa chave: endereços podem mudar, ser compartilhados ou estar ausentes.

Se o destino não tiver uma identidade estável para correspondência nem uma identificação confiável da importação, reconheça essa limitação antes da gravação. Uma janela de timestamps ou uma diferença de contagem pode incluir atividade concorrente da aplicação. Adicione uma forma revisada de identificar as linhas desta importação ou restrinja a afirmação sobre o que foi verificado.

Demonstração: contagens iguais, registros diferentes

Execute esta configuração em uma sessão descartável do PostgreSQL 17. As duas tabelas representam apenas o tenant em revisão; suas chaves primárias impõem unicidade dentro deste exemplo.

SQL
CREATE TEMP TABLE import_expected_demo (
  tenant_id integer NOT NULL,
  external_ref text NOT NULL,
  display_name text,
  PRIMARY KEY (tenant_id, external_ref)
);
CREATE TEMP TABLE import_actual_demo (
  tenant_id integer NOT NULL,
  external_ref text NOT NULL,
  display_name text,
  PRIMARY KEY (tenant_id, external_ref)
);

INSERT INTO import_expected_demo VALUES
  (10, 'contact-a', 'Ada'),
  (10, 'contact-b', 'Bela'),
  (10, 'contact-c', NULL);

INSERT INTO import_actual_demo VALUES
  (10, 'contact-a', 'Ada'),
  (10, 'contact-c', ''),
  (10, 'contact-d', 'Dara');

SELECT
  (SELECT count(*) FROM import_expected_demo WHERE tenant_id = 10)
    AS expected_count,
  (SELECT count(*) FROM import_actual_demo WHERE tenant_id = 10)
    AS actual_count;

As duas contagens são três. Ainda assim, o contato B está ausente, o contato D é inesperado e o contato C tem um nome vazio em vez de NULL. Por isso, a igualdade de contagens é necessária para esta expectativa específica de correspondência um para um, mas insuficiente para garantir a correção.

Compare a presença nos conjuntos nos dois sentidos

Use as chaves de negócio aceitas para encontrar registros ausentes:

SQL
SELECT external_ref
FROM import_expected_demo WHERE tenant_id = 10
EXCEPT
SELECT external_ref
FROM import_actual_demo WHERE tenant_id = 10;

Isso retorna contact-b. Inverta a comparação para encontrar registros inesperados:

SQL
SELECT external_ref
FROM import_actual_demo WHERE tenant_id = 10
EXCEPT
SELECT external_ref
FROM import_expected_demo WHERE tenant_id = 10;

Isso retorna contact-d. Em uma tabela de produção com contatos preexistentes, não compare o tenant inteiro com o conjunto aceito de uma nova importação e classifique todo contato antigo como inesperado. Delimite as linhas reais usando uma identificação confiável da importação ou compare com uma expectativa explícita e construída do estado completo após a importação.

EXCEPT remove linhas duplicadas da saída. Aqui, as chaves primárias garantem a unicidade. Sem essa garantia, a igualdade de conjuntos pode esconder erros de multiplicidade. Verifique contagens agrupadas por chave ou use uma comparação de multiconjuntos adequada, como EXCEPT ALL, entendendo a semântica de NULL e de normalização.

Compare valores com semântica que trate NULL corretamente

Depois de verificar a presença dos registros nos conjuntos, compare os campos importantes. Use a representação transformada esperada em vez de uma string bruta que foi normalizada intencionalmente.

SQL
SELECT expected.external_ref,
       expected.display_name AS expected_name,
       actual.display_name AS actual_name
FROM import_expected_demo AS expected
JOIN import_actual_demo AS actual
  ON actual.tenant_id = expected.tenant_id
 AND actual.external_ref = expected.external_ref
WHERE expected.tenant_id = 10
  AND expected.display_name IS DISTINCT FROM actual.display_name;

Isso retorna o contato C. Uma comparação comum com <> envolvendo NULL produz um resultado desconhecido, portanto pode deixar de detectar a divergência em uma cláusula WHERE. IS DISTINCT FROM fornece a comparação com tratamento de NULL usada aqui. O join trata deliberadamente das chaves correspondentes; ele não substitui as verificações de chaves ausentes.

Escolha os invariantes do domínio com o responsável pelos dados. Para uma importação de contatos, isso pode significar o tenant e o relacionamento com a empresa aprovados, status de ciclo de vida permitidos e preservação de referências externas. Para uma importação de faturas, pode significar totais por moeda e estado do documento. Uma única soma global pode ocultar erros que se anulam.

Compare os valores gerados pelo banco de acordo com suas propriedades pretendidas. Não espere que um ID recém-gerado seja igual a um ID legado. Para um total calculado, teste a aritmética e o arredondamento acordados. Para um timestamp de auditoria, teste a regra de tempo documentada. Veja valores gerados e valores padrão.

Contabilize cada registro de origem uma única vez

Defina estados mutuamente exclusivos para seu fluxo de trabalho. Para uma importação apenas de inserção, uma contabilização final útil separa exclusões aprovadas, registros com commit confirmado, registros comprovadamente não inseridos e registros não resolvidos. O trabalho não tentado pertence à categoria de não inseridos, com um motivo separado; não dê a entender que ele falhou em uma verificação do banco.

Contabilização ilustrativa
205 parsed source records
  5 approved exclusions
196 confirmed committed
  2 confirmed non-inserted (rejected batch)
  2 unresolved (commit evidence unavailable)
---
205 accounted for; import is not yet complete

Esta é uma convenção de contabilização de exemplo, não um esquema universal. Um fluxo de upsert também precisa distinguir registros inseridos, atualizados e intencionalmente inalterados. Uma origem deduplicada pode ter vários registros de origem representando uma entidade aceita; registre essa relação em vez de forçar uma equação inválida de uma linha para uma linha.

Um timeout de rede não comprova que não houve gravações. Consulte um comprovante persistente ou evidências no banco que identifiquem a tentativa. Se o resultado continuar desconhecido, mantenha-o como desconhecido e interrompa uma repetição cega de tudo. O guia de projeto do importador explica um padrão atômico de gravação e comprovante.

Use um snapshot consistente e depois teste a aplicação

Várias consultas executadas em momentos diferentes podem descrever estados diferentes do banco. Para uma aplicação ativa, decida se a verificação usa uma janela de gravação controlada, um snapshot consistente do banco ou uma visão imutável específica da importação. Ensaie o custo dessa escolha; uma transação longa tem consequências operacionais.

No isolamento padrão Read Committed do PostgreSQL, instruções separadas podem enxergar snapshots confirmados diferentes. Repeatable Read oferece um snapshot consistente da transação, mas não congela o mundo do negócio nem inclui transações que confirmem após esse snapshot. Veja a documentação de isolamento.

Após as comparações no banco, teste a aplicação com o usuário pretendido. Uma linha pode existir, mas estar oculta pela RLS, vinculada a outro tenant, excluída por um filtro de status ou sem um relacionamento obrigatório. Revise também os efeitos posteriores: um e-mail ou uma chamada de API externa pode precisar de evidências próprias, além da linha inserida.

A amostragem pode ajudar a encontrar problemas de formato ou de experiência do usuário; ela não comprova que todas as linhas correspondem. Informe quais verificações cobriram todo o conjunto aceito e quais foram por amostragem.

Torne o registro de aceitação útil para consultas futuras

Mantenha uma documentação de entrega concisa com a identidade da origem aceita, a versão do mapeamento e das regras, o destino e o tenant, as evidências de execução, as exclusões, os resultados das comparações e as verificações não resolvidas. Inclua quem revisou o significado de negócio e quando a verificação foi executada.

Não coloque registros brutos de clientes em logs rotineiros ou chamados públicos. Retenha dados de diagnóstico apenas onde houver autorização, restrinja o acesso e acorde sua exclusão. Uma referência externa pode ser sensível por si só, mesmo quando não é um nome nem um endereço de e-mail.

Se uma verificação falhar, investigue antes de alterar a produção. Corrigir um valor de origem, reparar um mapeamento e compensar uma gravação confirmada são ações diferentes. Preserve as evidências do resultado original para que um reparo não apague o que precisa ser explicado.

Fontes e leituras complementares