GaussDB(DWS)函数不同写法引发的结果差异

莫张周刘王  论坛元老 | 2023-8-11 17:24:23 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1013|帖子 1013|积分 3039

本文分享自华为云社区《GaussDB(DWS)函数结果差异案例之greatest》,作者: 你是猴子请来的救兵吗。
GaussDB(DWS)支持多种兼容模式,为了兼容目标数据库,各模式之间或多或少存在一些行为差异。这里分享一个mysql兼容模式下的表达式函数因不同写法引发的结果差异案例。
问题背景

问题版本 GaussDB 8.1.1
问题描述
  1. 用户反馈mysql兼容模式下,以下两条sql的执行结果存在差异:
  2. select greatest(1,2,100,-1,0,nvl(null,0)) 出来的结果是 2
  3. select greatest(1,2,100,-1,0) 出来结果是 100
复制代码
场景再现
  1. mysql=# select greatest(1,2,100,-1,nvl(null,0));
  2. greatest
  3. ----------
  4. 2
  5. (1 row)
  6. mysql=# select greatest(1,2,100,-1,0,0);
  7. greatest
  8. ----------
  9. 100
  10. (1 row)
复制代码
根因分析

1,不知道小伙伴们有没有注意到,这两个结果集的显示一个是靠左的一个是靠右的;ok,我们先来确认下这两个结果的数据类型:
  1. mysql=# select pg_typeof(greatest(1,2,100,-1,nvl(null,0)));
  2. pg_typeof
  3. -----------
  4. text
  5. (1 row)
  6. mysql=# select pg_typeof(greatest(1,2,100,-1,0));
  7. pg_typeof
  8. -----------
  9. integer
  10. (1 row)
复制代码
2,依靠pg_typeof我们拿到了返回结果的数据类型;这就说明第一条语句是以text类型进行排序选择最大值的,依次为(‘0’,‘1’,’-1’,‘100’,‘2’),因此我们得到最大值是字符串类型的’2’。
  1. 0
  2. 1
  3. -1
  4. 100
  5. 2
复制代码
3,依次类推,第二条语句是以int类型进行排序选择最大值的,依次为(-1,0,1,2,100),因此我们得到最大值是数值类型的100。
  1. -1
  2. 0
  3. 1
  4. 2
  5. 100
复制代码
4,表达式函数greatest的返回类型是基于入参类型确定的,这里的差异是由于第五个入参类型导致的结果差异。
  1. mysql=# select pg_typeof(nvl(null,0));
  2. pg_typeof
  3. -----------
  4. text
  5. (1 row)
  6. mysql=# select pg_typeof(0);
  7. pg_typeof
  8. -----------
  9. integer
  10. (1 row)
复制代码
5,而nvl/greatest之所以会出现不同的返回类型,是由mysql兼容模式下的类型匹配规则决定的。
具体规则可参考:UNION,CASE和相关构造
修改建议

针对此差异场景,建议在不确定返回类型时显式指定其入参类型,将nvl(null,0)改为nvl(null,0)::int,这样结果就是已int排序的,与另一台语句预期相符。
  1. mysql=# select greatest(1,2,100,-1,nvl(null,0)::int);
  2. greatest
  3. ----------
  4. 100
  5. (1 row)
复制代码
知识剖析

SQL UNION构造把不相同的数据类型进行匹配输出为统一的数据类型结果集。因为SELECT UNION语句中的所有查询结果必须在一列里显示出来,所以每个SELECT子句中的元素类型必须相互匹配并转换成一个统一的数据类型。同样的要求广泛存在于 UNION、ARRAY 和 CASE、COALESCE、IF、IFNULL 和 GREATEST、LEAST 和 NVL 等表达式和函数中。
GaussDB(DWS)支持多种兼容模式,不同兼容模式下的类型匹配规则也不尽相同。为了便于理解,这里仅以mysql兼容模式下 IFNULL 的类型匹配规则进行举例说明,它与 GREATEST 在mysql兼容模式下的规则是一致的。
规则1: 如果所有输入都是相同的类型,不包括unknown类型,那么解析成所输入的相同数据类型。
  1. mysql=# select pg_typeof(1),pg_typeof(2);
  2. pg_typeof | pg_typeof
  3. -----------+-----------
  4. integer | integer
  5. (1 row)
  6. mysql=# select ifnull(1,2),pg_typeof(ifnull(1,2));
  7. ifnull | pg_typeof
  8. --------+-----------
  9. 1 | integer
  10. (1 row)
