CSV 自动检测
使用 read_csv 时,系统会借助 CSV sniffer 自动推断应如何读取 CSV 文件。
这一步是必要的,因为 CSV 文件并非自描述格式,而且存在许多不同“方言(dialect)”。自动检测大致包含以下步骤:
- 检测 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 sniffer。该函数会以单行表的形式返回检测到的 CSV 属性。
sniff_csv 还支持可选参数 sample_size,用于配置采样行数。
FROM sniff_csv('my_file.csv');
FROM sniff_csv('my_file.csv', sample_size = 1000);
| 列名 | 说明 | 示例 |
|---|---|---|
Delimiter | 分隔符 | , |
Quote | 引号字符 | " |
Escape | 转义字符 | \ |
NewLineDelimiter | 换行分隔符 | \r\n |
Comment | 注释字符 | # |
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 命令,内含 sniffer 检测到的配置。
-- 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={...});
检测步骤
方言检测
方言检测会尝试使用候选值集合去解析样本。最终选出的方言应同时满足:
- 每行列数一致;
- 每行列数尽可能多。
自动方言检测会考虑以下参数组合。
| 参数 | 候选值 |
|---|---|
delim | , ` |
quote | " ' (empty) |
escape | " ' \ (empty) |
考虑示例文件 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列。
在本例中,系统会选择 | 作为分隔符。因为所有行列数一致,且每行列数大于 1,说明该分隔符确实存在于 CSV 文件中。
类型检测
完成方言检测后,系统会尝试推断每一列的类型。注意,这一步只在调用 read_csv 时执行;对于 COPY 语句,则会直接使用目标表的列类型。
类型检测通过将每列值尝试转换到候选类型来完成。若转换失败,该候选类型会从该列的候选集合中移除。处理完全部样本后,会选择剩余候选类型中优先级最高的一个。默认候选类型(按优先级从高到低)如下:
| Types |
|---|
| NULL |
| BOOLEAN |
| TIME |
| DATE |
| TIMESTAMP |
| TIMESTAMPTZ |
| BIGINT |
| DOUBLE |
| VARCHAR |
所有值都可以转换为 VARCHAR,因此它优先级最低:当列无法转换为其他类型时,会回退到 VARCHAR。
在 flights.csv 中,FlightDate 会被识别为 DATE,其他列会被识别为 VARCHAR。
你可以通过 auto_type_candidates 显式指定 CSV 读取器应考虑的候选类型集合。作为回退类型的 VARCHAR 无论是否显式指定都会被纳入候选。
下面是可通过 auto_type_candidates 指定的额外候选类型(按优先级):
| Types |
|---|
| TINYINT |
| SMALLINT |
| INTEGER |
| DECIMAL |
| FLOAT |
虽然可自动检测的数据类型看起来有限,但你可以使用下一节介绍的 types 选项将 CSV 读取器配置为读取任意复杂类型。
你可以通过 all_varchar 选项完全禁用类型检测。启用后,所有列都会保持为 VARCHAR(即 CSV 原始读入形式)。
注意,字段是否带引号(如 "42" 与 42)不会影响类型检测。
带引号字段并不会被强制转成 VARCHAR;sniffer 仍会尝试选择优先级最高的可行类型。
覆盖类型检测结果
可通过 types 选项逐列覆盖检测出的类型。该选项支持两种形式:
- 类型定义列表(如
types = ['INTEGER', 'VARCHAR', 'DATE']):按列在 CSV 中出现顺序覆盖类型。 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 来覆盖该行为。
日期与时间戳
Goose 默认使用 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 |