You need to enable JavaScript to run this app.
导航
复杂数据类型
最近更新时间:2024.12.04 14:29:11首次发布时间:2024.10.30 17:04:31

Map类型

定义

Map类型,存储k-v键值对,含义类似Java中的Map。主要分为隐式列 Map(默认类型,也称为 ByteMap)和 KV Map,二者主要区别在于数据存储的方式,隐式列 Map 会将每个 key 对应的所有 value 单独存为一个隐式列,KV Map 则类似于 Array(Tuple(K, V)),会将出现的 key 和 value 统一存储。

Map key类型允许为 Integers(包括 Int8UInt64 等), Floats32, Float64, Date, DateTime, String, FixedString, LowCardinality, UUID, Enums,不支持为 Nullable
KV Map 的 value 类型没有限制,隐式列 Map value 类型允许为 Integers(包括 Int8UInt64 等), Floats32, Float64, Date, DateTime, String, FixedString, LowCardinality(Nullable()), Array, 不支持最外层为Nullable 或者包含 Tuple 的类型。

定义时在类型后增加 KV 后缀可以使用 KV Map,否则为隐式列 Map。例如 Map(Int16, String), Map(String, Array(Int32)) KV, Map(String, LowCardinality(Nullable(String)))

注意事项
  • 隐式列 Map 适合用于每次只查询少量 map key 的情况,如 SELECT m{'k1'} FROM xx,查询使用花括号 {}(等价于 mapElement 函数)可以有较好的性能;
  • KV Map 适合用于查询整个 Map 列的情况,如 SELECT m FROM xx
  • 隐式列 Map 在单个分区内出现的 key 数量较多时可能影响写入的速度及资源用量,建议数量不要超过 1000(此时相当于一个 1000 列的大宽表);
  • Map列不支持构建索引,因此,在SQL查询语句中不建议直接过滤,需配合其他检索条件过滤。尽量减少scan数据量。
  • 针对一条Map记录,KV Map 允许包含重复的 key,隐式列 Map 只会保存第一次出现的 key 的 value。
  • 不保证key的写入顺序。比如写入时{"a":1,"b":2,"d":3},查询时结果为{"d":3, "a":1, "b":2}

创建表

在创建表时,可以指定列的数据类型为 Map

CREATE TABLE example_map_table (
    id UInt64,
    m1 Map(String, String),   -- 隐式列 Map
    m2 Map(String, String) KV -- KV Map
) ENGINE = CnchMergeTree()
ORDER BY id;

插入数据

插入数据时,可以使用键值对的格式或者在 SELECT 中使用 map 函数。

-- INSERT INTO xx VALUES,使用类似 JSON 的格式(替换为单引号)
INSERT INTO example_map_table (id, m1, m2) VALUES
(1, {'name': 'Alice', 'age': '30'}, {'name': 'Bob', 'age': '25'});

-- INSERT INTO xx SELECT,可以使用 map 函数来生成 Map 类型数据
INSERT INTO example_map_table (id, m1, m2)
SELECT 2, map('name', 'Alice', 'age', '31'), map('name', 'Bob', 'age', '26');

查询数据

使用 Map 提供的函数可以方便地查询和处理 Map 数据。

查询 Map 数据中的特定 Key

-- 1. 使用 {} 或者 mapElement 函数
--   返回值类型为 Nullable(ValueType),key 不存在时返回 Null
--   查询隐式列 Map 时能够只读取对应隐式列的数据,有较好的性能
SELECT
    id,
    m1{'name'} AS m1_name,
    toTypeName(m1_name) AS m1_name_type,
    mapElement(m2, 'city') AS m2_city,
    toTypeName(m2_city) AS m2_city_type
FROM example_map_table;

-- result:
-- 1    'Alice'    'Nullable(String)'    NULL    'Nullable(String)'
-- 2    'Alice'    'Nullable(String)'    NULL    'Nullable(String)'


-- 1. 使用 [] 或者 arrayElement 函数
--   返回值类型为 ValueType,key 不存在时返回默认值
--   查询隐式列 Map 时需要先读出整个 Map,性能比较差
SELECT
    id,
    m1['name'] AS m1_name,
    toTypeName(m1_name) AS m1_name_type,
    arrayElement(m2, 'city') AS m2_city,
    toTypeName(m2_city) AS m2_city_type
FROM example_map_table;
-- 1    'Alice'    'String'    ''    'String'
-- 2    'Alice'    'String'    ''    'String'

