PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

ERRO:dados extras após a última coluna esperada ao usar PostgreSQL COPY


Uma mesa vazia não serve. Você precisa de uma tabela que corresponda à estrutura dos dados de entrada. Algo como:
CREATE TABLE raw_data (
  col1 int
, col2 int
  ...
);

Você não precisa declarar tab como DELIMITER já que esse é o padrão:
COPY raw_data FROM '/home/Projects/TestData/raw_data.txt';

800 colunas você diz? Essa quantidade de colunas normalmente indicaria um problema com seu design. De qualquer forma, existem maneiras de automatizar parcialmente o CREATE TABLE roteiro.

Automação


Presumindo dados brutos simplificados
1   2   3   4  -- first row contains "column names"
1   1   0   1  -- tab separated
1   0   0   1
1   0   1   1

Defina um DELIMITER diferente (uma que não ocorre nos dados de importação) e importe para uma tabela de preparação temporária com um único text coluna:
CREATE TEMP TABLE tmp_data (raw text);

COPY tmp_data FROM '/home/Projects/TestData/raw_data.txt' WITH (DELIMITER '§');

Esta consulta cria o CREATE TABLE roteiro:
SELECT 'CREATE TABLE tbl (col' || replace (raw, E'\t', ' bool, col') || ' bool)'
FROM   (SELECT raw FROM tmp_data LIMIT 1) t;

Uma consulta mais genérica e segura:
SELECT 'CREATE TABLE tbl('
    ||  string_agg(quote_ident('col' || col), ' bool, ' ORDER  BY ord)
    || ' bool);'
FROM  (SELECT raw FROM tmp_data LIMIT 1) t
     , unnest(string_to_array(t.raw, E'\t')) WITH ORDINALITY c(col, ord);

Devoluções:
CREATE TABLE tbl (col1 bool, col2 bool, col3 bool, col4 bool);

Execute após verificar a validade - ou execute dinamicamente se você confiar no resultado:
DO
$$BEGIN
EXECUTE (
   SELECT 'CREATE TABLE tbl (col' || replace(raw, ' ', ' bool, col') || ' bool)'
   FROM  (SELECT raw FROM tmp_data LIMIT 1) t
   );
END$$;

Então INSERIR os dados com esta consulta:
INSERT INTO tbl
SELECT (('(' || replace(replace(replace(
                  raw
                , '1',   't')
                , '0',   'f')
                , E'\t', ',')
             || ')')::tbl).*
FROM   (SELECT raw FROM tmp_data OFFSET 1) t;

Ou mais simples com translate() :
INSERT INTO tbl
SELECT (('(' || translate(raw, E'10\t', 'tf,') || ')')::tbl).*
FROM   (SELECT raw FROM tmp_data OFFSET 1) t;

A string é convertida em um literal de linha, convertida para o tipo de linha da tabela recém-criada e decomposta com (row).* .

Tudo feito.

Você poderia colocar tudo isso em uma função plpgsql, mas precisaria se proteger contra injeção de SQL. (Existem várias soluções relacionadas aqui no SO. Tente pesquisar.

db<>fiddle aqui
Velha violino SQL