⌘+k ctrl+k
1.1.3 (stable)
Search Shortcut cmd + k | ctrl + k
Reading Faulty CSV Files

CSV文件可能有各种形状和形式,其中一些文件可能包含许多错误,这使得干净地读取它们的过程变得非常困难。为了帮助用户读取这些文件,DuckDB支持详细的错误消息、跳过错误行的能力,以及将错误行存储在临时表中以帮助用户进行数据清理步骤的可能性。

结构错误

DuckDB 支持检测并跳过多种不同的结构错误。在本节中,我们将通过示例逐一介绍每种错误。 对于这些示例,请考虑以下表格:

CREATE TABLE people (name VARCHAR, birth_date DATE);

DuckDB 检测到以下错误类型:

  • CAST: 当CSV文件中的列无法转换为预期的模式值时,会发生类型转换错误。例如,行 Pedro,The 90s 会导致错误,因为字符串 The 90s 无法转换为日期。
  • MISSING COLUMNS: 如果CSV文件中的一行比预期的列数少,就会发生此错误。在我们的示例中,我们期望有两列;因此,只有一行的值,例如Pedro,将会导致此错误。
  • TOO MANY COLUMNS: 如果CSV中的一行比预期的列数多,就会发生此错误。在我们的示例中,任何超过两列的行都会导致此错误,例如 Pedro,01-01-1992,pdet
  • UNQUOTED VALUE: CSV 行中的引用值必须在最后取消引用;如果引用值始终保持引用状态,将会导致错误。例如,假设我们的扫描器使用 quote='"',行 "pedro"holanda, 01-01-1992 将会出现未引用值错误。
  • LINE SIZE OVER MAXIMUM: DuckDB 有一个参数,用于设置 CSV 文件的最大行大小,默认设置为 2,097,152 字节。假设我们的扫描器设置为 max_line_size = 25,那么行 Pedro Holanda, 01-01-1992 将产生错误,因为它超过了 25 字节。
  • INVALID UNICODE: DuckDB 仅支持 UTF-8 字符串;因此,包含非 UTF-8 字符的行将产生错误。例如,行 pedro\xff\xff, 01-01-1992 将会出现问题。

CSV错误解析

默认情况下,在执行CSV读取时,如果遇到任何结构错误,扫描器将立即停止扫描过程并将错误抛给用户。 这些错误旨在提供尽可能多的信息,以允许用户直接在其CSV文件中评估它们。

这是一个完整错误消息的示例:

Conversion Error: CSV Error on Line: 5648
Original Line: Pedro,The 90s
Error when converting column "birth_date". date field value out of range: "The 90s", expected format is (DD-MM-YYYY)

Column date is being converted as type DATE
This type was auto-detected from the CSV file.
Possible solutions:
* Override the type for this column manually by setting the type explicitly, e.g. types={'birth_date': 'VARCHAR'}
* Set the sample size to a larger value to enable the auto-detection to scan more values, e.g. sample_size=-1
* Use a COPY statement to automatically derive types from an existing table.

  file= people.csv
  delimiter = , (Auto-Detected)
  quote = " (Auto-Detected)
  escape = " (Auto-Detected)
  new_line = \r\n (Auto-Detected)
  header = true (Auto-Detected)
  skip_rows = 0 (Auto-Detected)
  date_format = (DD-MM-YYYY) (Auto-Detected)
  timestamp_format =  (Auto-Detected)
  null_padding=0
  sample_size=20480
  ignore_errors=false
  all_varchar=0

第一个块为我们提供了有关错误发生位置的信息,包括行号、原始CSV行以及哪个字段有问题:

Conversion Error: CSV Error on Line: 5648
Original Line: Pedro,The 90s
Error when converting column "birth_date". date field value out of range: "The 90s", expected format is (DD-MM-YYYY)

第二个块为我们提供了潜在的解决方案:

Column date is being converted as type DATE
This type was auto-detected from the CSV file.
Possible solutions:
* Override the type for this column manually by setting the type explicitly, e.g. types={'birth_date': 'VARCHAR'}
* Set the sample size to a larger value to enable the auto-detection to scan more values, e.g. sample_size=-1
* Use a COPY statement to automatically derive types from an existing table.

由于此字段的类型是自动检测的,建议将该字段定义为VARCHAR或充分利用数据集进行类型检测。

最后,最后一个块展示了扫描仪中使用的一些可能导致错误的选项,指示它们是自动检测的还是由用户手动设置的。

使用 ignore_errors 选项

在某些情况下,CSV文件可能包含多个结构错误,而用户只希望跳过这些错误并读取正确的数据。通过使用ignore_errors选项,可以读取有错误的CSV文件。设置此选项后,包含会导致CSV解析器生成错误的数据的行将被忽略。在我们的示例中,我们将演示一个CAST错误,但请注意,任何在我们结构错误部分中描述的错误都会导致跳过有问题的行。

例如,考虑以下CSV文件,faulty.csv

Pedro,31
Oogie Boogie, three