查询整个 Map 列

-- 隐式列 Map 若 key 数量较多时此类查询性能较差,可以考虑使用 KV Map
SELECT 
    id,
    m1,
    m2
FROM example_map_table;

-- result:
-- 1    {'age':'30','name':'Alice'}    {'name':'Bob','age':'25'}
-- 2    {'age':'31','name':'Alice'}    {'name':'Bob','age':'26'}

使用 Map Key(对应 Value)作为筛选条件

SELECT
    id,
    m1{'age'}
FROM example_map_table
WHERE m1{'name'} = 'Alice' AND m2{'age'} = '26';

-- result:
-- 2    31

使用示例

示例 1:动态属性存储

在某些应用场景中,不同的记录可能具有不同的属性。MAP 数据类型可以方便地存储这些动态属性。

CREATE TABLE dynamic_attributes (
    entity_id UInt64,
    attributes Map(String, String)
) ENGINE = CnchMergeTree()
ORDER BY entity_id;

INSERT INTO dynamic_attributes (entity_id, attributes) VALUES
(1, {'color': 'red', 'size': 'M')),
(2, {'brand': 'Nike', 'material': 'cotton'));

我们可以查询所有具有特定属性的记录:

SELECT
    entity_id,
    attributes
FROM dynamic_attributes
WHERE attributes{'color'} = 'red';

数组类型(ARRAY)

定义

ARRAYT 类型元素组成的数组。
T 可以是任意类型,包含数组类型。 但不推荐使用多维数组,ByteHouse 对多维数组的支持有限。例如,不能存储在 MergeTree 表中存储多维数组。

创建数组

您可以使用array函数来创建数组:

array(T)

示例:

SELECT array(1, 2) AS x, toTypeName(x)
┌─x─────┬─toTypeName(array(1, 2))─┐
│ [1,2] │ Array(UInt8)            │
└───────┴─────────────────────────┘
SELECT [1, 2] AS x, toTypeName(x)
┌─x─────┬─toTypeName([1, 2])─┐
│ [1,2] │ Array(UInt8)       │
└───────┴────────────────────┘

使用数据类型

ByteHouse会自动检测数组元素,并根据元素计算出存储这些元素最小的数据类型。如果在元素中存在 NULL 或存在 可为空 类型元素,那么数组的元素类型将会变成 可为空。
如果 ByteHouse 无法确定数据类型,它将产生异常。当尝试同时创建一个包含字符串和数字的数组时会发生这种情况 (SELECT array(1, 'a'))。
自动数据类型检测示例:

SELECT array(1, 2, NULL) AS x, toTypeName(x)
┌─x──────────┬─toTypeName(array(1, 2, NULL))─┐
│ [1,2,NULL] │ Array(Nullable(UInt8))        │
└────────────┴───────────────────────────────┘

如果您尝试创建不兼容的数据类型数组,ByteHouse 将引发异常:

SELECT array(1, 'a')
Code: 386, Error: DB::Exception: There is no supertype for types There is no supertype for types UInt8, String because some of them are String/FixedString and some of them are not SQLSTATE: HY000

数组大小

可以使用 size0 子列找到数组的大小,而无需读取整个列。对于多维数组,您可以使用 sizeN-1,其中 N 是所需的维度。
示例
SQL查询:

CREATE TABLE t_arr (`arr` Array(Array(Array(UInt32)))) ENGINE = CnchMergeTree ORDER BY tuple();

INSERT INTO t_arr VALUES ([[[12, 13, 0, 1],[12]]]);

SELECT arr.size0, arr.size1, arr.size2 FROM t_arr;

结果:

┌─arr.size0─┬─arr.size1─┬─arr.size2─┐
│         1 │ [2]       │ [[4,1]]   │
└───────────┴───────────┴───────────┘

元组类型(Tuple)

说明

元组 的每个元素都有单独的 类型。
不能在表中存储元组(除了内存表)。它们可以用于临时列分组。在查询中,IN 表达式和带特定参数的 lambda 函数可以来对临时列进行分组。
元组可以是查询的结果。在这种情况下,对于JSON以外的文本格式,括号中的值是逗号分隔的。在JSON格式中,元组作为数组输出(在方括号中)。

创建元组

可以使用函数来创建元组:

tuple(T1, T2, ...)

创建元组的示例:

SELECT tuple(1,'a') AS x, toTypeName(x)
┌─x───────┬─toTypeName(tuple(1, 'a'))─┐
│ (1,'a') │ Tuple(UInt8, String)      │
└─────────┴───────────────────────────┘

