在适当的地方使用 JSON
ClickHouse 现在提供了一种专为半结构化和动态数据设计的原生 JSON 列类型。重要的是要明确 这是一种列类型,而不是数据格式——您可以将 JSON 作为字符串插入 ClickHouse,或者通过支持的格式如 JSONEachRow 插入,但这并不意味着使用 JSON 列类型。当数据结构是动态时,用户应仅使用 JSON 类型,而不是仅仅因为存储了 JSON。
何时使用 JSON 类型
当您的数据:
- 具有 不可预测的键,可能会随着时间而变化。
- 包含 具有不同类型的值(例如,一个路径有时可能包含字符串,有时可能包含数字)。
- 需要模式灵活性,严格类型不切实际。
如果您的数据结构是已知且一致的,即使数据是 JSON 格式,也很少需要使用 JSON 类型。具体来说,如果您的数据具有:
- 具有已知键的扁平结构:使用标准列类型,例如 String。
- 可预测的嵌套:对于这些结构,使用 Tuple、Array 或 Nested 类型。
- 具有不同类型的可预测结构:可以考虑使用 Dynamic 或 Variant 类型。
您还可以混合使用方法——例如,使用静态列来存储可预测的顶级字段,并为有效载荷的动态部分使用单个 JSON 列。
使用 JSON 的注意事项和建议
JSON 类型通过将路径扁平化为子列,启用高效的列式存储。但是,灵活性伴随着责任。要有效使用它:
- 使用column definition中的提示 指定路径类型,以避免不必要的类型推断。
- 跳过不需要的路径,使用 SKIP 和 SKIP REGEXP 以减少存储并提高性能。
- 避免将
max_dynamic_paths
设置得过高——较大的值会增加资源消耗并降低效率。作为经验法则,保持在 10,000 以下。
类型提示不仅仅是避免不必要类型推断的方法——它们完全消除了存储和处理间接的需要。带有类型提示的 JSON 路径始终像传统列一样存储,避免了在查询时需要鉴别列或动态解析。这意味着,使用明确定义的类型提示,嵌套 JSON 字段能够达到与从一开始就建模为顶级字段相同的性能和效率。因此,对于大部分一致但仍受益于 JSON 灵活性的数据集,类型提示提供了一种方便的方式来保持性能,而无需重构您的模式或摄取管道。
高级功能
- JSON 列 可以像其他列一样用于主键。子列不能指定编解码器。
- 它们支持通过 JSONAllPathsWithTypes() 和 JSONDynamicPaths()等函数进行自省。
- 您可以使用
.^
语法读取嵌套子对象。 - 查询语法可能与标准 SQL 不同,可能需要为嵌套字段进行特殊的类型转换或操作符。
有关额外指导,请参阅 ClickHouse JSON 文档 或探索我们的博客文章 ClickHouse 的新强大 JSON 数据类型。
示例
考虑以下 JSON 示例,代表 Python PyPI 数据集 中的一行:
假设此模式是静态的且类型可以明确定义。即使数据采用 NDJSON 格式(每行一个 JSON),对于此模式也没有必要使用 JSON 类型。只需使用经典类型定义架构即可。
并插入 JSON 行:
考虑包含 250 万篇学术论文的 arXiv 数据集。此数据集中,每行分布为 NDJSON,代表已发布的学术论文。以下是一个示例行:
虽然这里的 JSON 结构复杂,并且存在嵌套结构,但它是可预测的。字段的数量和类型不会改变。虽然我们可以为此示例使用 JSON 类型,但我们也可以仅使用 Tuples 和 Nested 类型明确定义该结构:
同样,我们可以将数据作为 JSON 插入:
假设添加了一个名为 tags
的列。如果这只是一个字符串列表,我们可以将其建模为 Array(String)
,但假设用户可以添加具有混合类型的任意标签结构(注意 score 是字符串或整数)。我们的修改 JSON 文档:
在这种情况下,我们可以将 arXiv 文档建模为全部 JSON 或简单地添加一个 JSON tags
列。我们在下面提供两种示例:
我们在 JSON 定义中为 update_date
列提供类型提示,因为我们在排序/主键中使用它。这有助于 ClickHouse 知道该列不会为 null,并确保它知道使用哪个 update_date
子列(每种类型可能有多个,因此否则会造成歧义)。
我们可以向此表插入数据,并使用 JSONAllPathsWithTypes
函数和 PrettyJSONEachRow
输出格式查看随后的推断架构:
另外,我们可以使用先前的模式和 JSON tags
列进行建模。这通常更可取,最小化 ClickHouse 所需的推断:
我们现在可以推断子列 tags 的类型。