Variant类型

更新时间:
复制 MD 格式

Hologres V5.0及以上版本支持通过外部表读取数据湖表中的Variant数据,但内部表暂不支持Variant类型。Variant适用于字段结构频繁变化的半结构化数据,可保留值的原生类型,并通过Parquet Shredding提升常用子字段的查询效率。本文为您介绍Variant的适用范围、操作符、类型转换和查询优化方式。

Variant介绍

事件、日志和设备上报等半结构化数据通常具有字段不固定、同一字段类型可能变化、Schema演进频繁等特点。将此类数据存储为JSON文本便于接入,但查询单个字段时仍需解析完整文本;将字段全部展开为结构化列虽然便于查询,但新增字段需要持续演进表Schema,稀疏字段也会产生大量空值。

Variant用于在保留灵活Schema的同时,提高湖表中半结构化数据的存储和查询效率。Variant采用Apache Parquet Variant编码,以紧凑的二进制格式保存数据,并保留日期、时间戳、带时区时间戳和精确数值等原生类型。单个Variant列可以存储结构不同的对象、数组或标量,新增内部字段不需要修改表Schema。

Hologres支持读取数据湖表中的Variant列,并通过操作符访问嵌套字段、通过typed cast(类型化转换)将字段转换为Hologres原生类型。对于采用Shredding存储的常用字段,查询还可以利用Parquet列裁剪和RowGroup级DataSkip,减少不相关数据的读取。

Variant与JSON/JSONB的区别

JSON、JSONB和Variant均可表示半结构化数据,但适用的数据位置、物理编码和查询优化方式不同。JSON和JSONB的详细说明,请参见JSON和JSONB类型。

维度

JSON/JSONB

Variant

适用范围

内部表。

DLF Paimon等外部表。

存储格式

文本(JSON)、二进制(JSONB)。

Parquet Variant编码,支持Shredding。

写入支持

支持INSERT、COPY。

不支持,由Spark、Flink等引擎写入Paimon。

索引支持

GIN索引(JSONB)。

不支持索引,通过Shredding和RowGroup DataSkip加速。

操作符

->、->>、#>、#>>、?、@>、<@等。

->、->>、#>、?,以及typed cast。

类型转换

需通过->>取TEXT后再转换,如::int。

直接typed cast,如(v->'age')::int8。

列裁剪

列式JSONB(Hologres V1.3及以上版本)提供有限列裁剪。

Shredded子列原生列裁剪。

使用限制

  • Variant类型仅支持在Hologres V5.0及以上版本中,通过外部表读取数据湖表(如DLF Paimon表)中的Variant列。

  • Hologres内部表暂不支持Variant类型,不能在内部表中创建Variant类型的列。如需在Hologres内部表中存储半结构化数据,请使用JSON或JSONB类型,详情请参见JSON和JSONB类型。

  • Hologres不支持写入Variant列,仅支持读取。数据湖表中的Variant数据由Spark、Flink等引擎写入Paimon。

  • 不支持#>>操作符(以TEXT形式获取指定路径的值)。如需获取嵌套路径的文本值,可使用#>操作符结合::text类型转换实现。

  • typed cast当前支持::int8(BIGINT)、::float8(DOUBLE PRECISION)、::bool(BOOLEAN)、::text(TEXT)和::timestamptz(TIMESTAMP WITH TIME ZONE)五种目标类型,不支持::int4(INTEGER)等其他类型的直接转换。如需获取INTEGER值,可先转换为::int8,再在外层进行转换。

  • Variant类型目前不支持GIN索引。

Variant操作符

常用操作符

Variant类型支持的操作符如下表所示,语义与JSON/JSONB操作符保持一致。

操作符

右操作数类型

描述

操作示例

执行结果

->

int

获得Variant数组元素,索引从0开始。

SELECT (v->0)::text FROM t WHERE id=5;

10

->

text

通过键获得Variant对象域,返回值仍为Variant类型。

SELECT v->'age' FROM t WHERE id=0;

