原文:DuckDB’s CSV Sniffer: Automatic Detection of Types and Dialects – DuckDB[1]
翻译:Gemini Pro
校对:alitrack
日期:2023 年 10 月 27 日
作者: 佩德罗·奥兰达
DuckDB 的 CSV 嗅探器:自动检测类型和方言
重点提示:DuckDB 主要专注于性能,利用现代文件格式的功能。同时,我们也关注灵活的、非性能驱动的格式,如 CSV 文件。为了在从 CSV 文件读取数据时创造一种友好愉悦的体验,DuckDB 实现了一个 CSV 嗅探器,可以自动检测 CSV 方言选项、列类型,甚至跳过脏数据。嗅探过程允许用户有效地探索 CSV 文件,而无需提供任何有关文件格式的输入。
在存储数据时,用户可以选择许多不同的文件格式。例如,有面向性能的二进制格式,如 Parquet,其中数据以列式格式存储,划分为行组,并经过大量压缩。然而,Parquet 以其刚性而闻名,需要专门的系统来读写这些文件。
另一方面,还有采用 CSV(逗号分隔值)格式的文件,我喜欢称之为“数据伍德斯托克”。CSV 文件具有灵活性的优势;它们被构造为文本文件,允许用户使用任何文本编辑器对其进行操作,几乎任何数据系统都可以读取它们并对其执行查询。
然而,这种灵活性是有代价的。读取 CSV 文件并非一项简单的任务,因为用户需要大量关于该文件的先验知识。例如,DuckDB 的 CSV 读取器[2]提供了 25 个以上的配置选项。我发现人们倾向于认为,如果我没有在每次发布中至少引入三个新选项,我就没有努力工作。开玩笑的。这些选项包括指定分隔符、引号和转义字符,确定 CSV 文件中的列数,以及标识是否存在标题,同时定义列类型。这可能会减慢交互式数据探索过程,并使分析新数据集成为一项繁琐且不那么愉快的工作。
DuckDB 的存在理由之一就是令人愉快且易于使用,因此我们不希望我们的用户不得不手动摆弄 CSV 文件和输入选项。手动输入应仅保留给对 CSV 方言(其中方言包括用于创建该文件的定界符、引号、转义符和换行符值的组合)有相当不寻常的选择或用于指定列类型。
自动检测 CSV 选项可能是一个艰巨的过程。不仅要调查许多选项,而且它们的组合很容易导致搜索空间爆炸。对于结构不佳的 CSV 文件尤其如此。有些人可能会争辩说 CSV 文件有一个规范[3],但事实是,只要有一个系统能够读取有缺陷的文件,“规范”就会发生变化。而且,天哪,在过去的几个月里,我遇到了很多半损坏的 CSV 文件,人们希望 DuckDB 能够读取这些文件。
DuckDB 实现了一个多假设 CSV 嗅探器[4],可以自动检测方言、标题、日期/时间格式、列类型,并标识要跳过的脏行。我们的最终目标是自动读取任何类似 CSV 文件的内容,永不放弃,永不让你失望!所有这一切都可以在读取 CSV 文件时不产生大量初始成本的情况下实现。在最新版本中,默认情况下在读取 CSV 文件时运行嗅探器。请注意,嗅探器始终会优先考虑用户设置的任何选项(例如,如果用户将 ,
设置为分隔符,则嗅探器不会尝试任何其他选项,并会假定用户输入正确)。
在这篇博文中,我将解释当前实现的工作原理,讨论其性能,并提供对接下来会发生什么的见解!
DuckDB 的自动检测
CSV 文件的解析过程如下图所示。它目前包括五个不同的阶段,将在下一节中详细介绍。
概述示例中使用的 CSV 文件如下:
Name, Height, Vegetarian, Birthday
"Pedro", 1.73, False, 30-07-92
... imagine 2048 consistent rows ...
"Mark", 1.72, N/A, 20-09-92
在第一阶段,我们执行方言检测,其中我们选择方言候选,这些候选在 CSV 文件中生成最多的每行列,同时保持一致性(即,在整个文件中列数没有显着变化)。在我们的示例中,我们可以观察到,在此阶段之后,嗅探器成功检测到分隔符、引号、转义符和换行符分隔符的必要选项。
第二阶段称为类型检测,涉及识别 CSV 文件中每列的数据类型。在我们的示例中,我们的嗅探器识别出四种列类型:VARCHAR
、DOUBLE
、BOOL
和 DATE
。
第三步称为标题检测,用于确定我们的文件是否包含标题。如果存在标题,我们使用它来设置列名;否则,我们会自动生成它们。在我们的示例中,有一个标题,每个列都在其中定义了其名称。
现在我们的列有了名称,我们进入第四个可选阶段:_类型替换_。DuckDB 的 CSV 读取器为用户提供了按名称指定列类型的选项。如果指定了这些类型,我们将用用户的规范替换检测到的类型。
最后,我们进入最后一个阶段类型细化。在此阶段,我们分析文件的其他部分以验证在初始类型检测阶段确定的类型的准确性。如有必要,我们会对它们进行细化。在我们的示例中,我们可以看到 Vegetarian
列最初被归类为 BOOL
。然而,在进一步检查后,发现它包含字符串 N/A
,导致列类型升级为 VARCHAR
以容纳所有可能的值。
自动检测仅在 CSV 文件的顺序样本上执行。默认情况下,样本大小为 20,480 个元组(即 10 个 DuckDB 执行块)。这可以通过 sample_size
选项进行配置,如果用户想要嗅探整个文件,可以将其设置为 -1。由于使用各种选项重复读取相同的数据,并且用户可以扫描整个文件,因此在嗅探期间生成的所有 CSV 缓冲区都会被缓存并有效管理,以确保高性能。
当然,在非常大的文件上运行 CSV 嗅探器会对整体性能产生巨大影响(请参阅下面的基准部分)。在这些情况下,应将样本大小保持在合理水平。
在接下来的小节中,我将详细描述每个阶段。
方言检测
在方言检测中,我们标识 CSV 文件的分隔符、引号、转义符和换行符分隔符。
我们的分隔符搜索空间包括以下分隔符:,
, |
, ;
, t
。如果文件的分隔符不在搜索空间内,则必须由用户提供(例如,delim='?'
)。我们的引号搜索空间是 "
, '
和