跳到主要内容
版本:0.2.x

数据类型

文档类型:说明型

内容提要:

  1. byconity 支持哪些数据类型
  2. 对比 clickhouse 缺少哪些数据类型

差异概要

byconity 缺少 Date32 类型,Boolean 类型,Geo 类型(需要 allow_experimental_geo_types =1 才能使用)

byconity 的 Map 有 KV/Byte/CompactByte 三种模式,其中 KV 模式和开源 ClickHouse 兼容

整数类型

固定长度的整型,包括有符号整型或无符号整型。

创建表时,可以为整数设置类型参数 (例如. TINYINT(8), SMALLINT(16), INT(32), BIGINT(64)), 但 byconity 会忽略它们.

整型范围​

  • Int8 — [-128 : 127]
  • Int16 — [-32768 : 32767]
  • Int32 — [-2147483648 : 2147483647]
  • Int64 — [-9223372036854775808 : 9223372036854775807]
  • Int128 — [-170141183460469231731687303715884105728 : 170141183460469231731687303715884105727]
  • Int256 — [-57896044618658097711785492504343953926634992332820282019728792003956564819968 : 57896044618658097711785492504343953926634992332820282019728792003956564819967]

别名:

  • Int8 — TINYINT, BOOL, BOOLEAN, INT1.
  • Int16 — SMALLINT, INT2.
  • Int32 — INT, INT4, INTEGER.
  • Int64 — BIGINT.

无符号整型范围​

  • UInt8 — [0 : 255]
  • UInt16 — [0 : 65535]
  • UInt32 — [0 : 4294967295]
  • UInt64 — [0 : 18446744073709551615]
  • UInt128 — [0 : 340282366920938463463374607431768211455]
  • UInt256 — [0 : 115792089237316195423570985008687907853269984665640564039457584007913129639935]

UUID

通用唯一标识符(UUID)是一个 16 字节的数字,用于标识记录。有关 UUID 的详细信息, 参见维基百科。

UUID 类型值的示例如下:

ec282515-a492-46d8-808c-8f3bd899b1b1

如果在插入新记录时未指定 UUID 列的值,则 UUID 值将用零填充:

00000000-0000-0000-0000-000000000000

如何生成​

要生成 UUID 值,byconity 提供了 generateuidv4 函数。

用法示例​

示例 1

这个例子演示了创建一个具有 UUID 类型列的表,并在表中插入一个值。

CREATE TABLE t_uuid (x UUID, y String) ENGINE=Memory

INSERT INTO t_uuid SELECT generateUUIDv4(), 'Example 1'

SELECT * FROM t_uuid

┌────────────────────────────────────x─┬─y─────────┐
│ 136091bf-474c-496c-a743-bcd531e12563 │ Example 1 │
└──────────────────────────────────────┴───────────┘

示例 2

在这个示例中,插入新记录时未指定 UUID 列的值。

INSERT INTO t_uuid (y) VALUES ('Example 2')

SELECT * FROM t_uuid

┌────────────────────────────────────x─┬─y─────────┐
│ 136091bf-474c-496c-a743-bcd531e12563 │ Example 1 │
│ 00000000-0000-0000-0000-000000000000 │ Example 2 │
└──────────────────────────────────────┴───────────┘

限制​

UUID 数据类型只支持 字符串 数据类型也支持的函数(比如, min, max, 和 count)。

算术运算不支持 UUID 数据类型(例如, abs)或聚合函数,例如 sum 和 avg.

浮点类型

浮点数。

类型与以下 C 语言中类型是相同的:

  • Float32 - float
  • Float64 - double

我们建议您尽可能以整数形式存储数据。例如,将固定精度的数字转换为整数值,例如货币数量或页面加载时间用毫秒为单位表示

使用浮点数​

  • 对浮点数进行计算可能引起四舍五入的误差。
SELECT 1 - 0.9