21

->>

text

以TEXT形式获得Variant对象域,去除引号。

SELECT v->>'city' FROM t WHERE id=0;

Beijing

#>

text[]

获取指定路径的Variant对象。

SELECT (v#>'{nested,k}')::text FROM t WHERE id=4;

[1, 2, 3]

?

text

判断键是否存在于Variant对象中。

SELECT v?'age' FROM t WHERE id=0;

t

Typed Cast(类型化转换)

Variant类型支持通过::type语法将子字段直接转换为原生类型,无需先转为TEXT再解析。支持的目标类型如下表所示。

转换语法

目标类型

描述

操作示例

执行结果

::text

TEXT

将Variant值转为文本。对象和数组返回JSON文本,字符串返回带引号的值,数字和布尔值返回其文本表示。

SELECT (v->'city')::text FROM t WHERE id=0;

"Beijing"

::int8

BIGINT

将Variant数值转为64位整数。

SELECT (v->'age')::int8 FROM t WHERE id=0;

21

::float8

DOUBLE PRECISION

将Variant数值转为双精度浮点数。

SELECT (v->'score')::float8 FROM t WHERE id=0;

3.14

::bool

BOOLEAN

将Variant布尔值转为BOOLEAN。

SELECT (v->'active')::bool FROM t WHERE id=0;

t

::timestamptz

TIMESTAMP WITH TIME ZONE

将Variant的带时区时间值转为TIMESTAMP WITH TIME ZONE。显示结果受当前会话时区影响。

SELECT (v->'event_time')::timestamptz FROM t WHERE id=0;

2026-09-02 10:00:00+08

Shredded存储与查询优化

Variant的核心优势在于Shredded存储,即Paimon在写入Variant列时,将高频字段物理拆分为独立的Parquet列(typed_value),查询引擎可以直接读取对应子列,无需解析整个Variant文档。

投影字段裁剪

当查询仅涉及Variant对象中的个别字段时,引擎只读取被Shredded的物理子列,跳过其他字段的数据。例如对一个包含20个字段的Variant列,仅查询v->'age'时,只需读取age对应的typed_value列,I/O开销与读取单个普通INT列相当。

RowGroup DataSkip

对于Shredded子列,Parquet的RowGroup级统计信息(min、max、null_count)自动生效。查询引擎在扫描前根据过滤条件的统计信息跳过不满足条件的RowGroup,大幅减少I/O扫描量。

以下示例对50万行表执行点查,利用Shredded子列的RowGroup统计信息跳过大部分数据。

SELECT id, (v->'seq')::int8 AS seq
FROM ext_dlf.variant_acc.v_skip
WHERE (v->'seq')::int8 = 250000;

通过EXPLAIN ANALYZE可以观察DataSkip效果。

->  Seq Scan on v_skip
      Filter: (((v -> 'seq'::text))::bigint = '250000'::bigint)
      RowGroupFilter: (((v -> 'seq'::text))::bigint = '250000'::bigint)
      [scan_rows=3868]

上述执行计划中,RowGroupFilter表明过滤条件被用于RowGroup级裁剪,全表50万行仅实际扫描3868行。

使用示例

基本读取

以下示例演示如何通过DLF外部数据库读取Paimon表中的Variant列。创建外部数据库的详细语法,请参见CREATE EXTERNAL DATABASE;通过DLF访问Paimon数据的完整流程,请参见基于DLF Catalog访问Paimon数据。

-- 1. 创建外部数据库(如尚未创建)
CREATE EXTERNAL DATABASE ext_dlf WITH
  catalog_type 'paimon'
  metastore_type 'dlf-rest'
  dlf_catalog '<YOUR_CATALOG_NAME>'
  comment 'DLF Paimon catalog';

-- 2. 直接读取Variant列
SELECT id, v::text
FROM ext_dlf.<YOUR_SCHEMA>.<YOUR_TABLE>
ORDER BY id
LIMIT 10;

执行结果示例如下。

 id |                    v
----+-----------------------------------------
  0 | {"age": 21, "city": "Beijing"}
  1 | {"age": 27}
  2 | {"city": "Beijing", "other": "xxx"}
  3 | {"other": "yyy"}
  4 | {"age": 28, "nested": {"k": [1, 2, 3]}}
(5 行记录)

多字段共投影

可同时提取Variant对象中的多个字段,每个字段独立进行typed cast。

SELECT
  id,
  v::text AS raw,
  (v->'age')::int8 AS age,
  (v->'city')::text AS city
FROM ext_dlf.<YOUR_SCHEMA>.<YOUR_TABLE>
ORDER BY id;

执行结果示例如下。

 id |                   raw                   | age  |   city
----+-----------------------------------------+------+-----------
  0 | {"age": 21, "city": "Beijing"}          |   21 | "Beijing"
  1 | {"age": 27}                             |   27 |
  2 | {"city": "Beijing", "other": "xxx"}     |      | "Beijing"
  3 | {"other": "yyy"}                        |      |
  4 | {"age": 28, "nested": {"k": [1, 2, 3]}} |   28 |
(5 行记录)
说明

当某行中不存在被访问的字段时,对应列返回SQL NULL。

嵌套路径访问

使用#>操作符访问嵌套对象。

SELECT (v#>'{nested,k}')::text AS nested_array
FROM ext_dlf.<YOUR_SCHEMA>.<YOUR_TABLE>
WHERE id = 4;

执行结果如下。

 nested_array
--------------
 [1, 2, 3]
(1 行记录)

数组下标访问

以Variant值为[10, 20, 30]的行为例,通过数组下标访问各元素。

SELECT
  (v->0)::text AS idx0,
  (v->1)::text AS idx1,
  (v->2)::text AS idx2
FROM ext_dlf.<YOUR_SCHEMA>.<YOUR_TABLE>
WHERE id = 5;

执行结果如下。

 idx0 | idx1 | idx2
------+------+------
 10   | 20   | 30
(1 行记录)

键存在性检查

使用?操作符判断键是否存在。

SELECT id, v?'age' AS has_age
FROM ext_dlf.<YOUR_SCHEMA>.<YOUR_TABLE>
WHERE id IN (0, 3)
ORDER BY id;

执行结果如下。

 id | has_age
----+---------
  0 | t
  3 | f
(2 行记录)

使用Variant字段排序与聚合

Variant子字段经typed cast后可直接用于排序和聚合。

-- 按age字段排序
SELECT id
FROM ext_dlf.<YOUR_SCHEMA>.<YOUR_TABLE>
ORDER BY (v->'age')::int8 NULLS LAST
LIMIT 5;

-- 按age字段聚合
SELECT (v->'age')::int8 AS age, COUNT(*)
FROM ext_dlf.<YOUR_SCHEMA>.<YOUR_TABLE>
GROUP BY 1
ORDER BY 1 NULLS LAST;

访问不存在的字段

Variant列的根值可以是任意JSON值类型,不限于对象。对于非对象根值(数组、字符串、数字、布尔值、null),通过->访问不存在的键将返回SQL NULL。以下示例中,id为5至9的行的Variant值依次为[10,20,30]、"scalar string"、12345、true和null。

SELECT
  id,
  v::text,
  (v->'nokey')::int8 AS n_int,
  (v->'nokey')::text AS n_text
FROM ext_dlf.<YOUR_SCHEMA>.<YOUR_TABLE>
WHERE id >= 5
ORDER BY id;

执行结果如下。

 id |        v        | n_int | n_text
----+-----------------+-------+--------
  5 | [10, 20, 30]    |       |
  6 | "scalar string" |       |
  7 | 12345           |       |
  8 | true            |       |
  9 | null            |       |
(5 行记录)
说明
  • 对非对象类型的Variant值访问任何键名,均返回SQL NULL。

  • 查询时,Variant类型以JSON文本形式展示。