元组中的数据类型

在动态创建元组时,ByteHouse 会自动为元组的每一个参数赋予最小可表达的类型。如果参数为 NULL,那这个元组对应元素是 可为空
自动数据类型检测示例:

SELECT tuple(1, NULL) AS x, toTypeName(x)
┌─x────────┬─toTypeName(tuple(1, NULL))──────┐
│ (1,NULL) │ Tuple(UInt8, Nullable(Nothing)) │
└──────────┴─────────────────────────────────┘

JSON类型

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于阅读和编写。ByteHouse 支持 JSON 数据类型,使得用户能够直接在数据库中存储和查询 JSON 格式的数据。

创建表

要在 ByteHouse 中使用 JSON 数据类型,可以在创建表时指定列的数据类型为 JSON

CREATE TABLE example_json_table (
    id UInt64,
    data JSON
) ENGINE = CnchMergeTree()
ORDER BY id;

插入数据

可以直接插入 JSON 格式的数据。

INSERT INTO example_json_table (id, data) VALUES
(1, '{"name": "Alice", "age": 30, "city": "New York"}'),
(2, '{"name": "Bob", "age": 25, "city": "San Francisco"}');

查询数据

使用 JSON 提供的函数可以方便地查询和处理 JSON 数据。

-- 查询 JSON 数据中的特定字段
SELECT
    id,
    JSONExtractString(data, 'name') AS name,
    JSONExtractInt(data, 'age') AS age
FROM example_json_table;

-- 查询符合特定条件的 JSON 数据
SELECT
    id,
    data
FROM example_json_table
WHERE JSONExtractString(data, 'city') = 'New York';

枚举类型

包括 Enum8Enum16 类型。Enum 保存 'string'= integer 的对应关系。在 ClickHouse 中,尽管用户使用的是字符串常量,但所有含有 Enum 数据类型的操作都是按照包含整数的值来执行。这在性能方面比使用 String 数据类型更有效。

  • Enum8'String'= Int8 对描述。
  • Enum16'String'= Int16 对描述。

用法示例

创建一个带有一个枚举 Enum8('hello' = 1, 'world' = 2) 类型的列:

CREATE TABLE t_enum
(
    x Enum8('hello' = 1, 'world' = 2)
)
ENGINE = TinyLog

这个 x 列只能存储类型定义中列出的值:'hello''world'。如果您尝试保存任何其他值,ClickHouse 抛出异常。

INSERT INTO t_enum VALUES ('hello'), ('world'), ('hello')
Ok.

3 rows in set. Elapsed: 0.002 sec.
INSERT INTO t_enum VALUES('a')
Exception on client:
Code: 49. DB::Exception: Unknown element 'a' for type Enum8('hello' = 1, 'world' = 2)

当您从表中查询数据时,ClickHouse 从 Enum 中输出字符串值。

SELECT * FROM t_enum
┌─x─────┐
│ hello │
│ world │
│ hello │
└───────┘

如果需要看到对应行的数值,则必须将 Enum 值转换为整数类型。

SELECT CAST(x, 'Int8') FROM t_enum
┌─CAST(x, 'Int8')─┐
│               1 │
│               2 │
│               1 │
└─────────────────┘

在查询中创建枚举值,您还需要使用 CAST

SELECT toTypeName(CAST('a', 'Enum8(\'a\' = 1, \'b\' = 2)'))
┌─toTypeName(CAST('a', 'Enum8(\'a\' = 1, \'b\' = 2)'))─┐
│ Enum8('a' = 1, 'b' = 2)                              │
└──────────────────────────────────────────────────────┘

规则及用法

Enum8 类型的每个值范围是 -128 ... 127Enum16 类型的每个值范围是 -32768 ... 32767。所有的字符串或者数字都必须是不一样的。允许存在空字符串。如果某个 Enum 类型被指定了(在表定义的时候),数字可以是任意顺序。然而,顺序并不重要。
Enum 中的字符串和数值都不能是 NULL。
Enum 包含在 可为空 类型中。因此,如果您使用此查询创建一个表

CREATE TABLE t_enum_nullable
(
    x Nullable( Enum8('hello' = 1, 'world' = 2) )
)
ENGINE = TinyLog

不仅可以存储 'hello''world' ,还可以存储 NULL

INSERT INTO t_enum_nullable Values('hello'),('world'),(NULL)

