当使用 read_csv 时,系统会尝试使用 CSV 嗅探器 (CSV sniffer) 自动推断如何读取 CSV 文件。这一步是必要的,因为 CSV 文件不具备自我描述性,且有许多不同的方言 (dialects)。自动检测的大致工作流程如下:
- 检测 CSV 文件的方言(分隔符、引用规则、转义符)。
- 检测每一列的数据类型。
- 检测文件是否包含标题行。
默认情况下,系统会尝试自动检测所有选项。但是,用户可以手动覆盖这些选项。这在系统出错时非常有用。例如,如果分隔符选择错误,我们可以通过在调用 read_csv 时显式指定分隔符来覆盖它(例如 read_csv('file.csv', delim = '|'))。
采样大小
类型检测通过对文件进行采样来工作。可以通过设置 sample_size 参数来修改采样大小。默认采样大小为 20,480 行。将 sample_size 参数设置为 -1 意味着读取整个文件进行采样。
SELECT * FROM read_csv('my_csv_file.csv', sample_size = -1);
采样执行的方式取决于文件的类型。如果我们要读取磁盘上的常规文件,我们会跳转到文件中并尝试从文件的不同位置进行采样。如果我们读取的是无法跳转的文件(例如 .gz 压缩的 CSV 文件或 stdin),则仅从文件开头进行采样。
sniff_csv 函数
可以使用 sniff_csv(filename) 函数将 CSV 嗅探作为一个单独的步骤运行,该函数返回一个单行表,其中包含检测到的 CSV 属性。sniff_csv 函数接受一个可选的 sample_size 参数来配置采样行数。
FROM sniff_csv('my_file.csv');
FROM sniff_csv('my_file.csv', sample_size = 1000);
| 列名 | 描述 | 示例 |
|---|---|---|
Delimiter (分隔符) |
Delimiter (分隔符) | , |
Quote (引用符) |
引用字符 | " |
Escape (转义符) |
Escape (转义符) | \ |
NewLineDelimiter (换行符) |
换行符 | \r\n |
注释 |
注释字符 | # |
SkipRows (跳过行数) |
跳过的行数 | 1 |
HasHeader (是否有标题) |
CSV 是否有标题 | true |
Columns (列) |
列类型编码为 STRUCT 的 LIST |
({'name': 'VARCHAR', 'age': 'BIGINT'}) |
DateFormat (日期格式) |
日期格式 | %d/%m/%Y |
TimestampFormat (时间戳格式) |
时间戳格式 | %Y-%m-%dT%H:%M:%S.%f |
UserArguments (用户参数) |
调用 sniff_csv 时使用的参数 |
sample_size = 1000 |
Prompt (提示) |
准备好用于读取 CSV 的提示 | FROM read_csv('my_file.csv', auto_detect=false, delim=',', ...) |
Prompt (提示)
Prompt 列包含一个带有嗅探器检测到的配置的 SQL 命令。
-- use line mode in CLI to get the full command
.mode line
SELECT Prompt FROM sniff_csv('my_file.csv');
Prompt = FROM read_csv('my_file.csv', auto_detect=false, delim=',', quote='"', escape='"', new_line='\n', skip=0, header=true, columns={...});
检测步骤
方言检测
方言检测通过尝试使用一组考虑的值解析样本来工作。检测到的方言是满足以下条件的方言:(1) 每行的列数一致,并且 (2) 每行的列数最多。
自动方言检测考虑以下方言。
| 参数 | 考虑的值 |
|---|---|
delim |
, | ; \t |
quote |
" ' (空) |
escape |
" ' \ (空) |
考虑示例文件 flights.csv
FlightDate|UniqueCarrier|OriginCityName|DestCityName
1988-01-01|AA|New York, NY|Los Angeles, CA
1988-01-02|AA|New York, NY|Los Angeles, CA
1988-01-03|AA|New York, NY|Los Angeles, CA
在此文件中,方言检测工作如下:
- 如果我们按
|分割,每一行被分割成4列。 - 如果我们按
,分割,第 2-4 行被分割成3列,而第一行被分割成1列。 - 如果我们按
;分割,每一行被分割成1列。 - 如果我们按
\t分割,每一行被分割成1列。
在此示例中,系统选择 | 作为分隔符。所有行都被分割成相同数量的列,并且每行有多于一列,这意味着分隔符确实存在于 CSV 文件中。
类型检测
检测到方言后,系统将尝试确定每一列的类型。请注意,此步骤仅在调用 read_csv 时执行。如果是 COPY 语句,则使用我们要复制到的表的类型。
类型检测通过尝试将每一列中的值转换为候选类型来工作。如果转换不成功,则该候选类型将从该列的候选类型集中移除。处理完所有样本后,选择优先级最高的剩余候选类型。默认候选类型集如下,按优先级排序:
| 类型 |
|---|
| NULL |
| BOOLEAN |
| TIME |
| DATE |
| TIMESTAMP |
| TIMESTAMPTZ |
| BIGINT |
| DOUBLE |
| VARCHAR |
所有内容都可以转换为 VARCHAR,因此,此类型的优先级最低,这意味着如果无法转换为其他任何类型,所有列都将作为后备转换为 VARCHAR。在 flights.csv 中,FlightDate 列将被转换为 DATE,而其他列将被转换为 VARCHAR。
可以使用 auto_type_candidates 选项显式指定 CSV 读取器应考虑的候选类型集。无论您是否指定,VARCHAR 作为后备类型都将始终作为候选类型进行考虑。
以下是可以使用 auto_type_candidates 选项指定的所有附加候选类型,按优先级排序:
| 类型 |
|---|
| TINYINT |
| SMALLINT |
| INTEGER |
| DECIMAL |
| FLOAT |
即使可以自动检测的数据类型集看起来相当有限,CSV 读取器也可以配置为通过使用下一节中描述的 types 选项来读取任意复杂的类型。
可以通过使用 all_varchar 选项完全禁用类型检测。如果设置此选项,所有列都将保持为 VARCHAR(正如它们在 CSV 文件中原始出现的那样)。
请注意,使用引号字符与不使用引号字符(例如 "42" 和 42)对类型检测没有影响。带引号的字段不会被转换为 VARCHAR,嗅探器会尝试查找优先级最高的类型候选者。
覆盖类型检测
检测到的类型可以使用 types 选项单独覆盖。此选项接受以下两个选项之一:
- 类型定义列表(例如
types = ['INTEGER', 'VARCHAR', 'DATE'])。这会按照 CSV 文件中出现的顺序覆盖列的类型。 - 或者,
types接受一个name→type映射,它覆盖单个列的选项(例如types = {'quarter': 'INTEGER'})。
可以使用 types 选项指定的列类型集并不像 auto_type_candidates 选项可用的类型那样有限:任何有效的类型定义对于 types 选项都是可接受的。(要获取有效的类型定义,请使用 typeof() 函数,或使用 DESCRIBE 结果的 column_type 列。)
sniff_csv() 函数的 Column 字段返回一个包含列名和类型的结构体,可用作覆盖类型的基础。
标题检测
标题检测通过检查候选标题行在类型方面是否偏离文件中的其他行来工作。例如,在 flights.csv 中,我们可以看到标题行仅由 VARCHAR 列组成,而值在 FlightDate 列中包含一个 DATE 值。因此,系统将第一行定义为标题行,并从标题行中提取列名。
在没有标题行的文件中,列名生成为 column0, column1 等。
请注意,如果所有列的类型都是 VARCHAR,则无法正确检测到标题,因为在这种情况下,系统无法区分标题行和文件中的其他行。在这种情况下,系统假设文件有标题。这可以通过将 header 选项设置为 false 来覆盖。
日期和时间戳
DuckDB 默认支持 ISO 8601 格式用于时间戳、日期和时间。遗憾的是,并非所有日期和时间都使用此标准格式化。因此,CSV 读取器还支持 dateformat 和 timestampformat 选项。使用此格式,用户可以指定一个 格式字符串,该字符串指定应如何读取日期或时间戳。
作为自动检测的一部分,系统会尝试找出日期和时间是否以不同的表示形式存储。这并不总是可能的,因为表示中存在歧义。例如,日期 01-02-2000 可以解析为 1 月 2 日或 2 月 1 日。通常这些歧义可以解决。例如,如果我们随后遇到日期 21-02-2000,那么我们知道格式必须是 DD-MM-YYYY。MM-DD-YYYY 不再可能,因为没有第 21 个月。
如果通过查看数据无法解决歧义,系统会有一个关于使用哪种日期格式的偏好列表。如果系统选择错误,用户可以手动指定 dateformat 和 timestampformat 选项。
系统考虑以下日期格式(dateformat)。在存在歧义的情况下,较高的条目优先于较低的条目(即 ISO 8601 优于 MM-DD-YYYY)。
| dateformat |
|---|
| ISO 8601 |
| %y-%m-%d |
| %Y-%m-%d |
| %d-%m-%y |
| %d-%m-%Y |
| %m-%d-%y |
| %m-%d-%Y |
系统考虑以下时间戳格式(timestampformat)。在存在歧义的情况下,较高的条目优先于较低的条目。
| timestampformat |
|---|
| ISO 8601 |
| %y-%m-%d %H:%M:%S |
| %Y-%m-%d %H:%M:%S |
| %d-%m-%y %H:%M:%S |
| %d-%m-%Y %H:%M:%S |
| %m-%d-%y %I:%M:%S %p |
| %m-%d-%Y %I:%M:%S %p |
| %Y-%m-%d %H:%M:%S.%f |