┌───────minus(1, 0.9)─┐
│ 0.09999999999999998 │
└─────────────────────┘

  • 计算的结果取决于计算方法(计算机系统的处理器类型和体系结构)
  • 浮点计算结果可能是诸如无穷大(INF)和«非数字»(NaN)。对浮点数计算的时候应该考虑到这点。
  • 当一行行阅读浮点数的时候,浮点数的结果可能不是机器最近显示的数值。

NaN 和 Inf​

与标准 SQL 相比,byconity 支持以下类别的浮点数:

  • Inf – 正无穷
SELECT 0.5 / 0

┌─divide(0.5, 0)─┐
│ inf │
└────────────────┘

  • -Inf – 负无穷
SELECT -0.5 / 0

┌─divide(-0.5, 0)─┐
│ -inf │
└─────────────────┘

  • NaN – 非数字
SELECT 0 / 0

┌─divide(0, 0)─┐
│ nan │
└──────────────┘

可以在 ORDER BY 子句 查看更多关于 NaN 排序的规则。

Decimal

包括 Decimal(P,S),Decimal32(S),Decimal64(S),Decimal128(S)等类型

有符号的定点数,可在加、减和乘法运算过程中保持精度。对于除法,最低有效数字会被丢弃(不舍入)。

参数​

  • P - 精度。有效范围:[1:38],决定可以有多少个十进制数字(包括分数)。
  • S - 规模。有效范围:[0:P],决定数字的小数部分中包含的小数位数。

对于不同的 P 参数值 Decimal 表示,以下例子都是同义的: -P 从[1:9]-对于 Decimal32(S) -P 从[10:18]-对于 Decimal64(小号) -P 从[19:38]-对于 Decimal128(S)

十进制值范围​

  • Decimal32(S) - ( -1 10^(9 - S),110^(9-S) )
  • Decimal64(S) - ( -1 10^(18 - S),110^(18-S) )
  • Decimal128(S) - ( -1 10^(38 - S),110^(38-S) )

例如,Decimal32(4) 可以表示 -99999.9999 至 99999.9999 的数值,步长为 0.0001。

内部表示方式​

数据采用与自身位宽相同的有符号整数存储。这个数在内存中实际范围会高于上述范围,从 String 转换到十进制数的时候会做对应的检查。

由于现代 CPU 不支持 128 位数字,因此 Decimal128 上的操作由软件模拟。所以 Decimal128 的运算速度明显慢于 Decimal32/Decimal64。

运算和结果类型​

对 Decimal 的二进制运算导致更宽的结果类型(无论参数的顺序如何)。

  • Decimal64(S1) <op> Decimal32(S2) -> Decimal64(S)
  • Decimal128(S1) <op> Decimal32(S2) -> Decimal128(S)
  • Decimal128(S1) <op> Decimal64(S2) -> Decimal128(S)

精度变化的规则:

  • 加法,减法:S = max(S1, S2)。
  • 乘法:S = S1 + S2。
  • 除法:S = S1。

对于 Decimal 和整数之间的类似操作,结果是与参数大小相同的十进制。

未定义 Decimal 和 Float32/Float64 之间的函数。要执行此类操作,您可以使用:toDecimal32、toDecimal64、toDecimal128 或 toFloat32,toFloat64,需要显式地转换其中一个参数。注意,结果将失去精度,类型转换是昂贵的操作。

Decimal 上的一些函数返回结果为 Float64(例如,var 或 stddev)。对于其中一些,中间计算发生在 Decimal 中。对于此类函数,尽管结果类型相同,但 Float64 和 Decimal 中相同数据的结果可能不同。

溢出检查​

在对 Decimal 类型执行操作时,数值可能会发生溢出。分数中的过多数字被丢弃(不是舍入的)。整数中的过多数字将导致异常。

SELECT toDecimal32(2, 4) AS x, x / 3

┌──────x─┬─divide(toDecimal32(2, 4), 3)─┐
│ 2.0000 │ 0.6666 │
└────────┴──────────────────────────────┘

SELECT toDecimal32(4.2, 8) AS x, x * x

DB::Exception: Scale is out of bounds.

SELECT toDecimal32(4.2, 8) AS x, 6 * x

DB::Exception: Decimal math overflow.

