sql >> Databasteknik >  >> RDS >> PostgreSQL

FEL:extra data efter den senaste förväntade kolumnen vid användning av PostgreSQL COPY

Ett tomt bord duger inte. Du behöver en tabell som matchar strukturen för indata. Något i stil med:

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

Du behöver inte deklarera tab som DELIMITER eftersom det är standard:

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

800 kolumner säger du? Att många kolumner vanligtvis skulle indikera ett problem med din design. Hur som helst, det finns sätt att halvautomatisera CREATE TABLE manus.

Automatisering

Förutsatt förenklad rådata

1   2   3   4  -- first row contains "column names"
1   1   0   1  -- tab separated
1   0   0   1
1   0   1   1

Definiera en annan DELIMITER (en som inte förekommer i importdata alls), och importera till en temporär staging-tabell med en enda text kolumn:

CREATE TEMP TABLE tmp_data (raw text);

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

Den här frågan skapar CREATE TABLE skript:

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

En mer allmän och säkrare fråga:

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);

Returnerar:

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

Kör efter att ha verifierat giltigheten - eller kör dynamiskt om du litar på resultatet:

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

Sedan INSERT data med denna fråga:

INSERT INTO tbl
SELECT (('(' || replace(replace(replace(
                  raw
                , '1',   't')
                , '0',   'f')
                , E'\t', ',')
             || ')')::tbl).*
FROM   (SELECT raw FROM tmp_data OFFSET 1) t;

Eller enklare med translate() :

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

Strängen konverteras till en radliteral, cast till den nyskapade tabellradtypen och dekomponeras med (rad).* .

Allt klart.

Du kan lägga allt detta i en plpgsql-funktion, men du måste skydda dig mot SQL-injektion. (Det finns ett antal relaterade lösningar här på SO. Försök med en sökning.

db<>fiol här
Gammal SQL-fiol



  1. MySQL group_concat() sortering efter fallsatsvärden

  2. Hur kan jag lagra värdet av ett användarvalt alternativ från en rullgardinsvalslista i mysql-databasen?

  3. MySql-frågecacheinställningar

  4. MySQL UNION-klausul