使用 COPY FROM 时跳过出现类型转换错误的行

John Doe 十二月 20, 2024

摘要:在本文中,我们将会学习,在 PostgreSQL 中使用 COPY FROM 时,如何跳过出现类型转换错误的行。

目录

介绍

PostgreSQL 17 发布了一项很棒的特性。你可能已经知道 COPY 了。每当你想在 PostgreSQL 中导入或导出某些内容时,它就是要使用到的工具。在导入到 PostgreSQL 时,直到 PostgreSQL 16 都需要考虑一些事情,这可能非常烦人:每当 COPY 时有数据行无法写入到目标表时,整个过程会被中止,并且根本不会导入任何内容。也许这是你想要的,但对于其他场景,首选行为是忽略这些行,并将错误消息输出到其他位置。在这两种情况下,您都不会丢失其余数据,并且在修复要导入的数据后不需要从头开始。

示例

像往常一样,让我们从一个小的测试用例开始:假设我们有这样一个文本文件:

$ cat data.txt
1 aaaa 1 aaaa
2 bbbb 2 bbbb
3 cccc 3 cccc
4 dddd 4 dddd
5 eeee 5 eeee
6 ffff 6 ffff
7 gggg 7 gggg
8 hhhh 8 hhhh

可以保存这些数据的表,可能如下所示:

CREATE TABLE import (a int, b varchar(4), c int, d varchar(4));
postgres=# \d import
                     Table "public.import"
 Column |         Type         | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
 a      | integer              |           |          |
 b      | character varying(4) |           |          |
 c      | integer              |           |          |
 d      | character varying(4) |           |          |

要将这些数据从文本文件导入到表中,您需要做的就是:

COPY import FROM '/home/postgres/data.txt' with (delimiter ' ');

SELECT * FROM import;
 a |  b   | c |  d
---+------+---+------
 1 | aaaa | 1 | aaaa
 2 | bbbb | 2 | bbbb
 3 | cccc | 3 | cccc
 4 | dddd | 4 | dddd
 5 | eeee | 5 | eeee
 6 | ffff | 6 | ffff
 7 | gggg | 7 | gggg
 8 | hhhh | 8 | hhhh
(8 rows)

这很简单。如果要导入的文件确实如下面这样,则可能会出现问题:

TRUNCATE import;
$ cat data.txt2
1 aaaa 1 aaaa
2 bbbb 2 bbbb
3 cccc 3 cccc
4 dddd 4 dddd
5 eeee 5 eeee
6 ffff 6 ffff
7 gggg 7 gggg
8 hhhh 8 hhhh
9 jjjjjjjjj 9 jjjj
10 kkkk 10 kkkk
11 llll 11 lllllll
12 mmmm 12 mmmm
13 nnnn 13 nnnn

第 9 行和第 11 行肯定不能放入表中,当您尝试导入它时,整个过程会失败,并且根本不会导入任何内容:

COPY import FROM '/home/postgres/data.txt2' with (delimiter ' ');
ERROR:  value too long for type character varying(4)
CONTEXT:  COPY import, line 9, column b: "jjjjjjjjj"

SELECT * FROM import;
 a | b | c | d
---+---+---+---
(0 rows)

从 PostgreSQL 17 开始,您将有另一个选择:

postgres=# \h copy
Command:     COPY
Description: copy data between a file and a table
Syntax:
COPY table_name [ ( column_name [, ...] ) ]
    FROM { 'filename' | PROGRAM 'command' | STDIN }
    [ [ WITH ] ( option [, ...] ) ]
    [ WHERE condition ]

COPY { table_name [ ( column_name [, ...] ) ] | ( query ) }
    TO { 'filename' | PROGRAM 'command' | STDOUT }
    [ [ WITH ] ( option [, ...] ) ]

where option can be one of:

    FORMAT format_name
    FREEZE [ boolean ]
    DELIMITER 'delimiter_character'
    NULL 'null_string'
    DEFAULT 'default_string'
    HEADER [ boolean | MATCH ]
    QUOTE 'quote_character'
    ESCAPE 'escape_character'
    FORCE_QUOTE { ( column_name [, ...] ) | * }
    FORCE_NOT_NULL { ( column_name [, ...] ) | * }
    FORCE_NULL { ( column_name [, ...] ) | * }
    ON_ERROR error_action
    ENCODING 'encoding_name'
    LOG_VERBOSITY verbosity

使用选项 “ON_ERROR”,你可以要求 PostgreSQL 忽略任何不能放入目标表的数据:

COPY import FROM '/home/postgres/data.txt2' with (delimiter ' ', ON_ERROR ignore);

SELECT * FROM import;
 a  |  b   | c  |  d
----+------+----+------
  1 | aaaa |  1 | aaaa
  2 | bbbb |  2 | bbbb
  3 | cccc |  3 | cccc
  4 | dddd |  4 | dddd
  5 | eeee |  5 | eeee
  6 | ffff |  6 | ffff
  7 | gggg |  7 | gggg
  8 | hhhh |  8 | hhhh
 10 | kkkk | 10 | kkkk
 12 | mmmm | 12 | mmmm
 13 | nnnn | 13 | nnnn
(11 rows)

除了第 9 行和第 11 行,数据都在表里了。现在,您只能忽略错误,或者在第一个错误的地方停止(这是默认值,和以前一样)。