检查溢出会导致计算变慢。如果已知溢出不可能,则可以通过设置 decimal_check_overflow 来禁用溢出检查,在这种情况下,溢出将导致结果不正确:

SET decimal_check_overflow = 0;
SELECT toDecimal32(4.2, 8) AS x, 6 * x

┌──────────x─┬─multiply(6, toDecimal32(4.2, 8))─┐
│ 4.20000000 │ -17.74967296 │
└────────────┴──────────────────────────────────┘

溢出检查不仅发生在算术运算上,还发生在比较运算上:

SELECT toDecimal32(1, 8) < 100

DB::Exception: Can't compare.

日期时间相关类型

Date​

用两个字节存储,表示从 1970-01-01 (无符号) 到当前的日期值。允许存储从 Unix 纪元开始到编译阶段定义的上限阈值常量(目前上限是 2106 年,但最终完全支持的年份为 2105)。最小值输出为 1970-01-01。

值的范围: [1970-01-01, 2149-06-06]。

日期中没有存储时区信息。

示例​

CREATE TABLE date_test
(
`timestamp` Date,
`event_id` UInt8
)
ENGINE = Memory;

INSERT INTO date_test VALUES ('2019-12-20', 1);
SELECT * FROM date_test;

┌──timestamp─┬─event_id─┐
│ 2019-12-20 │ 1 │
└────────────┴──────────┘

DateTime​

时间戳类型。用四个字节(无符号的)存储 Unix 时间戳)。允许存储与日期类型相同的范围内的值。最小值为 1970-01-01 00:00:00。时间戳类型值精确到秒(没有闰秒)。

值的范围: [1970-01-01 00:00:00, 2106-02-07 06:28:15]。

时区​

使用启动客户端或服务器时的系统时区,时间戳是从文本(分解为组件)转换为二进制并返回。在文本格式中,有关夏令时的信息会丢失。

默认情况下,客户端连接到服务的时候会使用服务端时区。您可以通过启用客户端命令行选项 --use_client_time_zone 来设置使用客户端时间。

因此,在处理文本日期时(例如,在保存文本转储时),请记住在夏令时更改期间可能存在歧义,如果时区发生更改,则可能存在匹配数据的问题。

DateTime64​

此类型允许以日期(date)加时间(time)的形式来存储一个时刻的时间值,具有定义的亚秒精度

时间刻度大小(精度):10(-精度) 秒

语法:

DateTime64(precision, [timezone])

在内部,此类型以 Int64 类型将数据存储为自 Linux 纪元开始(1970-01-01 00:00:00UTC)的时间刻度数(ticks)。时间刻度的分辨率由 precision 参数确定。此外,DateTime64 类型可以像存储其他数据列一样存储时区信息,时区会影响 DateTime64 类型的值如何以文本格式显示,以及如何解析以字符串形式指定的时间数据 (‘2020-01-01 05:00:01.000’)。时区不存储在表的行中(也不在 resultset 中),而是存储在列的元数据中。详细信息请参考 DateTime 数据类型.

值的范围: [1900-01-01 00:00:00, 2299-12-31 23:59:59.99999999] (注意: 最大值的精度是 8)。

示例​

1. 创建一个具有 DateTime64 类型列的表,并向其中插入数据:

CREATE TABLE dt
(
`timestamp` DateTime64(3, 'Asia/Istanbul'),
`event_id` UInt8
)
ENGINE = Memory

INSERT INTO dt Values (1546300800000, 1), ('2019-01-01 00:00:00', 2)

SELECT * FROM dt

┌───────────────timestamp─┬─event_id─┐
│ 2019-01-01 03:00:00.000 │ 1 │
│ 2019-01-01 00:00:00.000 │ 2 │
└─────────────────────────┴──────────┘

  • 将日期时间作为 integer 类型插入时,它会被视为适当缩放的 Unix 时间戳(UTC)。1546300800000 (精度为 3)表示 '2019-01-01 00:00:00' UTC. 不过,因为 timestamp 列指定了 Asia/Istanbul (UTC+3)的时区,当作为字符串输出时,它将显示为 '2019-01-01 03:00:00'
  • 当把字符串作为日期时间插入时,它会被赋予时区信息。 '2019-01-01 00:00:00' 将被认为处于 Asia/Istanbul 时区并被存储为 1546290000000.