如果您读取CSV文件,指定第一列为VARCHAR,第二列为INTEGER,加载文件将会失败,因为字符串three无法转换为INTEGER

例如,以下查询将抛出类型转换错误。

FROM read_csv('faulty.csv', columns = {'name': 'VARCHAR', 'age': 'INTEGER'});

然而,当设置了ignore_errors时,文件的第二行将被跳过,仅输出完整的第一行。例如:

FROM read_csv(
    'faulty.csv',
    columns = {'name': 'VARCHAR', 'age': 'INTEGER'},
    ignore_errors = true
);

输出:

姓名 年龄
佩德罗 31

需要注意的是,CSV解析器受到投影下推优化的影响。因此,如果我们只选择名称列,两行都将被视为有效,因为年龄上的类型转换错误将永远不会发生。例如:

SELECT name
FROM read_csv('faulty.csv', columns = {'name': 'VARCHAR', 'age': 'INTEGER'});

输出:

name
佩德罗
乌吉·布吉

检索错误的CSV行

能够读取有问题的CSV文件很重要,但对于许多数据清理操作来说,还需要确切知道哪些行损坏以及解析器在它们上发现了哪些错误。对于这些场景,可以使用DuckDB的CSV拒绝表功能。 默认情况下,此功能会创建两个临时表。

  1. reject_scans: 存储有关CSV扫描器参数的信息
  2. reject_errors: 存储有关每个CSV错误行的信息以及它们发生在哪个CSV扫描器中。

请注意,我们结构错误部分中描述的任何错误都将存储在拒绝表中。此外,如果一行有多个错误,将为同一行存储多个条目,每个错误一个条目。

拒绝扫描

CSV拒绝扫描表返回以下信息:

列名 描述 类型
scan_id DuckDB 中用于表示该扫描器的内部 ID UBIGINT
file_id 扫描器可能会扫描多个文件,因此file_id表示扫描器中的唯一文件 UBIGINT
file_path 文件路径 VARCHAR
delimiter 使用的分隔符,例如; VARCHAR
quote 使用的引号,例如," VARCHAR
escape 使用的引号,例如," VARCHAR
newline_delimiter 使用的新行分隔符,例如 \r\n VARCHAR
skip_rows 如果从文件顶部跳过了任何行 UINTEGER
has_header 如果文件有标题 BOOLEAN
columns 文件的模式(即所有列名和类型) VARCHAR
date_format 用于日期类型的格式 VARCHAR
timestamp_format 用于时间戳类型的格式 VARCHAR
user_arguments 用户手动设置的任何额外扫描器参数 VARCHAR

拒绝错误

CSV拒绝错误表返回以下信息:

Column name Description Type
scan_id DuckDB 中用于表示该扫描器的内部 ID,用于与拒绝扫描表进行连接 UBIGINT
file_id file_id 表示扫描仪中的唯一文件,用于与拒绝扫描表进行连接 UBIGINT
line 行号,来自CSV文件,表示错误发生的位置。 UBIGINT
line_byte_position 发生错误的行的起始字节位置。 UBIGINT
byte_position 发生错误的字节位置。 UBIGINT
column_idx 如果错误发生在特定列中,则为该列的索引。 UBIGINT
column_name 如果错误发生在特定列中,则为列的名称。 VARCHAR
error_type 发生的错误类型。 ENUM
csv_line 原始的CSV行。 VARCHAR
error_message DuckDB 产生的错误信息。 VARCHAR

Parameters

下面列出的参数用于read_csv函数中,以配置CSV拒绝表。

Name Description Type Default
store_rejects 如果设置为true,文件中的任何错误将被跳过并存储在默认的拒绝临时表中。 BOOLEAN False
rejects_scan 存储错误CSV文件扫描信息的临时表的名称。 VARCHAR reject_scans
rejects_table 存储CSV文件中错误行信息的临时表的名称。 VARCHAR reject_errors
rejects_limit CSV文件中将被记录在拒绝表中的错误记录数量的上限。当不应应用限制时,使用0。 BIGINT 0

要将错误的CSV行信息存储在拒绝表中,用户只需将store_rejects选项设置为true。例如:

FROM read_csv(
    'faulty.csv',
    columns = {'name': 'VARCHAR', 'age': 'INTEGER'},
    store_rejects = true
);

然后你可以查询reject_scansreject_errors表,以检索有关被拒绝的元组的信息。例如:

FROM reject_scans;

输出:

扫描ID 文件ID 文件路径 分隔符 引用符 转义符 换行符 跳过行数 是否有标题 日期格式 时间戳格式 用户参数
5 0 faulty.csv , " " \n 0 false {'name': 'VARCHAR','age': 'INTEGER'}     store_rejects=true
FROM reject_errors;

输出:

扫描ID 文件ID 行字节位置 字节位置 列索引 列名 错误类型 CSV行 错误信息
5 0 2 10 23 2 age CAST Oogie Boogie, three 转换列“age”时出错。无法将字符串“three”转换为'INTEGER'