【问题标题】:"ERROR: extra data after last expected column" when using PostgreSQL COPY使用 PostgreSQL COPY 时出现“错误:最后一个预期列之后的额外数据”
【发布时间】:2013-04-28 08:37:04
【问题描述】:

请多多包涵,这是我的第一篇文章。

我正在尝试在 PostgreSQL-9.2 中运行 COPY 命令以将制表符分隔表从 .txt 文件添加到 PostgreSQL 数据库,例如:

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

我已经使用 SQL 命令在数据库中创建了一个名为“raw_data”的空表:

CREATE TABLE raw_data ();

我在尝试运行COPY 命令时不断收到以下错误消息:

ERROR:  extra data after last expected column
CONTEXT:  COPY raw_data, line 1: "  1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  ..."

(这里的数字应该是列标题)

我不确定是不是因为我在创建 db 表时没有指定表列,但我试图避免手动输入 800 或列。

关于如何解决此问题的任何建议?

以下是 .txt 文件的示例:

        1   2   3   4   5   6   7   8   9
binary1 1   1   0   1   1   1   1   1   1
binary2 1   0   0   1   0   1   1   0   0
binary3 1   0   1   1   1   0   0   1   0
binary4 1   1   1   1   0   1   0   1   0

【问题讨论】:

  • +1,顺便说一句。欢迎来到 Stackoverflow,第一篇文章非常好。

标签: postgresql database-design dynamic-sql sqlbulkcopy


【解决方案1】:

空桌子不行。您需要与输入数据结构匹配的表。比如:

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

您无需将tab 声明为DELIMITER,因为这是默认设置:

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

你说800列?这么多列通常表明您的设计存在问题。无论如何,有一些方法可以使 CREATE TABLE 脚本半自动化。

自动化

假设简化的原始数据

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

定义一个不同的DELIMITER(根本不会出现在导入数据中),并导入到具有单个text 列的临时登台表:

CREATE TEMP TABLE tmp_data (raw text);

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

此查询创建CREATE TABLE 脚本:

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

更通用和更安全的查询:

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

返回:

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

验证有效性后执行 - 如果您信任结果,则动态执行:

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

然后INSERT这个查询的数据:

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

或者更简单的translate():

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

字符串转换为行文字,转换为新创建的表行类型并用(row).*分解。

全部完成。

您可以将所有这些都放入一个 plpgsql 函数中,但您需要防范 SQL 注入。 (SO上有很多相关的解决方案。尝试搜索一下。

db小提琴here
Old SQL Fiddle

【讨论】:

  • 感谢您的快速回复。是的,数据表实际上是一个 800x1000 制表符分隔的文件。我已经查看了您提到的两种方法,但是我希望能够在将表上传到数据库时选择特定列,而不必将行解析到某个表中然后选择它们,所以也许是一种半自动化的方法必须考虑生成列。
  • @dnak:我添加了一个完整的解决方案。
  • 在 postgres9.2 上使用复制到 tmp_data 我得到 ERROR: COPY delimiter must be a single-byte character...
  • @HugoKoopmans: one-byte character 表示您不能使用多字节字符。检查octet_length()http://sqlfiddle.com/#!15/d41d8/2406
  • @Erwin 那么为什么你的例子在我上面描述的 pg 9.2 中失败了?
【解决方案2】:

您可以直接从复制命令创建表,查看 COPY 中的 HEADER 选项,例如: COPY FROM '/path/to/csv/SourceCSVFile.csv' DELIMITERS ',' CSV HEADER

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多