CSV
DuckDB 提供了命令来从CSV中导入数据。
导入 CSV
由于 CSV 文件并没有严格的规范,并且对于数据类型的表现非常弱。因此导入 CSV 文件时一个非常棘手的任务。
DuckDB 提供了两种方式来导入 CSV 文件:
- 通过 SQL 标准的
COPY ... FROM
语句来实现 - 通过 duckdb 内置的
read_csv()
CSV 读取器函数来导入 CSV 数据
-- 读取 csv 文件创建了内存表
SELECT * FROM read_csv('flights.csv', sample_size=20000);
-- 可以导入表
-- CREATE TABLE AS 方式
CREATE TABLE ontime AS SELECT * FROM 'flights.csv';
-- COPY 方式, 前提是必须先创建表
CREATE TABLE ontime (
FlightDate DATE,
UniqueCarrier VARCHAR,
OriginCityName VARCHAR,
DestCityName VARCHAR
);
COPY ontime FROM 'flights.csv';
read_csv()
默认情况下 read_csv()
能够通过 CSV 嗅探器来自动检测 CSV 文件的格式以及数据类型,特殊情况下 read_csv()
还提供了各种选项来修改嗅探器的结果:
参数 | 说明 |
---|---|
all_varchar:bool = false |
跳过类型检测,假定所有的列是 VARCHAR 类型 |
allow_quoted_nulls:bool = true |
允许将引号包裹的值(quoted value)转换为 null |
auto_detect:bool = true |
启动自动类型检测 |
auto_type_candidates:type[] = ['SQLNULL','BOOLEAN','BIGINT','DOUBLE','TIME','DATE','TIMESTAMP','VARCHAR'] |
允许嗅探器在检测类型时使用的类型 |
columns:dict = {} |
指定 csv 文件中包含的列名和对应的数据类型,这意味着不使用自动检测 |
compression:str = 'auto' |
文件的压缩类型,默认情况下会更具扩展名自动检测 |
dateformat:str = '' |
指定如何解析日期格式 |
decimal_separator:str = '.' |
数字的小数分隔符 |
delim/sep:str = ',' |
指定分隔符 |
filename:bool\|str = false |
结果中是否应当包含额外的 filename 列,如果值是字符串将作为 filename 的列名 |
force_not_null:str[] = [] |
不要将指定列的值设置为 null,这意味着空字符串会作为长度为零的字符串,而不是 null |
header:bool = false |
指定文件包含标题行 |
ignore_errors:bool = false |
忽略有错误的行 |
max_line_size:int = 2097152 |
一行的最大字节数 |
names:str[] = [] |
指定列名列表 |
new_line:str = '\n' |
指定换行符 |
nullstr:str = '' |
指定表示 null 值的字符串 |
parallel:bool = true |
csv 读取器是否并行 |
quote:str = '"' |
指定在对数据值进行包裹时使用的引号 |
null_padding:bool = false |
如果行缺少列时,是否使用 null 填充右侧列 |
sample_size:int = 20480 |
自动检测类型的样本行数 |
skip:int = 0 |
要跳过的文件行数 |
timestampformat:str = None |
制定如何解析日期时间格式 |
types/dtypes:dict = None |
类似 {header: type} 为对应字段指定类别 |
union_by_name:bool = false |
读取多个 csv 时根据字段名合并而不是位置 |
Tips
这些参数在DuckDB CLI中是可以全部使用的,当时其他客户端例如DuckDB Python API中并不都可用。此时可以通过作为 SQL 语句来传入使用
auto_detect
默认情况下 auto_detect:bool = true
开启自动检测,他会读取 sample_size
参数指定的行来检测:
- CSV 的方言,包括分隔符、引号规则、转义方式
- 检测每列的类型
- 检测文件是否具有标题行
DuckDB 还提供了 一个 sniff_csv(filename)
函数来单独运行 CSV 嗅探器,该函数会以一个单行表的形式返回,他的字段包括:
列名称 | 描述 | 示例 |
---|---|---|
Delimiter | 分隔符 | , |
Quote | 包裹引号 | " |
Escape | 转义字符 | | |
NewLineDelimiter | 换行符 | \r\n |
SkipRow | 跳过的行数 | 1 |
HasHeader | 是否有标头 | true |
Columns | 列对应的类型 | {'name': 'VARCHAR', 'age': 'BIGINT'} |
DateFormat | 日期格式 | %Y-%m-%d |
TimestampFormat | 日期时间格式 | 如果没有检测到可为空 |
UserArguments | 调用 sniff_csv 的参数 | sample_size=1000 |
Prompt | 最佳推荐 | FROM read_csv('my_file.csv', auto_detect=false, delim=',', ...) |
Tips
.mode
默认的情况无法展示表的所有内容,此时可以 .mode json
来输出结果
Tips
sniff_csv 方法只能在 CLI 中使用
错误 CSV 文件处理
如果默认情况下出现读取错误,通常可以使用三个选项来解决:
ignore_errors = true
: 会忽略包含错误的行max_line_size = xx
: 一行的最大字节数,这个在保存一些非常长的文本时就必须指定null_padding = true
: 缺失值以 null 补充,这个比较粗暴通常不建议使用
上面的三种方式都比较粗暴,会导致数据的丢失。我们更希望的是确切知道哪些行出现了解析错误,对于这种场景,DuckDB 提供了 CSV 拒绝表(rejects_table)功能,有三个参数来配置该功能:
参数 | 描述 |
---|---|
rejects_table:str = '' |
要存储 CSV 文件错误行信息的表名称 |
rejects_limit:int = 0 |
记录拒绝表中错误行的上限,0 表示没有任何限制 |
rejects_recovery_columns:str[] = [] |
用作 csv 文件的主键的列,他会存储在拒绝表中用于表示唯一行 |
FROM read_csv(
'faulty.csv',
columns = {'name': 'VARCHAR', 'age': 'INTEGER'},
rejects_table = 'rejects_table',
ignore_errors = true
);
Tips
需要注意,rejects_table 功能必须配合 ignore_errors=true
使用
CSV 拒绝表的返回结果的字段如下:
列名 | 描述 |
---|---|
file: str | 文件路径 |
line: int | 发生错误的 csv 文件中的行数 |
column: int | 发明错误的 csv 文件中的列号 |
column_name: str | 发生错误的 csv 文件中的列名称 |
parsed_value: str | 发生强制转换错误的值 |
recovery_columns: dict | csv 文件的可选主键 |
error: str | 解析器遇到的错误 |
读取多文件
duckdb 能够批量读取 CSV 文件来构造表:
-- 读取以 .csv 结尾的所有 dir 目录中的文件
SELECT * FROM 'dir/*.csv';
-- 能够进行深度定义,即两个目录下
SELECT * FROM '*/*/*.csv';
-- 任意深度
SELECT * FROM 'dir/**/*.csv';
Note
他使用了 Glob 语法,其中 *
匹配任意数量的任意字符,**
匹配任意数量的子目录,?
匹配任意单个字符,可以使用 [abc]
或者 [a-z]
来匹配指定范围的字符。
默认情况下假设 csv 中的字段以及顺序是完全一致的,如果想要实现根据字段名称来合并表需要指定 union_by_name = true
,如果想要保存文件的名称可以指定 filename = ture
:
导出 CSV
要导出数据到 CSV 文件有两种方式:
- 通过CLI Dot Command 的
.output path/filename.csv
配合.mode csv
来导出 - 通过来
COPY ... TO
SQL 语句来实现
Tips
Dot Command 比较灵活方便,但是 COPY TO 提供了更加细节的控制,例如导出时压缩