当使用 read_csv
时,系统会尝试使用 CSV 嗅探器自动推断如何读取 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 嗅探器作为单独的步骤运行,该函数将检测到的 CSV 属性作为单行表返回。sniff_csv
函数接受一个可选的 sample_size
参数来配置采样的行数。
FROM sniff_csv('my_file.csv');
FROM sniff_csv('my_file.csv', sample_size = 1000);
列名 | 描述 | 示例 |
---|---|---|
分隔符 |
分隔符 | , |
引用 |
引用字符 | " |
转义 |
转义 | \ |
NewLineDelimiter |
换行分隔符 | \r\n |
注释 |
注释字符 | # |
SkipRows |
跳过的行数 | 1 |
HasHeader |
CSV 是否有标题 | true |
列 |
列类型编码为 LIST 形式的 STRUCT |
({'name': 'VARCHAR', 'age': 'BIGINT'}) |
DateFormat |
日期格式 | %d/%m/%Y |
TimestampFormat |
时间戳格式 | %Y-%m-%dT%H:%M:%S.%f |
UserArguments |
用于调用 sniff_csv 的参数 |
sample_size = 1000 |
提示 |
可用于读取 CSV 的准备好的提示 | FROM read_csv('my_file.csv', auto_detect=false, delim=',', ...) |
提示
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) 每行具有最多的列数。
以下格式被考虑用于自动格式检测。
参数 | 考虑的值 |
---|---|
分隔符 |
, | ; \t |
引用 |
" ' (空) |
转义 |
" ' \ (空) |
考虑示例文件 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
语句,将使用要复制到的表的类型。
类型检测通过尝试将每个列中的值转换为候选类型来工作。如果转换不成功,则从该列的候选类型集中移除该候选类型。处理完所有样本后,选择优先级最高的剩余候选类型。默认的候选类型集按优先级顺序列出如下:
类型 |
---|
BOOLEAN |
BIGINT |
DOUBLE |
TIME |
DATE |
TIMESTAMP |
VARCHAR |
所有内容都可以转换为 VARCHAR
,因此,此类型优先级最低,这意味着如果不能转换为其他任何类型,所有列都将转换为 VARCHAR
。在 flights.csv
中,FlightDate
列将转换为 DATE
,而其他列将转换为 VARCHAR
。
可以通过 auto_type_candidates
选项显式指定 CSV 读取器应考虑的候选类型集。
除了默认的候选类型集之外,使用 auto_type_candidates
选项还可以指定其他类型,包括:
类型 |
---|
DECIMAL |
FLOAT |
INTEGER |
SMALLINT |
TINYINT |
尽管可以自动检测的数据类型集可能看起来相当有限,但 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
)。
日期格式 |
---|
ISO 8601 |
%y-%m-%d |
%Y-%m-%d |
%d-%m-%y |
%d-%m-%Y |
%m-%d-%y |
%m-%d-%Y |
系统考虑以下时间戳格式(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 |