2. 过滤 DateTime64 类型的值

SELECT * FROM dt WHERE timestamp = toDateTime64('2019-01-01 00:00:00', 3, 'Asia/Istanbul')

┌───────────────timestamp─┬─event_id─┐
│ 2019-01-01 00:00:00.000 │ 2 │
└─────────────────────────┴──────────┘

与 DateTime 不同, DateTime64 类型的值不会自动从 String 类型的值转换过来

3. 获取 DateTime64 类型值的时区信息:

SELECT toDateTime64(now(), 3, 'Asia/Istanbul') AS column, toTypeName(column) AS x

┌──────────────────column─┬─x──────────────────────────────┐
│ 2019-10-16 04:12:04.000 │ DateTime64(3, 'Asia/Istanbul') │
└─────────────────────────┴────────────────────────────────┘

4. 时区转换

SELECT
toDateTime64(timestamp, 3, 'Europe/London') as lon_time,
toDateTime64(timestamp, 3, 'Asia/Istanbul') as mos_time
FROM dt

┌───────────────lon_time──┬────────────────mos_time─┐
│ 2019-01-01 00:00:00.000 │ 2019-01-01 03:00:00.000 │
│ 2018-12-31 21:00:00.000 │ 2019-01-01 00:00:00.000 │
└─────────────────────────┴─────────────────────────┘

枚举类型(Enum8,Enum16)

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

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

用法示例​

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

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

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

:) INSERT INTO t_enum VALUES ('hello'), ('world'), ('hello')

INSERT INTO t_enum VALUES

Ok.

3 rows in set. Elapsed: 0.002 sec.

:) insert into t_enum values('a')

INSERT INTO t_enum VALUES


Exception on client:
Code: 49. DB::Exception: Unknown element 'a' for type Enum8('hello' = 1, 'world' = 2)

当您从表中查询数据时,byconity 从 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 ... 127,Enum16 类型的每个值范围是 -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 列的存储方式与相应数值的 Int8 或 Int16 相同。

当以文本方式读取的时候,byconity 将值解析成字符串然后去枚举值的集合中搜索对应字符串。如果没有找到,会抛出异常。当读取文本格式的时候,会根据读取到的字符串去找对应的数值。如果没有找到,会抛出异常。

当以文本形式写入时,byconity 将值解析成字符串写入。如果列数据包含垃圾数据(不是来自有效集合的数字),则抛出异常。Enum 类型以二进制读取和写入的方式与 Int8 和 Int16 类型一样的。

隐式默认值是数值最小的值。

在 ORDER BY,GROUP BY,IN,DISTINCT 等等中,Enum 的行为与相应的数字相同。例如,按数字排序。对于等式运算符和比较运算符,Enum 的工作机制与它们在底层数值上的工作机制相同。

枚举值不能与数字进行比较。枚举可以与常量字符串进行比较。如果与之比较的字符串不是有效 Enum 值,则将引发异常。可以使用 IN 运算符来判断一个 Enum 是否存在于某个 Enum 集合中,其中集合中的 Enum 需要用字符串表示。

大多数具有数字和字符串的运算并不适用于 Enums;例如,Enum 类型不能和一个数值相加。但是,Enum 有一个原生的 toString 函数,它返回它的字符串值。

Enum 值使用 toT 函数可以转换成数值类型,其中 T 是一个数值类型。若 T 恰好对应 Enum 的底层数值类型,这个转换是零消耗的。

Enum 类型可以被 ALTER 无成本地修改对应集合的值。可以通过 ALTER 操作来增加或删除 Enum 的成员(只要表没有用到该值,删除都是安全的)。作为安全保障,改变之前使用过的 Enum 成员将抛出异常。

通过 ALTER 操作,可以将 Enum8 转成 Enum16,反之亦然,就像 Int8 转 Int16 一样。