复制代码
规则2: 如果所有输入都是unknown类型则解析成text类型。(常量字符串就是unknow类型)
  1. mysql=# select pg_typeof('1'),pg_typeof('2');
  2. pg_typeof | pg_typeof
  3. -----------+-----------
  4. unknown | unknown
  5. (1 row)
  6. mysql=# select ifnull('1','2'),pg_typeof(ifnull('1','2'));
  7. ifnull | pg_typeof
  8. --------+-----------
  9. 1 | text
  10. (1 row)
复制代码
规则3: 如果输入是unknown类型和某一非unknown类型,则解析成该非unknown类型。
  1. mysql=# select pg_typeof(current_date),pg_typeof('20230801');
  2. pg_typeof | pg_typeof
  3. -----------+-----------
  4. date | unknown
  5. (1 row)
  6. mysql=# select ifnull(current_date,'20230801'),pg_typeof(ifnull(current_date,'20230801'));
  7. ifnull | pg_typeof
  8. ------------+-----------
  9. 2023-08-10 | date
  10. (1 row)
复制代码
规则4: 如果存在多种非unknown类型,将enum类型当做text类型,再进行比较。
  1. mysql=# create type gender as enum('boy','girl');
  2. CREATE TYPE
  3. mysql=# select pg_typeof('boy'::gender),pg_typeof('girl'::varchar);
  4. pg_typeof | pg_typeof
  5. -----------+-------------------
  6. gender | character varying
  7. (1 row)
  8. mysql=# select ifnull('boy'::gender,'girl'::varchar),pg_typeof(ifnull('boy'::gender,'girl'::varchar));
  9. ifnull | pg_typeof
  10. --------+-----------
  11. boy | text
  12. (1 row)
复制代码
规则5: 如果输入类型是同一个类型范畴,则选择该类型的优先级较高的类型。如果是不同的类型范畴,则解析成text类型。
  1. --相同类型范畴
  2. mysql=# select pg_typeof(1),pg_typeof(2.0);
  3. pg_typeof | pg_typeof
  4. -----------+-----------
  5. integer | numeric
  6. (1 row)
  7. mysql=# select ifnull(1,2.0),pg_typeof(ifnull(1,2.0));
  8. ifnull | pg_typeof
  9. --------+-----------
  10. 1 | numeric
  11. (1 row)
  12. --不同类型范畴
  13. mysql=# select pg_typeof(1),pg_typeof(current_date);
  14. pg_typeof | pg_typeof
  15. -----------+-----------
  16. integer | date
  17. (1 row)
  18. mysql=# select ifnull(1,current_date),pg_typeof(ifnull(1,current_date));
  19. ifnull | pg_typeof
  20. --------+-----------
  21. 1 | text
  22. (1 row)
复制代码
规则6: 把所有输入转换为所选的类型。如果从给定的输入到所选的类型没有隐式转换则失败。
  1. --json不存在到text的隐式转换
  2. mysql=# select pg_typeof(1),pg_typeof('{"a":1}'::json);
  3. pg_typeof | pg_typeof
  4. -----------+-----------
  5. integer | json
  6. (1 row)
  7. mysql=# select ifnull(1,'{"a":1}'::json),pg_typeof(ifnull(1,'{"a":1}'::json));
  8. ERROR: IFNULL could not convert type json to text
  9. LINE 1: select ifnull(1,'{"a":1}'::json),pg_typeof(ifnull(1,'{"a":1}...
  10. ^
  11. CONTEXT: referenced column: ifnull
  12. --可以尝试显式指定类型转换
  13. mysql=# select ifnull(1,'{"a":1}'::json::text);
  14. ifnull
  15. --------
  16. 1
  17. (1 row)
复制代码
 
点击关注,第一时间了解华为云新鲜技术~

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

莫张周刘王

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表