sql >> Base de Datos >  >> RDS >> PostgreSQL

ERROR:datos adicionales después de la última columna esperada cuando se usa COPY de PostgreSQL

Una mesa vacía no servirá. Necesita una tabla que coincida con la estructura de los datos de entrada. Algo como:

CREATE TABLE raw_data (
  col1 int
, col2 int
  ...
);

No es necesario declarar tab como DELIMITER ya que ese es el valor predeterminado:

COPY raw_data FROM '/home/Projects/TestData/raw_data.txt';

800 columnas dices? Esa cantidad de columnas normalmente indicaría un problema con su diseño. De todos modos, hay formas de automatizar a medias el CREATE TABLE guión.

Automatización

Suponiendo datos sin procesar 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 un DELIMITER diferente (uno que no ocurre en absoluto en los datos de importación) e importar a una tabla de preparación temporal con un solo text columna:

CREATE TEMP TABLE tmp_data (raw text);

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

Esta consulta crea el CREATE TABLE guión:

SELECT 'CREATE TABLE tbl (col' || replace (raw, E'\t', ' bool, col') || ' bool)'
FROM   (SELECT raw FROM tmp_data LIMIT 1) t;

Una consulta más genérica y 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);

Devoluciones:

CREATE TABLE tbl (col1 bool, col2 bool, col3 bool, col4 bool);

Ejecutar después de verificar la validez, o ejecutar dinámicamente si confía en el resultado:

DO
$$BEGIN
EXECUTE (
   SELECT 'CREATE TABLE tbl (col' || replace(raw, ' ', ' bool, col') || ' bool)'
   FROM  (SELECT raw FROM tmp_data LIMIT 1) t
   );
END$$;

Luego INSERT los datos con 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;

O más simple con translate() :

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

La cadena se convierte en un literal de fila, se convierte en el tipo de fila de la tabla recién creada y se descompone con (row).* .

Todo listo.

Podría poner todo eso en una función plpgsql, pero necesitaría protegerse contra la inyección de SQL. (Hay varias soluciones relacionadas aquí en SO. Intente realizar una búsqueda.

db<>fiddle aquí
Viejo SQL Fiddle