众所周知,在SQL语言系统中,我们每一条插入存储的数据记录都需与其位置的数据类型相符合。换言之,数据类型决定了数据的行为和含义。GBase 8c扫描/分析器只将词法元素分解成五个基本种类:整数、浮点数、字符串、标识符和关键字。大多数非数字类型首先表现为字符串。例如text数据类型的列只能存入符合text类型的数据,而不能任意存入整数、浮点数等其他类型。

但实际上,我们总会有需要进行数据类型转换的场景,大多数类型转换是由通用规则来管理的,这种做法允许使用混合类型的表达式。在南大通用GBase 8c数据库管理系统中,有四种基本的SQL结构需要独立的类型转换规则:

  • 函数调用

多数SQL类型系统是建筑在一套丰富的函数上的。函数调用可以有一个或多个参数。因为SQL允许函数重载,所以不能通过函数名直接找到要调用的函数,分析器必须根据函数提供的参数类型选择正确的函数。

  • 操作符

SQL允许在表达式上使用前缀或后缀(单目)操作符,也允许表达式内部使用双目操作符(两个参数)。像函数一样,操作符也可以被重载,因此操作符的选择也和函数一样取决于参数类型。

  • 值存储

INSERT和UPDATE语句将表达式结果存入表中。语句中的表达式类型必须和目标字段的类型一致或者可以转换为一致。

  • UNION,CASE和相关构造

因为联合SELECT语句中的所有查询结果必须在一列里显示出来,所以每个SELECT 子句中的元素类型必须相互匹配并转换成一个统一类型。类似地,一个CASE构造的结果表达式必须转换成统一的类型,这样整个case表达式会有一个统一的输出类型。同样的要求也存在于ARRAY构造以及GREATEST和LEAST函数中。

本文主要围绕函数调用规则进行说明,期间可能穿插几个简单的例子。

(1)函数规则是怎么作用的呢?

  1. 当我们指定类型转换函数时,GBase 8c自动在系统表pg_proc选择相关函数。如果使用函数时没有指定模式名称修饰,那么认为该函数是在当前搜索路径中;如果给出一个带修饰的函数名,那么只考虑指定模式中的函数。如果搜索路径中找到了多个不同参数类型的函数。将从中选择一个合适的函数。
  2. 找到输入参数类型与之完全匹配的函数。但如果输入的实参类型都是unknown类型,则不会找到匹配的函数。
  3. 如果未找到完全匹配,请查看该函数是否为一个特殊的类型转换函数。
  4. 寻找最优匹配。抛弃那些输入类型不匹配并且也不能隐式转换成匹配的候选函数。unknown 文本在这种情况下可以转换成任何东西。如果只剩下一个候选项,则用之, 否则继续下一步。
  • 遍历所有候选函数,保留那些输入类型匹配最准确的。此时,域被看作和它们的基本类型相同。如果没有一个函数能准确匹配,则保留所有候选。如果只剩下一个候选项,则用之,否则继续下一步。
  • 遍历所有候选函数,保留那些需要类型转换时接受首选类型位置最多的函数。如果没有接受首选类型的函数,则保留所有候选。如果只剩下一个候选项,则用之,否则继续下一步。
  • 如果有任何输入参数是unknown类型,检查剩余的候选函数对应参数位置的类型范畴。在每一个能够接受字符串类型范畴的位置使用string类型。另外,如果所有剩下的候选函数都接受相同的类型范畴,则选择该类型范畴,否则报错。如果只有一个候选函数符合, 则使用该函数;否则,继续下一步。
  • 如果同时有unknown和已知类型的参数,并且所有已知类型都是相同的类型,则会假设unknown参数也是这种类型,此时检查哪个候选函数可以在unknown参数位置接受这种类型。如果正好一个候选符合,那么使用它。否则报错。

(2)使用示例

示例1

圆整函数参数类型解析。只有一个round函数有两个参数(第一个是numeric,第二个是integer)。所以下面的查询自动把第一个类型为integer的参数转换成numeric类型。

postgres=# SELECT round(4, 4); 
round
--------
4.0000
(1 row)

实际上它被分析器转换成:

postgres=# SELECT round(CAST (4 AS numeric), 4);

因为带小数点的数值常量初始时被赋予numeric类型,因此下面的查询将不需要类型转换,并且可能会略微高效一些:

postgres=# SELECT round(4.0, 4);

示例2

子字符串函数类型解析。有好几个substr函数,其中一个接受text和integer类型。如果用一个未声明类型的字符串常量调用它,系统将选择接受string类型范畴的首选类型(也就是text类型)的候选函数。

postgres=# SELECT substr('1234', 3); 
substr
--------
34
(1 row)

如果该字符串声明为varchar类型,就像从表中取出来的数据一样,分析器将试着将其转换成text类型:

postgres=# SELECT substr(varchar '1234', 3); 
substr
--------
34
(1 row)

被分析器转换后实际上变成:

postgres=# SELECT substr(CAST (varchar '1234' AS text), 3);

分析器从pg_cast表中了解到text和varchar是二进制兼容的,意思是说一个可以传递给接受另一个的函数而不需要做任何物理转换。因此,在这种情况下,实际上没有做任何类型转换。而且,如果以integer为参数调用函数,分析器将试图将其转换成text类型:

postgres=# SELECT substr(1234, 3); 
substr
--------
34
(1 row)

被分析器转换后实际上变成:

postgres=# SELECT substr(CAST (1234 AS text), 3); 
substr
--------
34
(1 row)

(3)小结

数据类型转换是一种常见的操作,帮助我们处理不同类型的数据。类型转换规则对于不同的应用场景尤其是数据迁移或同步时,能够极大提高数据处理的灵活性,保持数据一致性,避免因数据不匹配导致的繁杂操作。