在内存中,Enum 列的存储方式与相应数值的 Int8Int16 相同。
当以文本方式读取的时候,ClickHouse 将值解析成字符串然后去枚举值的集合中搜索对应字符串。如果没有找到,会抛出异常。当读取文本格式的时候,会根据读取到的字符串去找对应的数值。如果没有找到,会抛出异常。
当以文本形式写入时,ClickHouse 将值解析成字符串写入。如果列数据包含垃圾数据(不是来自有效集合的数字),则抛出异常。Enum 类型以二进制读取和写入的方式与 Int8Int16 类型一样的。
隐式默认值是数值最小的值。
ORDER BYGROUP BYINDISTINCT 等等中,Enum 的行为与相应的数字相同。例如,按数字排序。对于等式运算符和比较运算符,Enum 的工作机制与它们在底层数值上的工作机制相同。
枚举值不能与数字进行比较。枚举可以与常量字符串进行比较。如果与之比较的字符串不是有效Enum值,则将引发异常。可以使用 IN 运算符来判断一个 Enum 是否存在于某个 Enum 集合中,其中集合中的 Enum 需要用字符串表示。
大多数具有数字和字符串的运算并不适用于Enums;例如,Enum 类型不能和一个数值相加。但是,Enum有一个原生的 toString 函数,它返回它的字符串值。
Enum 值使用 toT 函数可以转换成数值类型,其中 T 是一个数值类型。若 T 恰好对应 Enum 的底层数值类型,这个转换是零消耗的。
Enum 类型可以被 ALTER 无成本地修改对应集合的值。可以通过 ALTER 操作来增加或删除 Enum 的成员(只要表没有用到该值,删除都是安全的)。作为安全保障,改变之前使用过的 Enum 成员将抛出异常。
通过 ALTER 操作,可以将 Enum8 转成 Enum16,反之亦然,就像 Int8Int16一样。

BitMap64类型

BitMap64是 ByteHouse 中提供的一种数据类型(DataType),可以参考Array(UInt64)这种类型。这种类型在数据表示上等同于社区 ClickHouse 提供的数据类型。AggregateFunction(groupBitmap, UInt64)。你可以定义该类型的字段,写入数据并读取数据进行计算。

⚠️Tips: BitMap64是数据类型,底层使用了 RoaringBitmap 开源库。ByteHouse 中有较多功能都利用了 RoaringBimap 实现,其中BitmapIndex比较容易和 BitMap64类型混淆。区别如下:
BitMap64: ByteHouse提供的物理列数据类型,直接把RoaringBitmap定义成了一个数据类型,可写可读;相同功能的社区数据类型是AggregateFunction(groupBitmap, UInt64)
BitmapIndex: ByteHouse提供的一种数据索引能力,目前用于Array + Int8/16/32/64/UInt/8/16/32/64/String类型,它为Array中每个元素构建一个基于RoaringBitmap实现的索引,查询时使用arraySetCheck(column, (item)) 检测item是否在数组里,并使用bitmap-index做过滤,从而避免大量的array数据读取。索引文件本身不可读取

创建BitMap64数据

如果是在查询中,你可以通过如下函数产生BitMap64 类型的数据:

select bitmapFromColumn(number) from numbers(10);

