Guias de engenharia
Importações de CSV no PostgreSQL: valores padrão, identidade e colunas geradas
Escolha uma lista explícita de colunas para COPY. Entenda valores omitidos, NULL, substituições de identidade e expressões geradas antes de importar dados de clientes.
Quando o PostgreSQL deve fornecer um valor, omita essa coluna da lista de entrada do COPY. Não adicione uma célula CSV em branco como substituto para a omissão. Um campo em branco pode se tornar NULL, e NULL normalmente não aciona o valor padrão de uma coluna.
Há uma segunda decisão: se um valor da origem é permitido. Um valor padrão comum geralmente permite uma substituição explícita. Uma expressão gerada não permite. Colunas de identidade têm regras próprias, incluindo uma exceção do COPY que pode surpreender um engenheiro acostumado ao INSERT.
Essas distinções importam quando uma exportação de cliente contém campos como id, created_at ou um total calculado. A capacidade do banco de aceitar um valor e a permissão da aplicação para aceitá-lo são questões separadas.
Escopo e premissas. Os exemplos foram preparados para PostgreSQL 17. O PostgreSQL 18 também oferece suporte a colunas geradas virtuais; os exemplos aqui usam STORED.
Classifique os valores antes de mapear cabeçalhos
| Comportamento da coluna | O que fornece o valor? | Decisão de importação |
|---|---|---|
| Coluna comum, sem valor padrão | A origem ou um valor confiável da aplicação; caso contrário, NULL | Forneça os dados obrigatórios explicitamente |
| Expressão DEFAULT comum | O valor padrão quando omitido; um valor explícito pode substituí-lo | Escolha se substituições são permitidas |
| GENERATED BY DEFAULT AS IDENTITY | Sequência quando omitido; é possível fornecer valores | Escolha uma política de preservação de IDs |
| GENERATED ALWAYS AS IDENTITY | Sequência em um INSERT comum com o valor omitido; um INSERT explícito precisa de uma cláusula de substituição | Não pressuponha que COPY rejeite IDs fornecidos |
| GENERATED ALWAYS AS (expression) STORED | O PostgreSQL calcula a partir da linha | Omita da lista de colunas do COPY |
| Tenant ou autor controlado pela aplicação | Contexto autorizado da aplicação | Exclua dos mapeamentos controlados pelo cliente |
“Controlado pelo banco de dados” é uma política de importação, não um tipo único de coluna do PostgreSQL. Um timestamp com DEFAULT now() pode representar um evento histórico que a origem tem permissão para fornecer ou um horário de auditoria que a origem nunca deve alterar. O DDL não consegue determinar esse significado de negócio.
Documente a decisão para cada campo. Para timestamps históricos, registre também o fuso horário da origem e o tratamento de valores ausentes. Para identificadores, declare se são referências externas ou chaves primárias de produção. Nomes iguais não estabelecem identidades iguais.
Uma pequena importação que deixa a geração a cargo do PostgreSQL
Execute este exemplo em uma única sessão psql conectada a um banco PostgreSQL 17 descartável. A tabela temporária desaparece quando a sessão termina.
CREATE TEMP TABLE import_lines_demo (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
external_ref text NOT NULL UNIQUE,
quantity integer NOT NULL CHECK (quantity > 0),
unit_price numeric(12,2) NOT NULL CHECK (unit_price >= 0),
status text NOT NULL DEFAULT 'new',
total numeric GENERATED ALWAYS AS (quantity * unit_price) STORED
);
COPY import_lines_demo (external_ref, quantity, unit_price)
FROM STDIN WITH (FORMAT csv, HEADER MATCH);
external_ref,quantity,unit_price
line-a,2,12.50
line-b,3,4.00
\.
SELECT id, external_ref, status, total
FROM import_lines_demo ORDER BY id;O resultado contém os IDs 1 e 2, o status new nas duas linhas e os totais 25.00 e 12.00. Este é um exemplo com dados embutidos no psql: a linha que contém barra invertida e ponto encerra o fluxo de dados. Não é um bloco para colar em um editor SQL na web.
A lista de importação tem três colunas porque há três campos na origem. ID, status e total estão ausentes da entrada. O PostgreSQL os fornece conforme suas diferentes definições. Isso é mais fácil de revisar do que enviar seis campos e esperar que os valores em branco tenham o significado correto.
Para um arquivo local separado, use a mesma lista de colunas com \copy do psql. A referência do COPY especifica como a ordem dos campos, as colunas omitidas e as opções de entrada são interpretadas.
Omitido, NULL e vazio são entradas diferentes
Com as configurações padrão de CSV do PostgreSQL, um campo vazio sem aspas representa NULL. Um campo vazio entre aspas, "", representa uma string vazia. Nenhum dos dois equivale a deixar uma coluna fora da lista de entrada.
external_ref,quantity,unit_price,status
line-c,1,8.00,
line-d,1,8.00,""Ao importar com status incluído e as opções padrão de CSV, o primeiro registro viola a restrição NOT NULL da tabela. O segundo forneceria um status vazio em vez de new. Não há um CHECK de status permitidos nesta tabela de demonstração, portanto uma string vazia isoladamente seria aceita. Isso ilustra por que “aceito pelo PostgreSQL” ainda pode estar errado para a aplicação.
Em uma instrução INSERT, o SQL DEFAULT pode solicitar um valor padrão explicitamente. O texto DEFAULT em um campo CSV comum é apenas texto. O PostgreSQL 17 tem uma opção separada de marcador DEFAULT no COPY; use-a apenas com uma convenção de arquivo acordada e sem colisões. Ela não é necessária quando a coluna inteira pode ser omitida.
Quando alguns registros fornecem um valor intencionalmente e outros precisam do valor padrão, uma única lista de colunas omitidas não consegue expressar os dois casos. As opções incluem um marcador de valor padrão documentado, agrupar linhas de acordo com os campos da gravação ou usar staging seguido de instruções INSERT deliberadas. Não substitua essa distinção por “vazio significa padrão” a menos que o responsável pelos dados realmente aprove essa regra.
COPY pode fornecer uma identidade ALWAYS
GENERATED ALWAYS AS IDENTITY impede que um INSERT comum forneça o valor de identidade sem uma opção explícita. COPY FROM ainda pode usar IDs presentes em sua entrada. Trate isso como uma capacidade explícita de importação, não como uma fronteira de autorização.
Após a demonstração anterior, o COPY a seguir pode inserir um ID escolhido:
COPY import_lines_demo (id, external_ref, quantity, unit_price)
FROM STDIN WITH (FORMAT csv);
900,line-explicit,1,1.00
\.Se sua política for criar novas identidades no destino, exclua id tanto do mapeamento quanto da lista do COPY. Se a preservação de IDs for intencional, verifique conflitos e o estado da sequência associada. Fornecer uma identidade não avança, por si só, a sequência até o maior ID importado; um valor gerado posteriormente pode acabar colidindo.
O ajuste da sequência faz parte do plano de migração do responsável pelo banco. Não execute um comando genérico de “definir a sequência para o maior ID” durante gravações concorrentes da aplicação: valores podem já ter sido alocados. Ensaie o procedimento escolhido com as condições de concorrência e manutenção da aplicação. A documentação de identidades e a referência de funções de sequência explicam os mecanismos.
Expressões geradas são recalculadas, não importadas
A planilha de um cliente pode ter sua própria coluna “Total”. Para a tabela do exemplo, o total no destino é calculado a partir da quantidade e do preço unitário. Deixe o total da origem fora do mapeamento de gravação e compare-o separadamente se ele for uma evidência útil.
Se os totais divergirem, decida qual regra está errada. Um sistema de origem pode calcular impostos antes de arredondar, enquanto o destino arredonda primeiro. Sobrescrever um valor gerado no destino não é o caminho de correção. Você precisa de uma transformação de entrada aprovada ou de uma alteração revisada no modelo de destino.
O PostgreSQL 17 oferece suporte a colunas geradas armazenadas. A geração ocorre após as alterações relevantes de triggers BEFORE na linha-base. A presença de uma expressão gerada, portanto, não estabelece que o valor será igual a um cálculo feito anteriormente em uma prévia. Leia a expressão e os triggers aplicáveis e depois teste o caminho real de gravação. Veja as regras de colunas geradas do PostgreSQL 17.
O que incluir na revisão da importação
Para cada coluna, registre quem fornece seu valor, a regra de omissão, as substituições permitidas pela origem e as evidências por trás dessa decisão. Inclua a tabela de destino exata e a lista ordenada de colunas do COPY. Se um valor obrigatório vier da aplicação, nomeie a autoridade que o fornece.
- Teste valores omitidos, NULL e vazios separadamente onde fizerem sentido.
- Teste uma tentativa de substituição pela origem para cada coluna protegida.
- Confirme se IDs preservados exigem um procedimento de sequência ou de mapeamento de referências.
- Compare os valores derivados armazenados com as regras de negócio aprovadas após a inserção.
O PG Import Check pode ajudar a classificar o DDL fornecido: valores de origem obrigatórios, valores padrão comuns, identidades e expressões geradas permanecem distintos. Ele também mostra achados estruturais e lacunas da análise. Não executa expressões, não inspeciona o esquema ativo, não valida registros de origem nem deduz que um valor padrão autoriza um campo.
Para campos como o ID do tenant, continue com o guia de autoridade e RLS. Para conferir os valores resultantes, use a verificação pós-importação.