当使用read_csv
时,系统会尝试自动推断如何使用CSV sniffer读取CSV文件。
这一步是必要的,因为CSV文件不是自描述的,并且有许多不同的方言。自动检测大致如下:
- 检测CSV文件的方言(分隔符、引用规则、转义符)
- 检测每一列的类型
- 检测文件是否有标题行
默认情况下,系统会尝试自动检测所有选项。然而,用户可以单独覆盖这些选项。这在系统出错时非常有用。例如,如果分隔符选择不正确,我们可以通过调用read_csv
并明确指定分隔符来覆盖它(例如,read_csv('file.csv', delim = '|')
)。
检测工作通过对文件样本进行操作来完成。样本的大小可以通过设置sample_size
参数来修改。默认的样本大小是20480
行。将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 |
分隔符 | , |
Quote |
引用字符 | " |
Escape |
转义 | \ |
NewLineDelimiter |
换行分隔符 | \r\n |
Comment |
注释字符 | # |
SkipRows |
跳过的行数 | 1 |
HasHeader |
CSV是否有标题 | true |
Columns |
列类型编码为LIST 的STRUCT s |
({'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
语句的情况下,将使用我们复制到的表的类型。
类型检测的工作原理是尝试将每列中的值转换为候选类型。如果转换不成功,则从该列的候选类型集中移除该候选类型。处理完所有样本后,选择优先级最高的剩余候选类型。默认的候选类型集如下,按优先级顺序排列:
类型 |
---|
BOOLEAN |
BIGINT |
DOUBLE |
TIME |
DATE |
TIMESTAMP |
VARCHAR |
请注意,并非所有内容都可以转换为VARCHAR
。这种类型的优先级最低,即如果列无法转换为其他类型,它们将被转换为VARCHAR
。在flights.csv
中,FlightDate
列将被转换为DATE
,而其他列将被转换为VARCHAR
。
CSV阅读器应考虑的候选类型集可以使用auto_type_candidates
选项明确指定。
除了默认的候选类型集之外,还可以使用auto_type_candidates
选项指定的其他类型有:
类型 |
---|
DECIMAL |
FLOAT |
INTEGER |
SMALLINT |
TINYINT |
尽管可以自动检测的数据类型集合可能看起来相当有限,但可以通过使用下一节中描述的types
选项来配置CSV阅读器以读取任意复杂类型。
可以通过使用all_varchar
选项完全禁用类型检测。如果设置了此选项,所有列将保持为VARCHAR
(就像它们在CSV文件中最初出现的那样)。
覆盖类型检测
检测到的类型可以使用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 |