select arrayToBitmap(groupArray(number)) from numbers(10;

定义BitMap64 类型

使用BitMap64关键字作为该类型的定义方法。示例如下:

CREATE TABLE bitmap_table
(
    `tag_id`           Int32,
    `tag_value`        String,
    `id_map`           BitMap64,
    `p_date`           Date
)
ENGINE = CnchMergeTree
PARTITION BY p_date
ORDER BY (tag_id, tag_value);

写入数据

如果是文本格式的数据源(如 CSV/JSONEachRow)等,既可以使用和数组一样的表示格式(使用[]包裹数据),也可使用 BitMap64 的格式(使用{}包裹数据)填充字段值。示例如下:

insert into bitmap_table values (1, 'aaa', [2,3,4,5,6], '2024-01-01');

insert into bitmap_table format JSONEachRow {"tag_id":2,"tag_value":"bbb","id_map":"{4,5,6}","p_date":"2024-01-01"}

如果是 Parquet 格式,则使用 LIST(UINT64),和数组一样的方式填充数据。
如果使用insert select的方式写入,则和其他类型一样把列对应上即可。

查询数据

BitMap64是可打印类型,你可以直接查询该字段,会以文本的方式进行展示。示例如下:

select * from bitmap_table where p_date = '2024-01-01' and tag_id = 1;

--- 查询结果
tag_id  tag_value       id_map  p_date
1       aaa     {2,3,4,5,6}     2024-01-01

还可以使用 bitmap函数来对 BitMap64 类型数据进行复杂运算,这部分参考【函数 - BitMap64函数】章节。

低基数类型(LowCardinality)

把其它数据类型转变为字典编码类型。

语法
LowCardinality(data_type)

参数
  • data_type — String, FixedString, Date, DateTime,包括数字类型,但是Decimal除外。对一些数据类型来说,LowCardinality 并不高效,详查allow_suspicious_low_cardinality_types设置描述。

描述

LowCardinality 是一种改变数据存储和数据处理方法的概念。 ClickHouse会把 LowCardinality 所在的列进行dictionary coding。对很多应用来说,处理字典编码的数据可以显著的增加SELECT查询速度。
使用 LowCarditality 数据类型的效率依赖于数据的多样性。如果一个字典包含少于10000个不同的值,那么ClickHouse可以进行更高效的数据存储和处理。反之如果字典多于10000,效率会表现的更差。
当使用字符类型的时候,可以考虑使用 LowCardinality 代替Enum。 LowCardinality 通常更加灵活和高效。

示例

创建一个 LowCardinality 类型的列:

CREATE TABLE lc_t
(
    `id` UInt16,
    `strings` LowCardinality(String)
)
ENGINE = MergeTree()
ORDER BY id

相关的设置和函数

设置:

函数:

网络类型(IPv4/IPv6)

IPv4

IPv4是与UInt32类型保持二进制兼容的Domain类型,其用于存储IPv4地址的值。它提供了更为紧凑的二进制存储的同时支持识别可读性更加友好的输入输出格式。

CREATE TABLE hits (url String, from IPv4) ENGINE = MergeTree() ORDER BY url;

DESCRIBE TABLE hits;
┌─name─┬─type───┬─default_type─┬─default_expression─┬─comment─┬─codec_expression─┐
│ url  │ String │              │                    │         │                  │
│ from │ IPv4   │              │                    │         │                  │
└──────┴────────┴──────────────┴────────────────────┴─────────┴──────────────────┘

同时您也可以使用IPv4类型的列作为主键:

CREATE TABLE hits (url String, from IPv4) ENGINE = MergeTree() ORDER BY from;

在写入与查询时,IPv4类型能够识别可读性更加友好的输入输出格式:

INSERT INTO hits (url, from) VALUES ('https://wikipedia.org', '116.253.40.133')('https://clickhouse.com', '183.247.232.58')('https://clickhouse.com/docs/en/', '116.106.34.242');
SELECT * FROM hits;
┌─url────────────────────────────────┬───────────from─┐
│ https://clickhouse.com/docs/en/ │ 116.106.34.242 │
│ https://wikipedia.org              │ 116.253.40.133 │
│ https://clickhouse.com          │ 183.247.232.58 │
└────────────────────────────────────┴────────────────┘

同时它提供更为紧凑的二进制存储格式:

SELECT toTypeName(from), hex(from) FROM hits LIMIT 1;
┌─toTypeName(from)─┬─hex(from)─┐
│ IPv4             │ B7F7E83A  │
└──────────────────┴───────────┘

不可隐式转换为除UInt32以外的其他类型类型。如果要将IPv4类型的值转换成字符串,你可以使用IPv4NumToString()显示的进行转换:

SELECT toTypeName(s), IPv4NumToString(from) as s FROM hits LIMIT 1;
┌─toTypeName(IPv4NumToString(from))─┬─s──────────────┐
│ String                            │ 183.247.232.58 │
└───────────────────────────────────┴────────────────┘

或可以使用CAST将它转换为UInt32类型:

SELECT toTypeName(i), CAST(from as UInt32) as i FROM hits LIMIT 1;
┌─toTypeName(CAST(from, 'UInt32'))─┬──────────i─┐
│ UInt32                           │ 3086477370 │
└──────────────────────────────────┴────────────┘

IPv6

IPv6是与FixedString(16)类型保持二进制兼容的Domain类型,其用于存储IPv6地址的值。它提供了更为紧凑的二进制存储的同时支持识别可读性更加友好的输入输出格式。

CREATE TABLE hits (url String, from IPv6) ENGINE = MergeTree() ORDER BY url;

DESCRIBE TABLE hits;
┌─name─┬─type───┬─default_type─┬─default_expression─┬─comment─┬─codec_expression─┐
│ url  │ String │              │                    │         │                  │
│ from │ IPv6   │              │                    │         │                  │
└──────┴────────┴──────────────┴────────────────────┴─────────┴──────────────────┘

同时您也可以使用IPv6类型的列作为主键:

CREATE TABLE hits (url String, from IPv6) ENGINE = MergeTree() ORDER BY from;

在写入与查询时,IPv6类型能够识别可读性更加友好的输入输出格式:

INSERT INTO hits (url, from) VALUES ('https://wikipedia.org', '2a02:aa08:e000:3100::2')('https://clickhouse.com', '2001:44c8:129:2632:33:0:252:2')('https://clickhouse.com/docs/en/', '2a02:e980:1e::1');

SELECT * FROM hits;
┌─url────────────────────────────────┬─from──────────────────────────┐
│ https://clickhouse.com          │ 2001:44c8:129:2632:33:0:252:2 │
│ https://clickhouse.com/docs/en/ │ 2a02:e980:1e::1               │
│ https://wikipedia.org              │ 2a02:aa08:e000:3100::2        │
└────────────────────────────────────┴───────────────────────────────┘

同时它提供更为紧凑的二进制存储格式:

SELECT toTypeName(from), hex(from) FROM hits LIMIT 1;
┌─toTypeName(from)─┬─hex(from)────────────────────────┐
│ IPv6             │ 200144C8012926320033000002520002 │
└──────────────────┴──────────────────────────────────┘

不可隐式转换为除FixedString(16)以外的其他类型类型。如果要将IPv6类型的值转换成字符串,你可以使用IPv6NumToString()显示的进行转换:

SELECT toTypeName(s), IPv6NumToString(from) as s FROM hits LIMIT 1;
┌─toTypeName(IPv6NumToString(from))─┬─s─────────────────────────────┐
│ String                            │ 2001:44c8:129:2632:33:0:252:2 │
└───────────────────────────────────┴───────────────────────────────┘

或使用CAST将其转换为FixedString(16)

SELECT toTypeName(i), CAST(from as FixedString(16)) as i FROM hits LIMIT 1;
┌─toTypeName(CAST(from, 'FixedString(16)'))─┬─i───────┐
│ FixedString(16)                           │  ��� │
└───────────────────────────────────────────┴─────────┘

聚合函数类型(AggregateFunction)

聚合函数的中间状态,可以通过聚合函数名称加-State后缀的形式得到它。与此同时,当您需要访问该类型的最终状态数据时,您需要以相同的聚合函数名加-Merge后缀的形式来得到最终状态数据。
AggregateFunction — 参数化的数据类型。
参数

  • 聚合函数名
如果函数具备多个参数列表,请在此处指定其他参数列表中的值。
  • 聚合函数参数的类型

示例

CREATE TABLE t
(
    column1 AggregateFunction(uniq, UInt64),
    column2 AggregateFunction(anyIf, String, UInt8),
    column3 AggregateFunction(quantiles(0.5, 0.9), UInt64)
) ENGINE = ...

上述中的uniq, anyIf (任何+如果) 以及 分位数 都为ClickHouse中支持的聚合函数。

数据写入

当需要写入数据时,您需要将数据包含在INSERT SELECT语句中,同时对于AggregateFunction类型的数据,您需要使用对应的以-State为后缀的函数进行处理。
函数使用示例

uniqState(UserID)
quantilesState(0.5, 0.9)(SendTiming)

不同于uniqquantiles函数返回聚合结果的最终值,以-State后缀的函数总是返回AggregateFunction类型的数据的中间状态。
对于SELECT而言,AggregateFunction类型总是以特定的二进制形式展现在所有的输出格式中。例如,您可以使用SELECT语句将函数的状态数据转储为TabSeparated格式的同时使用INSERT语句将数据转储回去。

数据查询

当从AggregatingMergeTree表中查询数据时,对于AggregateFunction类型的字段,您需要使用以-Merge为后缀的相同聚合函数来聚合数据。对于非AggregateFunction类型的字段,请将它们包含在GROUP BY子句中。
-Merge为后缀的聚合函数,可以将多个AggregateFunction类型的中间状态组合计算为最终的聚合结果。
例如,如下的两个查询返回的结果总是一致:

SELECT uniq(UserID) FROM table

SELECT uniqMerge(state) FROM (SELECT uniqState(UserID) AS state FROM table GROUP BY RegionID)