数据类型转换规则
表达式求值的类型转换
当表达式中的运算符与不同类型的操作数一起使用时,会发生数据类型转换以使操作数兼容。
转换方式
seekdb 支持显式数据类型转换和隐式数据类型转换。
隐式数据类型转换发生在这样的场景中:一个操作需要一个指定数据类型的参数,但语句的实际参数值并不是指定数据类型,这时 seekdb 会将实际参数值转为指定数据类型后,再进行后续操作。
如下例所示,seekdb 会根据需要自动将字符串转换为数字,反之亦然。
SELECT 1+'1';
+-------+
| 1+'1' |
+-------+
| 2 |
+-------+
1 row in set (0.001 sec)
seekdb 的显示数据类型转换通过 CAST 函数实现。如下例所示:
SELECT 31.4, CAST(31.4 AS TIME);
+------+--------------------+
| 31.4 | CAST(31.4 AS TIME) |
+------+--------------------+
| 31.4 | 00:00:31 |
+------+--------------------+
1 row in set (0.001 sec)
有关 CAST 函数的相关信息,请参见 CAST。
转换规则
比较运算的转换规则
-
如果一个或两个参数为
NULL,则比较结果为NULL,但<=>比较运算符除外。对于NULL <=> NULL,结果为真,不需要转换。 -
如果比较运算中的两个参数都是字符串,则将它们作为字符串进行比较。
-
如果两个参数都是整数,则将它们作为整数进行比较。
-
如果不与数字进行比较,十六进制值将被视为二进制字符串。
-
来自一个或多个表的单行子查询不被视为常量。例如,如果子查询返回的整数要与
DATETIME值进行比较,则将两个操作数作为整数进行。由于整数不会被转换为时间值,所 以要将操作数作为DATETIME值进行比较,请使用CAST()将子查询值显式转换为DATETIME。如果其中一个参数是
TIMESTAMP或DATETIME列,而另一个参数是常量,则在执行比较之前,该常量将转换为时间戳。为安全起见,在进行比较时始终使用完整的日期时间、日期或时间字符串。例如,在BETWEEN与日期或时间值一起使用时,要想获得最佳结果,请使用CAST()将值显式转换为所需的数据类型。 -
如果其中一个参数是十进制值,则比较类型取决于另一个参数。如果另一个参数是十进制或整数值,则将参数作为十进制值进行比较,如果另一个参数是浮点值,则作为浮点值进行比较。
-
除上述以外的情况,参数将作为浮点(实数)数进行比较。例如,字符串和数字操作数的比较是作为浮点数的比较进行的。
浮点数和
INTEGER类型的大值之间的比较是近似的,因为整数在比较之前被转换为双精度浮点数,无法准确表示所有 64 位整数。
类型降级
在满足比较语义的条件下,尽可能让常量往列类型对齐,从而实现高效的比较。目前支持标准语义下绝大部分列和常量的比较,剩余会在列上加 cast 的比较,后面单独列出:
int或uint列,支持常量的是字符类型、decimal类型、浮点数(double/float)类型降级到整型列。decimal列,支持浮点数(double/float) 类型降级到decimal列。year列,支持数值类型(包含整型、浮点数、decimal类型)、字符类型、时间类型(包含date、datetime、timestamp、time等)降级到year列。date列,支持字符类型、datetime、timestamp、time类型降级到date列。timestamp列,支持datetime降级到timestamp。
目前会在列上加 cast 的场景(不考虑 array,xml,json 等复杂类型)如下:
float列,float列的比较总是使用double进行比较,所以float列和任意常量比较都会加cast。fixed double列,即建表指定 ps,比如double(10, 2), 当常量的小数位更多时,会对齐精度比较,此时会在列上加cast。- 字符列,字符和数值类型的比较是按照数值序比较,会把列
cast成数值,另外字符集的比较规则也可能在列上加上cast。
非标比较
非标比较的逻辑和类型降级处理类似,都是只处理常量和列的比较,如果是列和列的比较,则不会处理。目前仅支持整型常量和字符列的比较,注意,如果设置成 range,可能排序的结果不符合标准语义,配置时需要根据数据特征选取。
下面示例说明:
-
创建了一个名为
t1的表,其中有一个biz_day列,类型为varchar(10)。CREATE TABLE t1 (biz_day varchar(10));
Query OK, 0 rows affected (0.216 sec) -
查看默认情况下的执行计划。
EXPLAIN SELECT * FROM t1 WHERE biz_day BETWEEN 20200101 AND 20200201;
+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Query Plan |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| =============================================== |
| |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
| ----------------------------------------------- |
| |0 |TABLE FULL SCAN|t1 |1 |3 | |
| =============================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([t1.biz_day]), filter([cast(t1.biz_day, DECIMAL(-1, -1)) >= cast(20200101, DECIMAL(20, 0))], [cast(t1.biz_day, DECIMAL(-1, -1)) <= cast(20200201, |
| DECIMAL(20, 0))]), rowset=16 |
| access([t1.biz_day]), partitions(p0) |
| is_index_back=false, is_global_index=false, filter_before_indexback[false,false], |
| range_key([t1.__pk_increment]), range(MIN ; MAX)always true |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
12 rows in set (0.003 sec)- 查询计划:默认情况下,查询优化器会将
biz_day列从字符串类型转换为数字类型,然后进行比较。 - 过滤条件:
filter([cast(t1.biz_day, DECIMAL(-1, -1)) >= 20200101], [cast(t1.biz_day, DECIMAL(-1, -1)) <= 20200201])表示将biz_day转换为数字后进行范围比较。
- 查询计划:默认情况下,查询优化器会将
-
设置
non_standard_comparison_level为equal然后查看执行计划。EXPLAIN SELECT /*+opt_param('non_standard_comparison_level', 'equal')*/ * FROM t1 WHERE biz_day BETWEEN 20200101 AND 20200201;
+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Query Plan |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ====================================================== |
| |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
| ------------------------------------------------------ |
| |0 |COLUMN TABLE FULL SCAN|t1 |1 |3 | |
| ====================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([t1.biz_day]), filter([cast(t1.biz_day, DECIMAL(-1, -1)) >= cast(20200101, DECIMAL(20, 0))], [cast(t1.biz_day, DECIMAL(-1, -1)) <= cast(20200201, |
| DECIMAL(20, 0))]), rowset=16 |
| access([t1.biz_day]), partitions(p0) |
| is_index_back=false, is_global_index=false, filter_before_indexback[false,false], |
| range_key([t1.__pk_increment]), range(MIN ; MAX)always true |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
12 rows in set (0.003 sec)- 查询计划:设置
non_standard_comparison_level为equal后,查询优化器将biz_day列从字符串类型转换为十进制类型,然后进行比较。 - 过滤条件:
filter([cast(t1.biz_day, DECIMAL(-1, -1)) >= cast(20200101, DECIMAL(20, 0))], [cast(t1.biz_day, DECIMAL(-1, -1)) <= cast(20200201, DECIMAL(20, 0))])表示将biz_day转换为十进制后进行范围比较。由于between是范围查询,所以equal的等值比较规则不会转换常量类型。
- 查询计划:设置
-
设置
non_standard_comparison_level为range然后查看执行计划。EXPLAIN SELECT /*+opt_param('non_standard_comparison_level', 'range')*/ * FROM t1 WHERE biz_day BETWEEN 20200101 AND 20200201;
+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Query Plan |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| =============================================== |
| |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
| ----------------------------------------------- |
| |0 |TABLE FULL SCAN|t1 |1 |3 | |
| =============================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([t1.biz_day]), filter([cast(t1.biz_day, DECIMAL(-1, -1)) >= cast(20200101, DECIMAL(20, 0))], [cast(t1.biz_day, DECIMAL(-1, -1)) <= cast(20200201, |
| DECIMAL(20, 0))]), rowset=16 |
| access([t1.biz_day]), partitions(p0) |
| is_index_back=false, is_global_index=false, filter_before_indexback[false,false], |
| range_key([t1.__pk_increment]), range(MIN ; MAX)always true |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
12 rows in set (0.002 sec)- 查询计划:设置
non_standard_comparison_level为range后,查询优化器将整型常量转换为字符串类型,然后进行比较。 - 过滤条件:
filter([t1.biz_day >= demote_cast(20200101, VARCHAR(10))], [t1.biz_day <= demote_cast(20200201, VARCHAR(10))])表示将整型常量转换为字符串后进行范围比较。非标比较后按照字符序比较,比如'202002010'属于字符序的['20200101', '20200201'],但是不属于数值序[20200101, 20200201]的范围里。
- 查询计划:设置
其他相关转换规则
-
从字符串到浮点数和从整数到浮点数的转换方式不一定相同。结果可能会受到编译器版本等因素的影响。避免此类问题的一种方法是使用
CAST()以防止该值不会隐式转换为浮点数。 -
将数值或时间值隐式转换为字符串会产生一个值,该值具有由
character_set_connection和collation_connection系统变量确定的字符集和字符序。这意味着此类转换会生成非二进制字符串(
CHAR、VARCHAR或LONGTEXT值),如果将连接字符集设置为二进制,则转换结果是二进制字符串(BINARY、VARBINARY或LONGBLOB值)。 -
对于整数表达式求值时的类型转换有些不同。例如,当您用一个整数表达式作为 CREATE TABLE 语句的一部分时,根据表达式的结果类型,所创建的新表中将包含类型为
INT或BIGINT的列:CREATE TABLE t SELECT integer_expr;如果表达式的最大长度不适合
INT,则使用BIGINT。但可以通过使用足够长的表达式来强制使用BIGINT而不是INT:CREATE TABLE t SELECT 000000000000000000000;
Query OK, 1 row affected (0.145 sec)
隐式数据类型转换规则
当数据类型转换有意义时,seekdb 会自动将一个值从一种数据类型转换为另一种数据类型。
下表为所有数据类型隐式转换矩阵,不需要考虑转换的方向或转换的上下文。
| 数据类型 | BOOL | INT | SMALLINT | MEDIUMINT | BIGINT | SERIAL | DECIMAL | NUMERIC | FLOAT | DOUBLE | BIT | DATETIME | TIMESTAMP | DATE | TIME |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| YEAR | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| CHAR | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| VARCHAR | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| BINARY | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| VARBINARY | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| TEXT | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| TINYBLOB | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| MEDIUMBLOB | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| LONGBLOB | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| TINYTEXT | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| MEDIUMTEXT | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| LONGTEXT | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| ENUM | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| SET | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| BLOB | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
隐式数据类型转换示例
SELECT CAST(BOOL_COLUMN AS YEAR) FROM YOUR_TABLE;
+---------------------------+
| CAST(BOOL_COLUMN AS YEAR) |
+---------------------------+
| 2001 |
+---------------------------+
2 rows in set (0.001 sec)