如何使用 psql \copy 元命令忽略错误

How to ignore errors with psql \copy meta-command

我正在使用 psql 和 PostgreSQL 数据库以及以下 copy 命令:

\COPY isa (np1, np2, sentence) FROM 'c:\Downloads\isa.txt' WITH DELIMITER '|'

我得到:

ERROR:  extra data after last expected column

如何跳过有错误的行?

如果不跳过包括 Postgres 14 在内的整个命令,您将无法跳过错误。目前没有更复杂的错误处理。

\copy is just a wrapper around SQL COPY 通过 psql 传递结果。 COPY 的手册:

COPY stops operation at the first error. This should not lead to problems in the event of a COPY TO, but the target table will already have received earlier rows in a COPY FROM. These rows will not be visible or accessible, but they still occupy disk space. This might amount to a considerable amount of wasted disk space if the failure happened well into a large copy operation. You might wish to invoke VACUUM to recover the wasted space.

大胆强调我的。并且:

COPY FROM will raise an error if any line of the input file contains more or fewer columns than are expected.

COPY 是一种极其快速的导入/导出数据的方法。复杂的检查和错误处理会减慢速度。

有一个 attempt to add error logging to COPY in Postgres 9.0 但从未提交。

解决方案

改为修复您的输入文件。

如果您的输入文件中有一个或多个额外的列并且该文件在其他方面是一致的,您可以将虚拟列添加到您的tableisa 然后放下那些。或者(生产 tables 更清洁)导入到临时暂存 table 和 INSERT 选定的列(或表达式)到目标 table isa 从那里。

相关答案及详细说明:

  • How to update selected rows with values from a CSV file in Postgres?
  • COPY command: copy only specific columns from csv

遗憾的是 25 年来 Postgres 没有 -ignore-errors 标志或 COPY 命令的选项。在这个大数据时代,你会得到很多脏记录,修复每个离群值对项目来说成本非常高。

我不得不以这种方式解决问题:

  1. 复制原来的table并命名为dummy_original_table
  2. 在原来的table中,创建一个这样的触发器:
    CREATE OR REPLACE FUNCTION on_insert_in_original_table() RETURNS trigger AS  $$  
    DECLARE
        v_rec   RECORD;
    BEGIN
        -- we use the trigger to prevent 'duplicate index' error by returning NULL on duplicates
        SELECT * FROM original_table WHERE primary_key=NEW.primary_key INTO v_rec;
        IF v_rec IS NOT NULL THEN
            RETURN NULL;
        END IF; 
        BEGIN 
            INSERT INTO original_table(datum,primary_key) VALUES(NEW.datum,NEW.primary_key)
                ON CONFLICT DO NOTHING;
        EXCEPTION
            WHEN OTHERS THEN
                NULL;
        END;
        RETURN NULL;
    END;
  1. 运行复制一份到虚拟table。那里不会插入任何记录,但所有记录都将插入 original_table

psql dbname -c \copy dummy_original_table(datum,primary_key) FROM '/home/user/data.csv' delimiter E'\t'

这是一个解决方案 -- 一次导入一行批处理文件。性能可能会慢得多,但对于您的场景来说可能已经足够了:

#!/bin/bash

input_file=./my_input.csv
tmp_file=/tmp/one-line.csv
cat $input_file | while read input_line; do
    echo "$input_line" > $tmp_file
    psql my_database \
     -c "\
     COPY my_table \
     FROM `$tmp_file` \
     DELIMITER '|'\
     CSV;\
    "
done

此外,您可以修改脚本以捕获 psql stdout/stderr 并退出 状态,如果退出状态为非零,则回显 $input_line 并将捕获的 stdout/stderr 到标准输入 and/or 将其附加到文件中。

解决方法:再次使用 sed 和 运行 \copy 删除报告的错误行

Postgres以后的版本(包括Postgres 13),会报错的行号。然后,您可以使用 sed 和 运行 \copy 再次删除该行,例如,

#!/bin/bash
bad_line_number=5  # assuming line 5 is the bad line
sed ${bad_line_number}d < input.csv > filtered.csv

[根据 ]