1.?ClickHouse基础数据类型
道生一,一生二,二生三,三生万物。
——【春秋·老子】道德经·第四十二章??
在第 2 章中,我们通过“一顿猛如虎的操作”,ClickHouse 环境已经准备好。现在,万事俱备只欠东风了。各位看官,先莫急。话说,要想做一道好菜,光有柴米油盐酱醋肉不行,还得有“菜谱”。菜谱是厨师利用烹饪原料,通过各种烹调技法创作出一道好菜的方法。要想成为大厨,不懂选料、切配、烹饪等技艺,那是不行的。要想搞好大数据,那就必须要很懂 Select(选料)、Update/Delete(切配)、Insert(烹饪)等技术才行。
本章先从“选料”开始,主要介绍 ClickHouse 中的“原料”——?数据类型。
1.1.?ClickHouse数据类型数据类型(Data types)是计算机科学的基础。在关系数据库理论中,也需要一个类型系统来支持。因为,关系本身也是定义在某种数据类型之上的。在介绍 SQL 基础之前,我们先来了解 ClickHouse?SQL 中的基本数据类型。
作为一款分析型数据库,ClickHouse 提供了许多数据类型,它们可以划分为:基础类型、复合类型和特殊类型。如下图所示。
其中基础类型使 ClickHouse 具备了描述数据的基本能力,而另外两种类型则使 ClickHouse 的数据表达能力更加的丰富立体。本节先介绍基础类型部分。关于复合类型和特殊类型我们放到第 5?章来介绍。
1.1.1.?概述ClickHouse?基础类型有数值、字符串和时间三大类型。完整的数据类型清单,可以执行如下 SQL 获得:
SELECT * FROM system.data_type_families
输出结果如下表。
从上表可以看出,ClickHouse 的 String 类型、Int 类型、Float 类型、Decimal类型等都是大小写敏感的(case_sensitive=1)。需要注意的是,ClickHouse 中的类型是区分大小写的,比如 Array、UInt8、String、DateTime、Float64?等。同时,ClickHouse 中的绝大部分函数也是区分大小写的,例如:array()、arrayCount() 、arrayJoin()、?bitmapAnd() 、bitmapCardinality()、concat() 、dateDiff() 、addDays() 等等。
另外,ClickHouse 中没有 true、false值的布尔类型(Bool),所以,一般用整型(UInt8)表示布尔类型,1 为真,0 为假。
为了快速理解 ClickHouse 中的基础数据类型,我们用熟悉的 MySQL 中的数据类型进行比较,如下表。
上面的ClickHouse 与 MySQL 数据类的对应关系可以到 ClickHouse 源代码convertMySQLDataType.cpp中找到。为了方便阅读,节省大家的查阅的时间,这里直接贴出相关的源码:
#include "convertMySQLDataType.h"
#include <Core/Field.h>
#include <base/types.h>
#include <Core/MultiEnum.h>
#include <Core/SettingsEnums.h>
#include <Parsers/ASTFunction.h>
#include <Parsers/IAST.h>
#include "DataTypeDate.h"
#include "DataTypeDateTime.h"
#include "DataTypeDateTime64.h"
#include "DataTypeEnum.h"
#include "DataTypesDecimal.h"
#include "DataTypeFixedString.h"
#include "DataTypeNullable.h"
#include "DataTypeString.h"
#include "DataTypesNumber.h"
#include "IDataType.h"
namespace DB
{
DataTypePtr convertMySQLDataType(MultiEnum<MySQLDataTypesSupport> type_support,
????????const std::string & mysql_data_type,
????????bool is_nullable,
????????bool is_unsigned,
????????size_t length,
????????size_t precision,
????????size_t scale)
{
????// Mysql returns mysql_data_type as below:
????// 1. basic_type
????// 2. basic_type options
????// 3. type_with_params(param1, param2, ...)
????// 4. type_with_params(param1, param2, ...) options
????// The options can be unsigned, zerofill, or some other strings.
????auto data_type = std::string_view(mysql_data_type);
????const auto type_end_pos = data_type.find_first_of(R"(( )"); // FIXME: fix style-check script instead
????const auto type_name = data_type.substr(0, type_end_pos);
????DataTypePtr res;
????if (type_name == "tinyint")
????{
????????if (is_unsigned)
????????????res = std::make_shared<DataTypeUInt8>();
????????else
????????????res = std::make_shared<DataTypeInt8>();
????}
????else if (type_name == "smallint")
????{
????????if (is_unsigned)
????????????res = std::make_shared<DataTypeUInt16>();
????????else
????????????res = std::make_shared<DataTypeInt16>();
????}
????else if (type_name == "int" || type_name == "mediumint")
????{
????????if (is_unsigned)
????????????res = std::make_shared<DataTypeUInt32>();
????????else
????????????res = std::make_shared<DataTypeInt32>();
????}
????else if (type_name == "bigint")
????{
????????if (is_unsigned)
????????????res = std::make_shared<DataTypeUInt64>();
????????else
????????????res = std::make_shared<DataTypeInt64>();
????}
????else if (type_name == "float")
????????res = std::make_shared<DataTypeFloat32>();
????else if (type_name == "double")
????????res = std::make_shared<DataTypeFloat64>();
????else if (type_name == "date")
????????res = std::make_shared<DataTypeDate>();
????else if (type_name == "binary")
????????res = std::make_shared<DataTypeFixedString>(length);
????else if (type_name == "datetime" || type_name == "timestamp")
????{
????????if (!type_support.isSet(MySQLDataTypesSupport::DATETIME64))
????????{
????????????res = std::make_shared<DataTypeDateTime>();
????????}
????????else if (type_name == "timestamp" && scale == 0)
????????{
????????????res = std::make_shared<DataTypeDateTime>();
????????}
????????else if (type_name == "datetime" || type_name == "timestamp")
????????{
????????????res = std::make_shared<DataTypeDateTime64>(scale);
????????}
????}
????else if (type_name == "bit")
????{
????????res = std::make_shared<DataTypeUInt64>();
????}
????else if (type_support.isSet(MySQLDataTypesSupport::DECIMAL) && (type_name == "numeric" || type_name == "decimal"))
????{
????????if (precision <= DecimalUtils::max_precision<Decimal32>)
????????????res = std::make_shared<DataTypeDecimal<Decimal32>>(precision, scale);
????????else if (precision <= DecimalUtils::max_precision<Decimal64>) //-V547
????????????res = std::make_shared<DataTypeDecimal<Decimal64>>(precision, scale);
????????else if (precision <= DecimalUtils::max_precision<Decimal128>) //-V547
????????????res = std::make_shared<DataTypeDecimal<Decimal128>>(precision, scale);
????}
????/// Also String is fallback for all unknown types.
????if (!res)
????????res = std::make_shared<DataTypeString>();
????if (is_nullable)
????????res = std::make_shared<DataTypeNullable>(res);
????return res;
}
}
从最后的几行代码中可以看到,在 ClickHouse 中针对所有的未知类型,都是用String 来填充。关于 ClickHouse 数据类型的相关源码,在src/DataTypes?目录下。一个典型的关于 ClickHouse 全部数据类型清单价的代码片段在IDataType.h中。
1.1.2.?数值类型ClickHouse数值类型分为整数类型、浮点数类型和 Decimal 类型三大类, 各个大类自己又分别有8、16、32、64、128、256?位等不同位数的细分。清单如下表。
在 ClickHouse 源码DataTypesNumber.cpp中,定义了如上数值类型。关键代码如下:
void registerDataTypeNumbers(DataTypeFactory & factory)
{
????factory.registerDataType("UInt8", createNumericDataType<UInt8>);
????factory.registerDataType("UInt16", createNumericDataType<UInt16>);
????factory.registerDataType("UInt32", createNumericDataType<UInt32>);
????factory.registerDataType("UInt64", createNumericDataType<UInt64>);
????factory.registerDataType("Int8", createNumericDataType<Int8>);
????factory.registerDataType("Int16", createNumericDataType<Int16>);
????factory.registerDataType("Int32", createNumericDataType<Int32>);
????factory.registerDataType("Int64", createNumericDataType<Int64>);
????factory.registerDataType("Float32", createNumericDataType<Float32>);
????factory.registerDataType("Float64", createNumericDataType<Float64>);
????factory.registerSimpleDataType("UInt128", [] { return DataTypePtr(std::make_shared<DataTypeUInt128>()); });
????factory.registerSimpleDataType("UInt256", [] { return DataTypePtr(std::make_shared<DataTypeUInt256>()); });
????factory.registerSimpleDataType("Int128", [] { return DataTypePtr(std::make_shared<DataTypeInt128>()); });
????factory.registerSimpleDataType("Int256", [] { return DataTypePtr(std::make_shared<DataTypeInt256>()); });
????/// These synonyms are added for compatibility.
????factory.registerAlias("TINYINT", "Int8", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("INT1", "Int8", DataTypeFactory::CaseInsensitive); ???/// MySQL
????factory.registerAlias("BYTE", "Int8", DataTypeFactory::CaseInsensitive); ???/// MS Access
????factory.registerAlias("SMALLINT", "Int16", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("INT", "Int32", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("INTEGER", "Int32", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("BIGINT", "Int64", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("FLOAT", "Float32", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("REAL", "Float32", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("SINGLE", "Float32", DataTypeFactory::CaseInsensitive); ??/// MS Access
????factory.registerAlias("DOUBLE", "Float64", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("MEDIUMINT", "Int32", DataTypeFactory::CaseInsensitive); ???/// MySQL
????factory.registerAlias("DOUBLE PRECISION", "Float64", DataTypeFactory::CaseInsensitive);
????/// MySQL
????factory.registerAlias("TINYINT SIGNED", "Int8", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("INT1 SIGNED", "Int8", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("SMALLINT SIGNED", "Int16", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("MEDIUMINT SIGNED", "Int32", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("INT SIGNED", "Int32", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("INTEGER SIGNED", "Int32", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("BIGINT SIGNED", "Int64", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("TINYINT UNSIGNED", "UInt8", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("INT1 UNSIGNED", "UInt8", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("SMALLINT UNSIGNED", "UInt16", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("MEDIUMINT UNSIGNED", "UInt32", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("INT UNSIGNED", "UInt32", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("INTEGER UNSIGNED", "UInt32", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("BIGINT UNSIGNED", "UInt64", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("BIT", "UInt64", DataTypeFactory::CaseInsensitive); ?/// MySQL
????factory.registerAlias("SET", "UInt64", DataTypeFactory::CaseInsensitive); ?/// MySQL
????factory.registerAlias("YEAR", "UInt16", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("TIME", "Int64", DataTypeFactory::CaseInsensitive);
}
通过源码可以看出,ClickHouse SQL数据类型做了针对MySQL、MS Access等数据类型的兼容处理。接下来,我们通过SQL实例介绍这些数据类型的使用。
IntClickHouse 整数类型有:UInt8, UInt16, UInt32, UInt64, UInt128, UInt256, Int8, Int16, Int32, Int64, Int128, Int256。其中,U 开头的是无符号整型,也就是大于等于 0 的。后面的 8、16、32、64、128、256分别是各个类型所占的 bit 位数。
整型范围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]
我们可以通过内置的数学函数?exp2(n) 计算2的 n 次幂。比如说 UInt8?的取值范围是[0:255], 我们可以计算出来这个最大值就是:
SELECT exp2(8) - 1
执行结果:
┌minus(exp2(8), 1)?┐
│ ????????????255????│
└──────────┘
UInt256的最大值是:
SELECT exp2(256) - 1
输出结果:
┌──minus(exp2(256), 1)─┐
│ 1.157920892373162e77 ???│
└──────────────┘
可以看到,在终端输出的是1.157920892373162e77?科学计数法的结果。但是我们想要看具体的数字,这个时候我们可能想用类型转换函数 toUInt256(x) 来实现。但是,事与愿违,ClickHouse?Client 终端执行的结果并不是我们所期望的。
SELECT toUInt256(exp2(256)) - 1
minus(toUInt256(exp2(256)), 1)
57896044618658097702369839901263932781391731748390190090761097376371310591999
因为,exp2(x) 返回值类型是 Float64,精度有丢失。
执行如下 SQL:
select toUInt256(115792089237316195423570985008687907853269984665640564039457584007913129639935)
输出:
SELECT toUInt256(1.157920892373162e77)
toUInt256(1.157920892373162e77)
57896044618658097702369839901263932781391731748390190090761097376371310592000
可以发现结果也不是我们期望的。
执行如下 SQL 来看一下UInt256?上限值在 ClickHouse 中的类型是什么:
select toTypeName(115792089237316195423570985008687907853269984665640564039457584007913129639935);
输出:
Float64
可以看出,并不是 UInt256。Float64?在进行计算的时候,会有精度丢失:小数点后除去左边的零后第17位起会产生数据溢出。
需要注意的是,UInt128、UInt256能表示的整数范围十分巨大,占用的字节大小也随之增大,用得比较少。
FloatClickHouse 浮点数有:单精度浮点数是Float32(等同于C语言中的类型float)和双精度浮点数是Float64(等同于C语言中的类型double),遵循IEEE 754浮点数标准。如下表。
对浮点数进行计算可能引起四舍五入的误差。
实例?SQL:
SELECT
????1 - 0.9,
????toTypeName(1 - 0.9)
输出:
┌───────minus(1, 0.9)─┬─toTypeName(minus(1, 0.9))─┐
│ 0.09999999999999998 ?????│ ??Float64 ??????????????????│
└──────────────┴──────────────┘
可以看出,小数点后除去左边的零后第17位起产生数据溢出,计算结果是 Float64?类型。
因为浮点数有精度丢失,所以在实际工作中,我们通常都使用整数形式存储数据,将固定精度的数字转换为整数值。例如货币单位使用分,页面加载时间用毫秒为单位表示等。
无穷与非数字值类型另外,ClickHouse 的浮点数支持正无穷(inf)、负无穷(-inf)以及非数字(nan)特殊数值符号。通过执行如下 SQL可以看到结果。
SELECT
????1 / 0,
????-1 / 0,
????0 / 0,
????sqrt(-1)
输出结果:
┌─divide(1, 0)─┬─divide(-1, 0)─┬─divide(0, 0)─┬─sqrt(-1)─┐
│ ????????inf │ ????????-inf ??│ ??????nan │ ?????nan │
└──────┴───────┴───────┴──────┘
另外,?inf/-inf 得到的结果是 nan。
执行 SQL:
SELECT (1 / 0) / (-1 / 0)
输出:
Query id: 52de21e8-d82d-4fdd-9050-a9e8ff4b3bae
┌─divide(divide(1, 0), divide(-1, 0))┐
│ ???????????nan ??????????????│
└───────────────┘
DecimalClickHouse 中高精度数值类型为Decimal,也称为为定点数。
用法:Decimal(P,S)
其中,
P:总位数?= 整数位数?+ 小数位数,取值范围是[1,76]。也叫精度(precision)。
S:小数位数,取值范围是[0,P]。也叫规模(scale)。
定点数加减计算通过使用toDecimal32(value, S)?函数来进行定点数的计算。SQL 实例:
SELECT
????1 - 0.9,
????toDecimal64(1 - 0.9, 4),
????toDecimal64(1.23, 3) + toDecimal64(1.234, 5),
????toDecimal64(1.23, 3) - toDecimal64(1.234, 5)
执行结果:
minus(1, 0.9):0.09999999999999998
toDecimal64(minus(1, 0.9), 4):0.0999
plus(toDecimal64(1.23, 3), toDecimal64(1.234, 5)):2.464
minus(toDecimal64(1.23, 3), toDecimal64(1.234, 5)):-0.004
定点数乘法运算乘法运算时,小数位数S为两者之和。
执行 SQL:
SELECT toDecimal32(22.9876312, 3) * toDecimal32(33.123450011, 2)
输出:
multiply(toDecimal32(22.9876312, 3), toDecimal32(33.123450011, 2)):761.32944
定点数除法运算除法运算时,小数位数S为被除数位数。此时要求 S(被除数) > S(除数),否则报错。
实例?SQL:
SELECT toDecimal64(22.9876312, 3) / toDecimal64(33.123450011, 7)
输出:
Received exception from server (version 21.12.1):
Code: 69. DB::Exception: Received from localhost:9000. DB::Exception: Decimal result's scale is less than argument's one: While processing toDecimal64(22.9876312, 3) / toDecimal64(33.123450011, 7). (ARGUMENT_OUT_OF_BOUND)
另外,因为当前计算机系统只支持 32 或者 64 位,所以高于这个位数的数字类型:Int128、Int256、UInt128、UInt256、Decimal128、Decimal256等,其实是在软件层面实现的,性能上会比 Int32、Int64、UInt32、UInt64、Decimal32、Decimal64等硬件支持的数据类型要差很多。
1.1.3.?字符串类型ClickHouse?中字符串类型主要包括:
l?不定长字符串String,也就是动态长度变化字符串。
l?固定长度字符串FixedString(N)。这里的N是最大字节数,而不是长度,例如UTF-8字符占用3个字节,GBK字符占用2个字节。
l?特殊字符串UUID。背后存储的是数值。
ClickHouse中的字符串,没有针对字符作编码操作。字符串可以包含一组任意字节,这些字节按原样存储和输出。编码和解码操作留给客户端。如果存储文本类型内容,ClickHouse推荐使用UTF-8编码。读取和写入数据都约定统一使用UTF-8编码。
StringString类型不限制字符串的长度,可以直接替代其他数据库的VARCHAR、BLOB、CLOB等字符串类型,相比VARCHAR这类要考虑预测数据最大长度,显然String要方便很多。
实例?SQL:
SELECT
????'123',
????'abc',
????toTypeName('123'),
????toTypeName('abc')
输出:
┌─'123'─┬─'abc'─┬─toTypeName('123')─┬─toTypeName('abc')─┐
│ 123 ???│ abc ???│ String ??????????????│ String ?????????????│
└────┴────┴───────────┴──────────┘
下面的 SQL 展示了在 ClickHouse 中计算字符串的长度和截取字串的方法。
select length('abc'), substring('abc', 2), substring('abc', 1);
输出:
┌─length('abc')─┬─substring('abc', 2)─┬─substring('abc', 1)─┐
│ ? ? ? ? ? ? 3 ??│ bc? ? ? ? ? ? ? ????? │ abc ? ? ? ? ? ? ? ? │
└───────────────┴─────────────────────┴─────────────────────┘
另外,需要注意的是,ClickHouse?中的String字符串是用单引号的。用双引号,会报错。
实例。终端执行:
select "123"
输出:
SELECT `123`
Received exception from server (version 21.12.1):
Code: 47. DB::Exception: Received from localhost:9000. DB::Exception: Missing columns: '123' while processing query: 'SELECT `123`', required columns: '123'. (UNKNOWN_IDENTIFIER)
通过报错日志:Missing columns: '123' ,可以看出,对双引号里面的内容,ClickHouse 是当成列名来处理的。
我们可以通过源码?DataTypeString.cpp 了解ClickHouse 中的?String类型与其他数据库的等价类型。相关代码如下。
void registerDataTypeString(DataTypeFactory & factory)
{
????factory.registerDataType("String", create);
????/// These synonims are added for compatibility.
????factory.registerAlias("CHAR", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("NCHAR", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("CHARACTER", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("VARCHAR", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("NVARCHAR", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("VARCHAR2", "String", DataTypeFactory::CaseInsensitive); /// Oracle
????factory.registerAlias("TEXT", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("TINYTEXT", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("MEDIUMTEXT", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("LONGTEXT", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("BLOB", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("CLOB", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("TINYBLOB", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("MEDIUMBLOB", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("LONGBLOB", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("BYTEA", "String", DataTypeFactory::CaseInsensitive); /// PostgreSQL
????factory.registerAlias("CHARACTER LARGE OBJECT", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("CHARACTER VARYING", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("CHAR LARGE OBJECT", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("CHAR VARYING", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("NATIONAL CHAR", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("NATIONAL CHARACTER", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("NATIONAL CHARACTER LARGE OBJECT", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("NATIONAL CHARACTER VARYING", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("NATIONAL CHAR VARYING", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("NCHAR VARYING", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("NCHAR LARGE OBJECT", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("BINARY LARGE OBJECT", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("BINARY VARYING", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("VARBINARY", "String", DataTypeFactory::CaseInsensitive);
????factory.registerAlias("GEOMETRY", "String", DataTypeFactory::CaseInsensitive); //mysql
}
FixedStringFixedString 类型存储定长字符串。
用法:FixedString(N)
其中,N代表N个字节(N bytes)。
当写入FixedString类型数据的时候,如果数据字节数大于N,则会返回一个Too large value for FixedString(N)的异常。如果数据字节数小于N,则会使用NULL 字节来填充末尾字符。
在查询条件WHERE中,如果需要匹配FixedString类型的列,传入的查询参数要自行补尾部的\0,否则有可能导致查询条件失效,因为要求写入数据和查询条件都是固定字节数。
实例。查询语句:
SELECT * FROM FixedStringTable WHERE a = 'b'
不会返回任何结果。需要使用空字节来填充筛选条件。改成这样即可:
SELECT * FROM FixedStringTable?WHERE a = 'b\0'
使用FixedString类型的典型场景:
l?存储IP地址,如使用FixedString(16)存储IPV6地址二进制值。
l?存储哈希值,如FixedString(16)存储MD5的二进制值,FixedString(32)存储SHA256的二进制值。
实例。使用 toFixedString() 函数来生成 FixedString。执行 SQL:
SELECT
????toFixedString('abc', 5) AS s,
????toTypeName(s),
????length(s)
输出:
┌─s──┬toTypeName(toFixedString('abc', 5))┬length(toFixedString('abc', 5))┐
│ abc ?│ FixedString(5) ??????????????????????│ ??????????????????????????????5 ?????│
└───┴─────────────────────┴───────────────────┘
UUIDUUID?(Universally Unique IDentifier , 通用唯一标识符)是一个16字节的数字,用于标识记录。UUID也称为 GUID(全球唯一标识符)。
UUID核心特性:
l?全局时空唯一性
l?固定长度128比特,也就是16字节(1 byte = 8 bit)
l?分配速率极高,单机每秒可以生成超过1000万个UUID(实际上更高)
UUID 最初用于Apollo 网络计算系统和后来的开放软件Foundation 的 (OSF) 分布式计算环境 (DCE)。UUID规范参考:https://www.ietf.org/rfc/rfc4122.txt。
UUID 在编程语言中通常是一种SDK 提供的类型,例如Java中的UUID.randomUUID()是用SecureRandom实现的完全随机数,这是一种在密码学意义上安全的随机数,随机位越多越安全。而在 ClickHouse 中直接把它作为一种数据类型。
生成 UUIDClickHouse提供了内置函数generateUUIDv4()?来生成 UUID。
SQL 实例:
SELECT generateUUIDv4()
输出:
┌─generateUUIDv4()───────────┐
│fa7a3205-90c2-47ba-b6db-3b1a6fd385c1│
└──────────────────────┘
从上面的结果可以看到,UUID 共有 32 位(中间的分隔符不算),它的格式为 8-4-4-4-12。UUID 类型默认值为:00000000-0000-0000-0000-000000000000。
我们可以用toTypeName() 函数看一下UUID 的数据类型。
SQL实例:
SELECT
????generateUUIDv4() AS uuid,
????toTypeName(uuid)
输出:
┌─uuid───────────────────┬─toTypeName(generateUUIDv4())─┐
│ 1a55960c-161a-493e-ba96-725ec453ae91 │ UUID ??????????????????????????????│
└───────────────────────┴──────────────────┘
字符串转 UUID可以通过toUUID() 函数把符合格式的String字符串?(如果格式不对,会报Cannot parse UUID from String?错误) 转成 UUID 类型。
SQL 实例:
SELECT
????toUUID('1a55960c-161a-493e-ba96-725ec453ae91') AS uuid,
????toTypeName(uuid)
输出:
uuid:1a55960c-161a-493e-ba96-725ec453ae91
toTypeName(): UUID
??Tips: UUID算法简介
开放软件基金会(OSF)规范定义了包括网卡MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素。利用这些元素来生成UUID。
UUID是由一组32位数的16进制数字所构成,由128位二进制组成?(可能的组合数量2^128,几乎不可能生成重复项,除非你能做到每秒生成 10 亿个 UUID,并持续大约 85 年?),显示为 (128/4) = 32 个十六进制数字。一般转换成十六进制,然后用String表示。
UUID 生成算法通常有 4 个版本,如下表。
上面位置 X1 的数字始终为“4”,位置 X2 的数字始终为“8”、“9”、“A”或“B”之一。典型的 UUID 字符串如下:
ccfbd0e9-e284-42be-bf62-35631ab61890
97dc2c56-210e-4d24-be4b-ec1580ec3712
ddca7525-a5f3-418b-9965-51bba972879c
c02bbcc0-f1c1-49ee-8f1c-901b10487e3f
dad54c4b-377f-4500-9dc3-f31021372d47
919b3557-e98d-4c24-bce5-e85c9dbac0b2
4317a815-9f51-46b8-8b1b-2f5f75a1cd6d
4999f513-5494-412a-8cb0-9eaebc89ebf4
1.1.4.?日期时间类型时间数据类型有 DateTime、DateTime64、Date和Date32。其中,Date使用 2 个字节(16bits)存储,DateTime、Date32使用 4?个字节(32bits)存储,DateTime64用 8 个字节存储。
首先,我们看一下计算当前时间的 SQL:
SELECT now()
输出:
┌────now()────┐
│2022-03-01 20:23:09│
└───────────┘
获取当前时区:
SELECT timeZone()
┌─timeZone()──┐
│ Asia/Shanghai │
└─────────┘
Date日期类型。用2个字节(16 bits)存储,表示从 1970-01-01到当前的日期值。最小值为0000-00-00。日期取值范围:[1970-01-01, 2149-06-06]。
Date类型只精确到天,并且和 DateTime、DateTime64 一样,支持字符串写入。Date 类型中没有时分秒信息,存储的日期值不带时区。
日期字符串转 Date 类型SQL 实例:
SELECT
????toDate('2022-03-02') AS dt,
????toTypeName(dt)
FORMAT Vertical
输出:
dt: 2022-03-02
toTypeName(toDate('2022-03-02')): Date
计算当前日期距离 1970-01-01?总共多少天SQL 实例:
SELECT
????toDate('2022-03-02') AS dt,
????toTypeName(dt),
????toInt32(dt)
FORMAT Vertical
输出:
dt: 2022-03-02
toTypeName(toDate('2022-03-02')): Date
toInt32(toDate('2022-03-02')): ???19053
dateDiff() 函数我们可以使用dateDiff() 函数来验证上面的19053天是否一致:
SELECT dateDiff('day', toDate('1970-01-01'), toDate('2022-03-02'))
FORMAT Vertical
输出:19053
Date 的取值范围计算原理另外,我们也可以看一下 Date 的取值范围计算原理。因为 Date 使用 2 个字节存储,也就是 16 bits,取值范围是??= 65535。我们可以用下面的 SQL 来验证:
SELECT
????dateDiff('day', toDate('1970-01-01'), toDate('2149-06-06')) AS days,
????exp2(16) - 1 AS int16val
FORMAT Vertical
输出:
days: ??65535
int16val: 65535
Date32日期类型。支持与Datetime64相同的日期范围。存储占用4个字节。日期取值范围:[1925-01-01, 2283-11-11]。我们可以试验一下使用 Date 来赋值1925-01-01的效果。
SQL:
SELECT
????toDate('1925-01-01') AS dt1,
????toDate32('1925-01-01') AS dt2
FORMAT Vertical
输出:
dt1: 2104-06-07
dt2: 1925-01-01
可以看出,toDate32('1925-01-01') 正确解析了日期1925-01-01。而toDate('1925-01-01')解析成了2104-06-07,其背后的计算逻辑简单分析如下。
首先,我们使用 toInt32()来看看背后真正存储的值是多少:
SELECT
????toDate('1925-01-01') AS dt1,
????toDate32('1925-01-01') AS dt2,
????toInt32(dt1) AS i1,
????toInt32(dt2) AS i2
FORMAT Vertical
输出:
dt1: 2104-06-07
dt2: 1925-01-01
i1: ?49100
i2: ?-16436
这个 49100?是这个逻辑算出来的:
SELECT dateDiff('day', toDate('1970-01-01'), toDate('1925-01-01'))
输出:49100?
所以,toDate('1925-01-01')?计算逻辑就是:
SELECT addDays(toDate('1970-01-01'), 49100)
输出:2104-06-07。
DateTime时间戳类型。存储占用4个字节(32?位)存储 Unix 时间戳。时间戳类型值精确到秒。DateTime 类型包含年、月、日、时、分、秒信息,支持使用字符串的方式写入。时间戳支持的有效值范围:[1970-01-01 00:00:00, 2106-02-07 06:28:15](UTC 时区)。最小值为0000-00-00 00:00:00。
计算DateTime 取值上限同样的逻辑,我们可以通过具体的 SQL计算出时间戳取值范围。
SQL实例:
SELECT addSeconds(toDateTime('1970-01-01 00:00:00', 'UTC'), exp2(32) - 1)
输出:2106-02-07 06:28:15
当前时间函数 now()当前时间函数 now()返回的是一个时间戳DateTime类型。
SQL 实例:
SELECT
????now() AS now,
????toTypeName(now)
FORMAT Vertical
输出:
now: ?2022-03-02 02:03:44
toTypeName(now()): DateTime
使用toInt32()函数计算具体秒数使用toInt32()函数,可以计算当前时间距离1970-01-01 00:00:00具体的秒数。
SQL:
SELECT
????now() AS now,
????toInt32(now),
????toUnixTimestamp(now)
FORMAT Vertical
输出:
now: ???????????????????????2022-03-02 02:57:05
toInt32(now()): ??????????1646161025
toUnixTimestamp(now()): 1646161025
我们可以使用dateDiff() 函数来验证这个?1646161025?数字。
SQL:
SELECT dateDiff('second', toDateTime('1970-01-01 00:00:00'), toDateTime('2022-03-02 02:57:05'))
FORMAT Vertical
输出:1646161025
字符串转成DateTime通过使用toDateTime() 函数可以把字符串转成DateTime。
SQL实例:
SELECT
????toDateTime('2022-03-02 02:03:44') AS dt,
????toTypeName(dt)
FORMAT Vertical
输出:
dt: 2022-03-02 02:03:44
toTypeName(toDateTime('2022-03-02 02:03:44')): DateTime
DateTime64时间戳类型。支持定义亚秒精度。DateTime64类型在内部以Int64类型存储数据,值是自1970-01-01 00:00:00UTC开始的时间刻度数(ticks)。时间戳取值范围:[1925-01-01 00:00:00,2283-11-11 23:59:59.9999999],最大值的精度为8。
使用DateTime64类型语法:DateTime64(precision, [timezone]),时间刻度的精度由precision参数确定。
实例 SQL:
SELECT toDateTime64(now(), 3, timeZone())
输出:2022-03-02 03:16:00.000
过滤 DateTime64 类型的值与 DateTime 不同, DateTime64 类型的值不会自动从 String 类型的值转换过来,需要单独调用类型转换函数?toDateTime64() 。
实例 SQL:
SELECT * FROM t WHERE timestamp = toDateTime64('2022-03-02?03:21:05', 3, 'Asia/Shanghai')
1.2.?本章小结数学中有整数、有理数、无理数、复数等数域的概念,而在计算机科学中,数据类型仍然是非常重要的概念——无论是在程序设计语言、算法或是数据库系统。关于类型理论的思想来源于哲学中的归类(classification)方法论,即把具有共同特点的类集合成超类的思维过程和方法。在计算机领域也有专门的类型理论研究。从最基础的类型开始,然后进行各种嵌套、组合就可以实现复杂领域模型的抽象。
ClickHouse支持多种数据类型。数据模型定义具体字段属性(例如,String、Int、Long、Double?...),每个字段的属性值,又都定义为某种具体的数据类型。本节主要介绍的是 ClickHouse基础数据类型。既然有基础类型,就有高级一点的“复合数据类型”。在下一章中,我们将会介绍 ClickHouse高级数据类型与函数主题。
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。 |
标签: #ClickHouse #字符串转array #春秋老子道德经第四十二章在第 #2 #环境已经