字符串类型

String​

字符串可以任意长度的。它可以包含任意的字节集,包含空字节。因此,字符串类型可以代替其他 DBMSs 中的 VARCHAR、BLOB、CLOB 等类型。

编码​

byconity 没有编码的概念。字符串可以是任意的字节集,按它们原本的方式进行存储和输出。 若需存储文本,我们建议使用 UTF-8 编码。至少,如果你的终端使用 UTF-8(推荐),这样读写就不需要进行任何的转换了。 同样,对不同的编码文本 byconity 会有不同处理字符串的函数。 比如,length 函数可以计算字符串包含的字节数组的长度,然而 lengthUTF8 函数是假设字符串以 UTF-8 编码,计算的是字符串包含的 Unicode 字符的长度。

FixString​

固定长度 N 的字符串(N 必须是严格的正自然数)。

您可以使用下面的语法对列声明为 FixedString 类型:

<column_name> FixedString(N)

其中 N 表示自然数。

当数据的长度恰好为 N 个字节时,FixedString 类型是高效的。 在其他情况下,这可能会降低效率。

可以有效存储在 FixedString 类型的列中的值的示例:

  • 二进制表示的 IP 地址(IPv6 使用 FixedString(16))
  • 语言代码(ru_RU, en_US … )
  • 货币代码(USD, RUB … )
  • 二进制表示的哈希值(MD5 使用 FixedString(16),SHA256 使用 FixedString(32))

请使用 UUID 数据类型来存储 UUID 值,。

