在适当的情况下使用 JSON
ClickHouse 现在提供了一种原生 JSON 列类型,专为半结构化和动态数据设计。需要明确的是,这是一种列类型,而不是数据格式——您可以将 JSON 作为字符串插入到 ClickHouse 中,或者通过支持的格式如 JSONEachRow 插入,但这并不意味着使用 JSON 列类型。用户应仅在数据结构动态时使用 JSON 类型,而不是仅仅因为他们存储 JSON。
何时使用 JSON 类型
当您的数据:
- 具有 不可预测的键,可能会随着时间变化。
- 包含 多种类型的值 (例如,一个路径有时可能包含字符串,有时包含数字)。
- 需要模式灵活性,严格的类型定义不可行。
如果您的数据结构已知且一致,则很少需要使用 JSON 类型,即使您的数据是 JSON 格式的。具体来说,如果您的数据具有:
- 已知键的扁平结构:使用标准列类型,例如 String。
- 可预测的嵌套:对于这些结构,使用 Tuple、Array 或 Nested 类型。
- 可预测的具有不同类型的结构:考虑使用 Dynamic 或 Variant 类型。
您也可以混合使用方法——例如,对可预测的顶层字段使用静态列,对有效负载的动态部分使用单一 JSON 列。
使用 JSON 的考虑事项和提示
JSON 类型通过将路径扁平化为子列来实现高效的列式存储。但灵活性带来了责任。要有效使用它:
- 使用 列定义中的提示 来 指定路径类型,为已知子列指定类型,从而避免不必要的类型推断。
- 如果不需要值,请 跳过路径,使用 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)
,但假设用户可以添加混合类型的任意标签结构(请注意分数是字符串或整数)。我们的修改后的 JSON 文档:
在这种情况下,我们可以将 arXiv 文档建模为全部 JSON 或仅添加一个 JSON tags
列。我们在下面提供两个示例:
我们在 JSON 定义中为 update_date
列提供类型提示,因为我们在排序/主键中使用它。这帮助 ClickHouse 知道此列不会为空,并确保它知道使用哪个 update_date
子列(可能会有多个,因此否则会模糊)。
我们可以将数据插入该表,并使用 JSONAllPathsWithTypes
函数和 PrettyJSONEachRow
输出格式查看随后的推断模式:
或者,我们可以使用我们早期的模式和一个 JSON tags
列进行建模。这通常是优选的,最小化 ClickHouse 所需的推断:
我们现在可以推断子列 tags 的类型。