NumericIndexedVector 是一种抽象数据结构,用于封装向量并实现向量聚合和逐元素运算。其存储方式为位切片索引(Bit-Sliced Index)。关于其理论基础和使用场景,请参考论文 Large-Scale Metric Computation in Online Controlled Experiment Platform。
BSI
在 BSI(Bit-Sliced Index,比特切片索引)存储方式中,数据首先以 Bit-Sliced Index 形式存储,然后再使用 Roaring Bitmap 进行压缩。聚合运算和逐点运算直接在压缩数据上进行,这可以显著提升存储和查询效率。
一个向量包含索引及其对应的值。以下是该数据结构在 BSI 存储模式下的一些特性和约束:
- 索引类型可以是
UInt8、UInt16 或 UInt32 之一。注意: 鉴于 Roaring Bitmap 在 64 位实现上的性能,BSI 格式不支持 UInt64/Int64。
- 值类型可以是
Int8、Int16、Int32、Int64、UInt8、UInt16、UInt32、UInt64、Float32 或 Float64 之一。注意: 值类型不会自动扩展。例如,如果你使用 UInt8 作为值类型,那么任何超过 UInt8 容量的求和都会发生溢出,而不会提升为更高类型;同样,对整数的运算会产生整数结果(例如,除法不会自动转换为浮点结果)。因此,提前规划和设计值类型非常重要。在实际场景中,常用的是浮点类型(Float32/Float64)。
- 只有索引类型和值类型都相同的两个向量才能进行运算。
- 底层存储使用 Bit-Sliced Index,其中索引用位图存储。Roaring Bitmap 用作位图的具体实现。最佳实践是尽可能将索引集中在少数几个 Roaring Bitmap 容器中,以最大化压缩率和查询性能。
- Bit-Sliced Index 机制会将值转换为二进制。对于浮点类型,转换采用定点数表示,这可能会带来精度损失。可以通过自定义小数部分所使用的比特数来调整精度,默认是 24 位,这在大多数场景下已经足够。在使用带有
-State 的聚合函数 groupNumericIndexedVector 构造 NumericIndexedVector 时,可以自定义整数位和小数位的位数。
- 索引有三种情况:非零值、零值和不存在。在 NumericIndexedVector 中,仅存储非零值和零值。此外,在两个 NumericIndexedVector 之间进行逐点运算时,不存在的索引值会被视为 0。在除法场景中,当除数为零时,结果为零。
创建 numericIndexedVector 对象
有两种方式可以创建这种结构:一种是使用带有 -State 的聚合函数 groupNumericIndexedVector。
你可以添加后缀 -if 来接受一个额外的条件。
聚合函数只会处理满足该条件的行。
另一种方式是使用 numericIndexedVectorBuild 从一个 map 构建该结构。
groupNumericIndexedVectorState 函数允许通过参数自定义整数位和小数位的位数,而 numericIndexedVectorBuild 则不支持这一点。
groupNumericIndexedVector
从两个数据列构造一个 NumericIndexedVector,并以 Float64 类型返回所有值的和。如果添加后缀 State,则返回一个 NumericIndexedVector 对象。
语法
groupNumericIndexedVectorState(col1, col2)
groupNumericIndexedVectorState(type, integer_bit_num, fraction_bit_num)(col1, col2)
参数
type:字符串类型,可选。指定存储格式。目前仅支持 'BSI'。
integer_bit_num:UInt32,可选。在 'BSI' 存储格式下生效,该参数表示用于整数部分的位数。当索引类型为整数类型时,默认值为存储该索引所用的位数。例如,当索引类型为 UInt16 时,默认的 integer_bit_num 为 16。对于 Float32 和 Float64 索引类型,integer_bit_num 的默认值为 40,因此可表示的数据整数部分范围为 [-2^39, 2^39 - 1]。合法范围为 [0, 64]。
fraction_bit_num:UInt32,可选。在 'BSI' 存储格式下生效,该参数表示用于小数部分的位数。当值类型为整数时,默认值为 0;当值类型为 Float32 或 Float64 时,默认值为 24。合法范围为 [0, 24]。
- 还存在一个约束条件:integer_bit_num + fraction_bit_num 的合法范围为 [0, 64]。
col1:索引列。支持的类型:UInt8/UInt16/UInt32/Int8/Int16/Int32。
col2:数值列。支持的类型:Int8/Int16/Int32/Int64/UInt8/UInt16/UInt32/UInt64/Float32/Float64。
返回值
一个 Float64 值,表示所有数值的和。
示例
测试数据:
UserID 游戏时长
1 10
2 20
3 30
查询与结果:
SELECT groupNumericIndexedVector(UserID, PlayTime) AS num FROM t;
┌─num─┐
│ 60 │
└─────┘
SELECT groupNumericIndexedVectorState(UserID, PlayTime) as res, toTypeName(res), numericIndexedVectorAllValueSum(res) FROM t;
┌─res─┬─toTypeName(res)─────────────────────────────────────────────┬─numericIndexedVectorAllValueSum(res)──┐
│ │ AggregateFunction(groupNumericIndexedVector, UInt8, UInt8) │ 60 │
└─────┴─────────────────────────────────────────────────────────────┴───────────────────────────────────────┘
SELECT groupNumericIndexedVectorStateIf(UserID, PlayTime, day = '2025-04-22') as res, toTypeName(res), numericIndexedVectorAllValueSum(res) FROM t;
┌─res─┬─toTypeName(res)────────────────────────────────────────────┬─numericIndexedVectorAllValueSum(res)──┐
│ │ AggregateFunction(groupNumericIndexedVector, UInt8, UInt8) │ 30 │
└─────┴────────────────────────────────────────────────────────────┴───────────────────────────────────────┘
SELECT groupNumericIndexedVectorStateIf('BSI', 32, 0)(UserID, PlayTime, day = '2025-04-22') as res, toTypeName(res), numericIndexedVectorAllValueSum(res) FROM t;
┌─res─┬─toTypeName(res)──────────────────────────────────────────────────────────┬─numericIndexedVectorAllValueSum(res)──┐
│ │ AggregateFunction('BSI', 32, 0)(groupNumericIndexedVector, UInt8, UInt8) │ 30 │
└─────┴──────────────────────────────────────────────────────────────────────────┴───────────────────────────────────────┘
注意
以下文档是从 system.functions 系统表生成的。
numericIndexedVectorAllValueSum
自 v25.7 起引入
返回 numericIndexedVector 中所有值的总和。
语法
numericIndexedVectorAllValueSum(v)
参数
返回值
返回总和。Float64
示例
用法示例
SELECT numericIndexedVectorAllValueSum(numericIndexedVectorBuild(mapFromArrays([1, 2, 3], [10, 20, 30]))) AS res;
numericIndexedVectorBuild
引入版本:v25.7
从 map 创建一个 NumericIndexedVector。map 的键表示向量的索引,map 的值表示向量的元素值。
语法
numericIndexedVectorBuild(map)
参数
返回值
返回一个 NumericIndexedVector 对象。AggregateFunction
示例
使用示例
SELECT numericIndexedVectorBuild(mapFromArrays([1, 2, 3], [10, 20, 30])) AS res, toTypeName(res);
┌─res─┬─toTypeName(res)────────────────────────────────────────────┐
│ │ AggregateFunction(groupNumericIndexedVector, UInt8, UInt8) │
└─────┴────────────────────────────────────────────────────────────┘
numericIndexedVectorCardinality
首次引入于:v25.7
返回 numericIndexedVector 的基数(唯一索引数量)。
语法
numericIndexedVectorCardinality(v)
参数
返回值
返回唯一索引的数量,类型为 UInt64。
示例
用法示例
SELECT numericIndexedVectorCardinality(numericIndexedVectorBuild(mapFromArrays([1, 2, 3], [10, 20, 30]))) AS res;
numericIndexedVectorGetValue
自 v25.7 版本引入
从 numericIndexedVector 中检索指定索引对应的值。
语法
numericIndexedVectorGetValue(v, i)
参数
返回值
一个数值,其类型与 NumericIndexedVector 的值类型相同。(U)Int* 或 Float*
示例
使用示例
SELECT numericIndexedVectorGetValue(numericIndexedVectorBuild(mapFromArrays([1, 2, 3], [10, 20, 30])), 3) AS res;
numericIndexedVectorPointwiseAdd
引入版本:v25.7
对一个 numericIndexedVector 与另一个 numericIndexedVector 或数值常量执行逐元素加法运算。
语法
numericIndexedVector逐点相加(v1, v2)
参数
返回值
返回一个新的 numericIndexedVector 对象。numericIndexedVector
示例
用法示例
WITH
numericIndexedVectorBuild(mapFromArrays([1, 2, 3], arrayMap(x -> toInt32(x), [10, 20, 30]))) AS vec1,
numericIndexedVectorBuild(mapFromArrays([2, 3, 4], arrayMap(x -> toInt32(x), [10, 20, 30]))) AS vec2
SELECT
numericIndexedVectorToMap(numericIndexedVectorPointwiseAdd(vec1, vec2)) AS res1,
numericIndexedVectorToMap(numericIndexedVectorPointwiseAdd(vec1, 2)) AS res2;
┌─res1──────────────────┬─res2─────────────┐
│ {1:10,2:30,3:50,4:30} │ {1:12,2:22,3:32} │
└───────────────────────┴──────────────────┘
numericIndexedVectorPointwiseDivide
引入于:v25.7
对一个 numericIndexedVector 与另一个 numericIndexedVector 或数值常量执行逐元素除法运算。
语法
numericIndexedVectorPointwiseDivide(v1, v2)
参数
返回值
返回一个新的 numericIndexedVector 对象。numericIndexedVector
示例
用法示例
with
numericIndexedVectorBuild(mapFromArrays([1, 2, 3], arrayMap(x -> toFloat64(x), [10, 20, 30]))) as vec1,
numericIndexedVectorBuild(mapFromArrays([2, 3, 4], arrayMap(x -> toFloat64(x), [10, 20, 30]))) as vec2
SELECT
numericIndexedVectorToMap(numericIndexedVectorPointwiseDivide(vec1, vec2)) AS res1,
numericIndexedVectorToMap(numericIndexedVectorPointwiseDivide(vec1, 2)) AS res2;
┌─res1────────┬─res2────────────┐
│ {2:2,3:1.5} │ {1:5,2:10,3:15} │
└─────────────┴─────────────────┘
numericIndexedVectorPointwiseEqual
引入于:v25.7
对一个 numericIndexedVector 与另一个 numericIndexedVector 或一个数值常量进行逐元素比较。
结果是一个 numericIndexedVector,其中包含值相等位置的索引,所有对应的值都被设为 1。
语法
numericIndexedVectorPointwiseEqual(v1, v2)
参数
返回值
返回一个新的 numericIndexedVector 对象。
示例
with
numericIndexedVectorBuild(mapFromArrays([1, 2, 3], arrayMap(x -> toFloat64(x), [10, 20, 30]))) as vec1,
numericIndexedVectorBuild(mapFromArrays([2, 3, 4], arrayMap(x -> toFloat64(x), [20, 20, 30]))) as vec2
SELECT
numericIndexedVectorToMap(numericIndexedVectorPointwiseEqual(vec1, vec2)) AS res1,
numericIndexedVectorToMap(numericIndexedVectorPointwiseEqual(vec1, 20)) AS res2;
┌─res1──┬─res2──┐
│ {2:1} │ {2:1} │
└───────┴───────┘
numericIndexedVectorPointwiseGreater
引入版本:v25.7
对一个 numericIndexedVector 与另一个 numericIndexedVector 或数值常量进行逐元素比较。
结果是一个 numericIndexedVector,其中包含第一个向量的值大于第二个向量的值的索引位置,且所有这些位置上的值均被设置为 1。
语法
numericIndexedVectorPointwiseGreater(v1, v2)
参数
返回值
返回一个新的 numericIndexedVector 对象。numericIndexedVector
示例
使用示例
with
numericIndexedVectorBuild(mapFromArrays([1, 2, 3], arrayMap(x -> toFloat64(x), [10, 20, 50]))) as vec1,
numericIndexedVectorBuild(mapFromArrays([2, 3, 4], arrayMap(x -> toFloat64(x), [20, 40, 30]))) as vec2
SELECT
numericIndexedVectorToMap(numericIndexedVectorPointwiseGreater(vec1, vec2)) AS res1,
numericIndexedVectorToMap(numericIndexedVectorPointwiseGreater(vec1, 20)) AS res2;
┌─res1──────┬─res2──┐
│ {1:1,3:1} │ {3:1} │
└───────────┴───────┘
numericIndexedVectorPointwiseGreaterEqual
引入版本:v25.7
对一个 numericIndexedVector 与另一个 numericIndexedVector 或一个数值常量进行逐元素比较。
结果是一个 numericIndexedVector,其中包含第一个向量中值大于或等于第二个向量中对应值的索引,且这些索引上的所有值都被设置为 1。
语法
numericIndexedVectorPointwiseGreaterEqual(v1, v2)
参数
返回值
返回一个新的 numericIndexedVector 对象。numericIndexedVector
示例
使用示例
with
numericIndexedVectorBuild(mapFromArrays([1, 2, 3], arrayMap(x -> toFloat64(x), [10, 20, 50]))) as vec1,
numericIndexedVectorBuild(mapFromArrays([2, 3, 4], arrayMap(x -> toFloat64(x), [20, 40, 30]))) as vec2
SELECT
numericIndexedVectorToMap(numericIndexedVectorPointwiseGreaterEqual(vec1, vec2)) AS res1,
numericIndexedVectorToMap(numericIndexedVectorPointwiseGreaterEqual(vec1, 20)) AS res2;
┌─res1──────────┬─res2──────┐
│ {1:1,2:1,3:1} │ {2:1,3:1} │
└───────────────┴───────────┘
numericIndexedVectorPointwiseLess
引入于:v25.7
对一个 numericIndexedVector 与另一个 numericIndexedVector 或一个数值常量进行逐元素比较。
结果是一个 numericIndexedVector,包含第一个向量中那些值小于第二个向量对应值的索引位置,并将这些索引位置上的值全部设为 1。
语法
numericIndexedVectorPointwiseLess(v1, v2)
参数
返回值
返回一个新的 numericIndexedVector 对象。numericIndexedVector
示例
使用示例
with
numericIndexedVectorBuild(mapFromArrays([1, 2, 3], arrayMap(x -> toFloat64(x), [10, 20, 30]))) as vec1,
numericIndexedVectorBuild(mapFromArrays([2, 3, 4], arrayMap(x -> toFloat64(x), [20, 40, 30]))) as vec2
SELECT
numericIndexedVectorToMap(numericIndexedVectorPointwiseLess(vec1, vec2)) AS res1,
numericIndexedVectorToMap(numericIndexedVectorPointwiseLess(vec1, 20)) AS res2;
┌─res1──────┬─res2──┐
│ {3:1,4:1} │ {1:1} │
└───────────┴───────┘
numericIndexedVectorPointwiseLessEqual
引入版本:v25.7
对一个 numericIndexedVector 与另一个 numericIndexedVector 或数值常量执行逐元素比较。
结果是一个 numericIndexedVector,其中包含第一个向量中值小于或等于第二个向量值的位置索引,且这些位置上的值均被设为 1。
语法
numericIndexedVectorPointwiseLessEqual(v1, v2)
参数
返回值
返回一个新的 numericIndexedVector 对象。
示例
使用示例
with
numericIndexedVectorBuild(mapFromArrays([1, 2, 3], arrayMap(x -> toFloat64(x), [10, 20, 30]))) 作为 vec1,
numericIndexedVectorBuild(mapFromArrays([2, 3, 4], arrayMap(x -> toFloat64(x), [20, 40, 30]))) 作为 vec2
SELECT
numericIndexedVectorToMap(numericIndexedVectorPointwiseLessEqual(vec1, vec2)) AS res1,
numericIndexedVectorToMap(numericIndexedVectorPointwiseLessEqual(vec1, 20)) AS res2;
┌─res1──────────┬─res2──────┐
│ {2:1,3:1,4:1} │ {1:1,2:1} │
└───────────────┴───────────┘
numericIndexedVectorPointwiseMultiply
在 v25.7 中引入
对一个 numericIndexedVector 与另一个 numericIndexedVector 或数值常量执行逐元素乘法。
语法
numericIndexedVectorPointwiseMultiply(v1, v2)
参数
返回值
返回一个新的 numericIndexedVector 对象。numericIndexedVector
示例
with
numericIndexedVectorBuild(mapFromArrays([1, 2, 3], arrayMap(x -> toInt32(x), [10, 20, 30]))) as vec1,
numericIndexedVectorBuild(mapFromArrays([2, 3, 4], arrayMap(x -> toInt32(x), [10, 20, 30]))) as vec2
SELECT
numericIndexedVectorToMap(numericIndexedVectorPointwiseMultiply(vec1, vec2)) AS res1,
numericIndexedVectorToMap(numericIndexedVectorPointwiseMultiply(vec1, 2)) AS res2;
┌─res1──────────┬─res2─────────────┐
│ {2:200,3:600} │ {1:20,2:40,3:60} │
└───────────────┴──────────────────┘
numericIndexedVectorPointwiseNotEqual
引入版本:v25.7
对一个 numericIndexedVector 与另一个 numericIndexedVector 或一个数值常量执行逐元素比较。
返回一个 numericIndexedVector,其中包含值不相等的索引,且所有对应的值都被设置为 1。
语法
numericIndexedVectorPointwiseNotEqual(v1, v2)
参数
返回值
返回一个新的 numericIndexedVector 对象。numericIndexedVector
示例
用法示例
with
numericIndexedVectorBuild(mapFromArrays([1, 2, 3], arrayMap(x -> toFloat64(x), [10, 20, 30]))) as vec1,
numericIndexedVectorBuild(mapFromArrays([2, 3, 4], arrayMap(x -> toFloat64(x), [20, 20, 30]))) as vec2
SELECT
numericIndexedVectorToMap(numericIndexedVectorPointwiseNotEqual(vec1, vec2)) AS res1,
numericIndexedVectorToMap(numericIndexedVectorPointwiseNotEqual(vec1, 20)) AS res2;
┌─res1──────────┬─res2──────┐
│ {1:1,3:1,4:1} │ {1:1,3:1} │
└───────────────┴───────────┘
numericIndexedVectorPointwiseSubtract
自 v25.7 起引入
对一个 numericIndexedVector 与另一个 numericIndexedVector 或一个数值常量执行按元素减法运算。
语法
numericIndexedVectorPointwiseSubtract(v1, v2)
参数
返回值
返回新的 numericIndexedVector 对象。numericIndexedVector
示例
使用示例
WITH
numericIndexedVectorBuild(mapFromArrays([1, 2, 3], arrayMap(x -> toInt32(x), [10, 20, 30]))) AS vec1,
numericIndexedVectorBuild(mapFromArrays([2, 3, 4], arrayMap(x -> toInt32(x), [10, 20, 30]))) AS vec2
SELECT
numericIndexedVectorToMap(numericIndexedVectorPointwiseSubtract(vec1, vec2)) AS res1,
numericIndexedVectorToMap(numericIndexedVectorPointwiseSubtract(vec1, 2)) AS res2;
┌─res1───────────────────┬─res2────────────┐
│ {1:10,2:10,3:10,4:-30} │ {1:8,2:18,3:28} │
└────────────────────────┴─────────────────┘
numericIndexedVectorShortDebugString
自 v25.7 引入
以 JSON 格式返回 numericIndexedVector 的内部信息。
此函数主要用于调试。
语法
numericIndexedVectorShortDebugString(v)
参数
返回值
返回一个包含调试信息的 JSON 字符串。String
示例
使用示例
SELECT numericIndexedVectorShortDebugString(numericIndexedVectorBuild(mapFromArrays([1, 2, 3], [10, 20, 30]))) AS res\G;
第 1 行:
──────
res: {"vector_type":"BSI","index_type":"char8_t","value_type":"char8_t","integer_bit_num":8,"fraction_bit_num":0,"zero_indexes_info":{"cardinality":"0"},"non_zero_indexes_info":{"total_cardinality":"3","all_value_sum":60,"number_of_bitmaps":"8","bitmap_info":{"cardinality":{"0":"0","1":"2","2":"2","3":"2","4":"2","5":"0","6":"0","7":"0"}}}}
numericIndexedVectorToMap
自 v25.7 引入
将 numericIndexedVector 转换为 map。
语法
numericIndexedVectorToMap(v)
参数
返回值
返回一个包含索引-值对的映射(Map)。Map
示例
使用示例
SELECT numericIndexedVectorToMap(numericIndexedVectorBuild(mapFromArrays([1, 2, 3], [10, 20, 30]))) AS res;
┌─res──────────────┐
│ {1:10,2:20,3:30} │
└──────────────────┘