当向 byconity 中插入数据时,

  • 如果字符串包含的字节数少于 `N’,将对字符串末尾进行空字节填充。
  • 如果字符串包含的字节数大于 N,将抛出Too large value for FixedString(N)异常。

当做数据查询时,byconity 不会删除字符串末尾的空字节。 如果使用 WHERE 子句,则须要手动添加空字节以匹配 FixedString 的值。 以下示例阐明了如何将 WHERE 子句与 FixedString 一起使用。

考虑带有FixedString(2)列的表:

┌─name──┐
│ b │
└───────┘

查询语句 SELECT * FROM FixedStringTable WHERE a = 'b' 不会返回任何结果。请使用空字节来填充筛选条件。

SELECT * FROM FixedStringTable
WHERE a = 'b\0'

┌─a─┐
│ b │
└───┘

这种方式与 MySQL 的 CHAR 类型的方式不同(MySQL 中使用空格填充字符串,并在输出时删除空格)。

请注意,FixedString(N)的长度是个常量。仅由空字符组成的字符串,函数 length 返回值为 N,而函数 empty 的返回值为 1。

嵌套数据结构

Nested(Name1 Type1, Name2 Type2, …)​

嵌套数据结构类似于嵌套表。嵌套数据结构的参数(列名和类型)与 CREATE 查询类似。每个表可以包含任意多行嵌套数据结构。

示例:

CREATE TABLE nested_test (s String, nest Nested(x UInt8, y UInt32)) ENGINE = Memory;
INSERT INTO nested_test VALUES ('Hello', [1,2], [10,20]), ('World', [3,4,5], [30,40,50]), ('Goodbye', [], []);


SELECT * FROM nested_test;

┌─s───────┬─nest.x──┬─nest.y─────┐
│ Hello │ [1,2] │ [10,20] │
│ World │ [3,4,5] │ [30,40,50] │
│ Goodbye │ [] │ [] │
└─────────┴─────────┴────────────┘

所以可以简单地把嵌套数据结构当做是所有列都是相同长度的多列数组。

SELECT 查询只有在使用 ARRAY JOIN 的时候才可以指定整个嵌套数据结构的名称。更多信息,参考 «ARRAY JOIN 子句»。示例:

SELECT s, nest.x, nest.y FROM nested_test ARRAY JOIN nest;

┌─s─────┬─nest.x─┬─nest.y─┐
│ Hello │ 1 │ 10 │
│ Hello │ 2 │ 20 │
│ World │ 3 │ 30 │
│ World │ 4 │ 40 │
│ World │ 5 │ 50 │
└───────┴────────┴────────┘

不能对整个嵌套数据结构执行 SELECT。只能明确列出属于它一部分列。

对于 INSERT 查询,可以单独地传入所有嵌套数据结构中的列数组(假如它们是单独的列数组)。在插入过程中,系统会检查它们是否有相同的长度。

对于 DESCRIBE 查询,嵌套数据结构中的列会以相同的方式分别列出来。

ALTER 查询对嵌套数据结构的操作非常有限。

元组类型(Tuple)

元组,其中每个元素都有单独的 类型。

不能在表中存储元组(除了内存表)。它们可以用于临时列分组。在查询中,IN 表达式和带特定参数的 lambda 函数可以来对临时列进行分组。更多信息,请参阅 IN 操作符 和 高阶函数。

元组可以是查询的结果。在这种情况下,对于 JSON 以外的文本格式,括号中的值是逗号分隔的。在 JSON 格式中,元组作为数组输出(在方括号中)。

创建元组​

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

tuple(T1, T2, ...)

创建元组的示例:

:) SELECT tuple(1,'a') AS x, toTypeName(x)

SELECT
(1, 'a') AS x,
toTypeName(x)

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

1 rows in set. Elapsed: 0.021 sec.

元组中的数据类型​

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

自动数据类型检测示例:

SELECT tuple(1, NULL) AS x, toTypeName(x)

SELECT
(1, NULL) AS x,
toTypeName(x)

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

1 rows in set. Elapsed: 0.002 sec.

低基数类型

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

语法​

LowCardinality(data_type)

参数

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

描述​

LowCardinality 是一种改变数据存储和数据处理方法的概念。 byconity 会把 LowCardinality 所在的列进行 dictionary coding。对很多应用来说,处理字典编码的数据可以显著的增加 SELECT 查询速度。

使用 LowCarditality 数据类型的效率依赖于数据的多样性。如果一个字典包含少于 10000 个不同的值,那么 byconity 可以进行更高效的数据存储和处理。反之如果字典多于 10000,效率会表现的更差。

当使用字符类型的时候,可以考虑使用 LowCardinality 代替 Enum。 LowCardinality 通常更加灵活和高效。

例子​

创建一个 LowCardinality 类型的列:

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

域

定义​

Domain 类型是特定实现的类型,它总是与某个现存的基础类型保持二进制兼容的同时添加一些额外的特性,以能够在维持磁盘数据不变的情况下使用这些额外的特性。目前 byconity 暂不支持自定义 domain 类型。

如果你可以在一个地方使用与 Domain 类型二进制兼容的基础类型,那么在相同的地方您也可以使用 Domain 类型,例如:

  • 使用 Domain 类型作为表中列的类型
  • 对 Domain 类型的列进行读/写数据
  • 如果与 Domain 二进制兼容的基础类型可以作为索引,那么 Domain 类型也可以作为索引
  • 将 Domain 类型作为参数传递给函数使用
  • 其他

Domains 的额外特性​

  • 在执行 SHOW CREATE TABLE 或 DESCRIBE TABLE 时,其对应的列总是展示为 Domain 类型的名称
  • 在 INSERT INTO domain_table(domain_column) VALUES(…)中输入数据总是以更人性化的格式进行输入
  • 在 SELECT domain_column FROM domain_table 中数据总是以更人性化的格式输出
  • 在 INSERT INTO domain_table FORMAT CSV …中,实现外部源数据以更人性化的格式载入

Domains 类型的限制​

  • 无法通过 ALTER TABLE 将基础类型的索引转换为 Domain 类型的索引。
  • 当从其他列或表插入数据时,无法将 string 类型的值隐式地转换为 Domain 类型的值。
  • 无法对存储为 Domain 类型的值添加约束。

ipv4​

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

基本使用​

CREATE TABLE hits (url String, from IPv4) ENGINE = Memory

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 = Memory;

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

INSERT INTO hits (url, from) VALUES ('https://wikipedia.org', '116.253.40.133')('https://byconity.com', '183.247.232.58')('https://byconity.com/docs/en/', '116.106.34.242');

SELECT * FROM hits;

┌─url────────────────────────────────┬───────────from─┐
│ https://byconity.com/docs/en/ │ 116.106.34.242 │
│ https://wikipedia.org │ 116.253.40.133 │
│ https://byconity.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 = Memory;

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 = Memory;

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

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

SELECT * FROM hits;

┌─url────────────────────────────────┬─from──────────────────────────┐
│ https://byconity.com │ 2001:44c8:129:2632:33:0:252:2 │
│ https://byconity.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) │ ��� │
└───────────────────────────────────────────┴─────────┘

Map(key, value)

Map(key, value) 可以存储 key:value 键值对类型的数据。

参数

内容比较多,参考 Map 功能梳理(WIP) CE 版本部分。

Array(T)

由 T 类型元素组成的数组。

T 可以是任意类型,包含数组类型。 但不推荐使用多维数组,byconity 对多维数组的支持有限。例如,不能存储在 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) │
└───────┴────────────────────┘

使用数据类型​

byconity 会自动检测数组元素,并根据元素计算出存储这些元素最小的数据类型。如果在元素中存在 NULL 或存在 可为空 类型元素,那么数组的元素类型将会变成 可为空。

如果 byconity 无法确定数据类型,它将产生异常。当尝试同时创建一个包含字符串和数字的数组时会发生这种情况 (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)) │
└────────────┴───────────────────────────────┘

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

SELECT array(1, 'a');

SELECT [1, 'a']

Query id: b3b81f4d-a082-4b2f-a63a-784edf94998e


0 rows in set. Elapsed: 1.231 sec.

Received exception from server (version 21.8.7):
Code: 386. DB::Exception: Received from 0.0.0.0:30000. DB::Exception: There is no supertype for types UInt8, String because some of them are String/FixedString and some of them are not: While processing [1, 'a'] SQLSTATE: HY000.

Nullable(typename)

允许用特殊标记 (NULL) 表示«缺失值»,可以与 TypeName 的正常值存放一起。例如,Nullable(Int8) 类型的列可以存储 Int8 类型值,而没有值的行将存储 NULL。

对于 TypeName,不能使用复合数据类型数组和元组。复合数据类型可以包含 Nullable 类型值,例如 Array(Nullable(Int8))。

Nullable 类型字段不能包含在表索引中。

除非在 byconity 服务器配置中另有说明,否则 NULL 是任何 Nullable 类型的默认值。

存储特性​

要在表的列中存储 Nullable 类型值,byconity 除了使用带有值的普通文件外,还使用带有 NULL 掩码的单独文件。 掩码文件中的条目允许 byconity 区分每个表行的 NULL 和相应数据类型的默认值。 由于附加了新文件,Nullable 列与类似的普通文件相比消耗额外的存储空间。

!!! 注意点 "注意点" 使用 Nullable 几乎总是对性能产生负面影响,在设计数据库时请记住这一点

掩码文件中的条目允许 byconity 区分每个表行的对应数据类型的«NULL»和默认值由于有额外的文件,«Nullable»列比普通列消耗更多的存储空间

AggregateFunction(name, types_of_arguments…)

聚合函数的中间状态,可以通过聚合函数名称加-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 (任何 + 如果) 以及 分位数 都为 byconity 中支持的聚合函数。

使用指南​

数据写入​

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

函数使用示例

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

不同于 uniq 和 quantiles 函数返回聚合结果的最终值,以-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)

使用示例​

请参阅 CnchAggregatingMergeTree 的说明

SimpleAggregateFunction

SimpleAggregateFunction(name, types_of_arguments…) 数据类型存储聚合函数的当前值, 并不像 AggregateFunction 那样存储其全部状态。这种优化可以应用于具有以下属性函数: 将函数 f 应用于行集合 S1 UNION ALL S2 的结果,可以通过将 f 分别应用于行集合的部分, 然后再将 f 应用于结果来获得: f(S1 UNION ALL S2) = f(f(S1) UNION ALL f(S2))。 这个属性保证了部分聚合结果足以计算出合并的结果,所以我们不必存储和处理任何额外的数据。

支持以下聚合函数:

  • any
  • anyLast
  • min
  • max
  • sum
  • sumWithOverflow
  • groupBitAnd
  • groupBitOr
  • groupBitXor
  • groupArrayArray
  • groupUniqArrayArray
  • sumMap
  • minMap
  • maxMap
  • argMin
  • argMax

!!! note "注" SimpleAggregateFunction(func, Type) 的值外观和存储方式于 Type 相同, 所以你不需要应用带有 -Merge/-State 后缀的函数。

`SimpleAggregateFunction` 的性能优于具有相同聚合函数的 `AggregateFunction` 。

参数

  • 聚合函数的名称。
  • 聚合函数参数的类型。

示例

CREATE TABLE simple (id UInt64, val SimpleAggregateFunction(sum, Double)) ENGINE=CnchAggregatingMergeTree ORDER BY id;


特殊数据类型

特殊数据类型的值既不能存在表中也不能在结果中输出,但可用于查询的中间结果。

Interval 类型​

表示时间和日期间隔的数据类型家族。 INTERVAL 运算的结果类型。

!!! warning "警告" Interval 数据类型值不能存储在表中。

结构:

  • 时间间隔作为无符号整数值。
  • 时间间隔的类型。

支持的时间间隔类型:

  • SECOND
  • MINUTE
  • HOUR
  • DAY
  • WEEK
  • MONTH
  • QUARTER
  • YEAR

对于每个时间间隔类型,都有一个单独的数据类型。 例如, DAY 间隔对应于 IntervalDay 数据类型:

SELECT toTypeName(INTERVAL 4 DAY)

┌─toTypeName(toIntervalDay(4))─┐
│ IntervalDay │
└──────────────────────────────┘

使用说明​

您可以在与 日期 和 日期时间 类型值的算术运算中使用 Interval 类型值。 例如,您可以将 4 天添加到当前时间:

SELECT now() as current_date_time, current_date_time + INTERVAL 4 DAY

┌───current_date_time─┬─plus(now(), toIntervalDay(4))─┐
│ 2019-10-23 10:58:45 │ 2019-10-27 10:58:45 │
└─────────────────────┴───────────────────────────────┘

不同类型的间隔不能合并。 你不能使用诸如 4 DAY 1 HOUR 的时间间隔. 以小于或等于时间间隔最小单位的单位来指定间隔,例如,时间间隔 1 day and an hour 可以表示为 25 HOUR 或 90000 SECOND.

你不能对 Interval 类型的值执行算术运算,但你可以向 Date 或 DateTime 数据类型的值添加不同类型的时间间隔,例如:

SELECT now() AS current_date_time, current_date_time + INTERVAL 4 DAY + INTERVAL 3 HOUR

┌───current_date_time─┬─plus(plus(now(), toIntervalDay(4)), toIntervalHour(3))─┐
│ 2019-10-23 11:16:28 │ 2019-10-27 14:16:28 │
└─────────────────────┴────────────────────────────────────────────────────────┘

以下查询将导致异常:

select now() AS current_date_time, current_date_time + (INTERVAL 4 DAY + INTERVAL 3 HOUR)

Received exception from server (version 19.14.1):
Code: 43. DB::Exception: Received from localhost:9000. DB::Exception: Wrong argument types for function plus: if one argument is Interval, then another must be Date or DateTime..

表达式​

用于表示高阶函数中的 lambdas 表达式。

集合​

可以用在 IN 表达式的右半部分。

Nothing​

此数据类型的唯一目的是表示不是期望值的情况。 所以不能创建一个 Nothing 类型的值。

例如,字面量 NULL 的类型为 Nullable(Nothing)。详情请见 Nullable。

Nothing 类型也可以用来表示空数组:

:) SELECT toTypeName(array())

SELECT toTypeName([])

┌─toTypeName(array())─┐
│ Array(Nothing) │
└─────────────────────┘

1 rows in set. Elapsed: 0.062 sec.