前言

这篇文章可能是你至今(2022-02-11)能在互联网看到的,关于utl_raw包的逻辑说得最深入的一篇文章了。
由于最近在复刻oracle中自带的包到其他数据库,因此需要对oracle中的包的逻辑进行解析。
比如UTL_RAW这个包,以前用得挺多,但没深究其函数逻辑,这次仔细分析,发现了有一些函数涉及到了一些计算机基本原理及IEEE标准,比较有意思,因此写一篇这样的文章来分享。

函数清单

先上官方文档 UTL_RAW
再贴个函数清单,稍微翻译了下,方便理解

SubprogramDescription中文描述
BIT\AND FunctionPerforms bitwise logical "and" of the values inRAWr1withRAWr2and returns the "anded" resultRAW计算两个raw的“位与”并返回结果的RAW
BIT\COMPLEMENT FunctionPerforms bitwise logical "complement" of the values inRAWrand returns the "complement'ed" resultRAW对 in 中的值执行按位逻辑“补码”RAWr并返回“补码”结果RAW
BIT\OR FunctionPerforms bitwise logical "or" of the values inRAWr1withRAWr2and returns the "or'd" resultRAW计算两个raw的“位或”并返回结果的RAW
BIT\XOR FunctionPerforms bitwise logical "exclusive or" of the values inRAWr1withRAWr2and returns the "xor'd" resultRAW计算两个raw的“位异或”并返回结果的RAW
CAST\FROM\BINARY\DOUBLE FunctionReturns theRAWbinary representation of aBINARY\DOUBLEvalue返回值的二进制RAW表示的双精度浮点
CAST\FROM\BINARY\FLOAT FunctionReturns theRAWbinary representation of aBINARY\FLOATvalue返回值的二进制RAW表示的单精度浮点
CAST\FROM\BINARY\INTEGER FunctionReturns theRAWbinary representation of aBINARY\INTEGERvalue返回值的二进制RAW表示的整数
CAST\FROM\NUMBER FunctionReturns theRAWbinary representation of aNUMBERvalue返回值的二进制RAW表示的数值
CAST\TO\BINARY\DOUBLE FunctionCasts theRAWbinary representation of aBINARY\DOUBLEinto aBINARY\DOUBLE将一个RAW二进制表示形式的双精度浮点转换为 双精度浮点数字
CAST\TO\BINARY\FLOAT FunctionCasts theRAWbinary representation of aBINARY\FLOATinto aBINARY\FLOAT将一个RAW二进制表示形式的单精度浮点转换为 单精度浮点数字
CAST\TO\BINARY\INTEGER FunctionCasts theRAWbinary representation of aBINARY\INTEGERinto aBINARY\INTEGER将一个RAW二进制表示形式的整数转换为 数字
CAST\TO\NUMBER FunctionCasts theRAWbinary representation of aNUMBERinto aNUMBER将一个RAW二进制表示形式数值转换为 成一个数值
CAST\TO\NVARCHAR2 FunctionConverts aRAWvalue into aVARCHAR2value将RAW值转换为NVARCHAR2值
CAST\TO\RAW FunctionConverts aVARCHAR2value into aRAWvalue将VARCHAR2值转换为RAW值
CAST\TO\VARCHAR2 FunctionConverts a RAW value into aVARCHAR2value将 RAW 值转换为VARCHAR2值
COMPARE FunctionComparesRAWr1againstRAWr2比较两个raw
CONCAT FunctionConcatenates up to 12RAWsinto a singleRAW最多可将 12 个连接RAWs成一个RAW
CONVERT FunctionConvertsRAWrfrom character setfrom\charsetto character setto\charsetand returns the resultingRAW将一个raw从字符集from\charset转换为字符集to\charset并返回结果RAW
COPIES FunctionReturnsncopies ofrconcatenated together返回n个重复的raw拼接的结果
LENGTH FunctionReturns the length in bytes of aRAWr返回一个RAW的字节长度
OVERLAY FunctionOverlays the specified portion of targetRAWwith overlayRAW, starting from byte positionposof target and proceeding forlenbytes使用指定的RAW,对目标raw的pos字节位置开始,len字节长度进行覆盖
REVERSE FunctionReverses a byte sequence inRAWrfrom end to end反转RAW字节序列
SUBSTR FunctionReturnslenbytes, starting atposfromRAWr对一个raw,从pos位置开始,截取len字节,返回结果
TRANSLATE FunctionTranslates the bytes in the inputRAWraccording to the bytes in the translationRAWsfrom\setandto\set对于输入的raw,将from\set中的字节逐个替换成to\set中对应位置的字节
TRANSLITERATE FunctionConverts the bytes in the inputRAWraccording to the bytes in the transliterationRAWsfrom\setandto\set实际上和translate差不多,但多了个补充长度的参数
XRANGE FunctionReturns aRAWcontaining all valid 1-byte encodings in succession, beginning with the valuestart\byteand ending with the valueend\byte从start\byte开始到end\byte的所有值拼接成一个raw返回

此包中的大部分函数,均已使用openGauss的plpgsql语言进行了实现,可结合代码来阅读本文,代码地址
https://gitee.com/darkathena/opengauss-oracle/blob/main/oracle-package/utl\raw.sql

详解

注意,由于oracle中,sql语言可以隐式的将十六进制字符串转换成raw类型,所以注意下文中十六进制的参数的类型应该均为raw。在plsql中,则需要使用hextoraw函数进行显式的转换以确保输入参数的准确性

1.BIT\AND

作用:
计算位与
例:

SELECT UTL_RAW.BIT_AND('F0','BA') FROM DUAL
--输出:B0

算法:
十六进制 F0 转二进制 11110000
十六进制 BA 转二进制 10111010
两个数字按位,逐个进行匹配,相等输出1,不等输出0
得10110000,然后转十六进制,得B0

2.BIT\OR

作用:
计算位或
例:

SELECT UTL_RAW.BIT_OR('F0','BA') FROM DUAL
--输出:FA

算法:
十六进制 F0 转二进制 11110000
十六进制 BA 转二进制 10111010
两个数字按位,逐个进行匹配,至少有个1则输出1,都是0则输出0
得11111010,然后转十六进制,得FA

3.BIT\XOR

作用:
计算位异或
例:

SELECT UTL_RAW.BIT_XOR('F0','BA') FROM DUAL
--输出:4A

算法:
十六进制 F0 转二进制 11110000
十六进制 BA 转二进制 10111010
两个数字按位,逐个进行匹配,相等输出0,不等输出1
得01001010,然后转十六进制,得4A

4.BIT_COMPLEMENT

作用:
计算补码
例:

select UTL_RAW.BIT_COMPLEMENT('EA') FROM DUAL
--输出:15

算法:
EA转二进制 11101010
用二进制 11111111减去它(或者理解为1变0,0变1),
得二进制 00010101,再转十六进制,得15

5.CONCAT

作用:
拼接多个raw,最多拼12个
例:

select UTL_RAW.CONCAT('EAAB','3A') FROM DUAL
--输出 :EAAB3A

算法:
EAAB 转二进制 1110101010101011
3A 转二进制 00111010
计算第二个参数,字节长度为1,占8位,因此第一个参数的二进制向左移动8位,
得111010101010101100000000,
然后加上第二个参数的2进制,得 111010101010101100111010,转十六进制得 EAAB3A
(可以看到,此例如果是把十六进制数值当成字符串来进行拼接,结果是一样的,但要注意是按字节长度拼接,比如 "FF"拼"F",得到应该是 "FF0F",而不是字符串拼接的"FFF")

6.LENGTH

作用:
计算长度,单位:字节
例:

select UTL_RAW.LENGTH('EAEA') FROM DUAL
--输出:2

算法:
EAEA转二进制 1110101011101010
从后往前数,8位为1个字节,不足8位的前面补0
(可以将十六进制数值视同为字符串,该字符串长度的一半即为原数据的字节长度,但同样要注意省去了前面的0的情况,比如"FFF"按字符串的长度是1.5,明显不对,应该计算"0FFF"长度的一半)

7.SUBSTR

作用:
按字节截取
例:

select UTL_RAW.SUBSTR('ABCDEF12345',3,2) FROM DUAL
--输出:EF12

算法:
(转二进制过程略)
AB 为一个字节, CD为一个字节,从第3字节 EF 开始,截取 2个字节长度,即 EF 12
(和字符串的substr类似,可以理解为2个字符一组,第3个参数为空时表示一直截到最后;第2个参数同样支持负数,即从倒数第n位开始截;但是第3个参数要截的长度不能超过raw的剩余长度,否则会报错ora-06502)

8.COPIES

作用:
复制自己成多份
例:

select utl_raw.COPIES('1A1B',3) FROM DUAL
--输出:1A1B1A1B1A1B

算法:
(转二进制过程略)
1A1B字节长度为2,往左移两个字节长度,得1A1B0000
再加上自己,得1A1B1A1B,继续左移两个字节长度,得1A1B1A1B0000
再加上自己,得1A1B1A1B1A1B
可以发现左移次数为第2个参数减1次
(也可以简单的视为字符串拼接,但同样要注意前面补0,比如utl_raw.COPIES('FFF',2) 的结果应该是0FFF0FFF)

9.REVERSE

作用:
按字节进行翻转
例:

select utl_raw.REVERSE('01020304') from dual
--输出:04030201

算法:
(转二进制过程略)
01020304字节长度为4,
取第4个字节04,左移3个字节,得04000000
取第3个字节03,左移2个字节,得00030000
取第2个字节02,左移1个字节,得00000200
取第1个字节01,得00000001
把上面4个数加起来,得04030201
(也可以简单的视为字符串,只不过是2个字符为一组进行翻转,不要忘了补0)

10.XRANGE

作用:
根据传入的两个参数作为开始数值和结束数值,以1递增,生成一串序列,注意两个参数均只接受第一个字节,后面的字节会被忽略
例:

select utl_raw.XRANGE('0A','12') FROM DUAL
--输出:0A0B0C0D0E0F101112

算法:
(转二进制过程略)
0A左移一个字节,得0A00,然后0A加1,得0B,0A00加上0B得0A0B
0A0B左移一个字节,得0A0B00,然后0B加1得0C,0A0B00加上0C得0A0B0C
循环下去,直至中间某次加1后的值等于第二个参数12,加上最后一个值后返回结果,
得0A 0B 0C 0D 0E 0F 10 11 12

11.TRANSLITERATE

作用:
按字节进行替换,(由于第二个参数和第三个参数的长度允许不一致,因此以填充码的方式,确保原数据和输出结果的长度一致)
例:

select utl_raw.transliterate( '010203040502', '0809', '01020304', '0a' ) FROM DUAL
--输出:08090A0A0509

算法:
(转二进制过程略)
第一个参数是原数据,第二个参数是要替换成的数据,第三个参数是要查找的数据,第4个参数是填充码
此例中,对第一个参数进行以下字节的覆盖
01 -> 08
02 -> 09
03 -> 空(取填充码0A)
04 -> 空(取填充码0A)
得输出结果 08 09 0A 0A 05 09

12.TRANSLATE

作用:
按字节进行替换(无填充码参数,因此输入和输出的长度可能不一致)
例:

select utl_raw.translate( '0102030405', '0203', '06' ) from dual
--输出 01060405

算法:
和TRANSLITERATE函数类似,不过没有填充码,没对应上的则替换成空(二进制数据需要右移),而且注意第二个参数和第三个参数反过来了

13.OVERLAY

作用:
按指定开始字节及长度覆写字节数据
用法:
第一个参数为覆写码,第二个参数为原始数据,第3个参数为开始字节位置(默认值为1,表示从第一个字节开始),第4个参数为覆盖字节长度(默认值为剩余的所有字节长度),第5个参数为填充码(默认值为00)

select utl_raw.overlay( 'AABB', '010203' ) from dual
--输出 AABB03
select utl_raw.overlay( 'AABB', '010203', 2 ) from dual
--输出 01AABB
select utl_raw.overlay( 'AABB', '010203', 5 ) from dual
--输出 01020300AABB
select utl_raw.overlay( 'AABB', '010203', 2, 1 ) from dual
--输出 01AA03
select utl_raw.overlay( 'AABB', '010203', 5, 1, 'FF' ) from dual
--输出 010203FFAA

算法:(以最后一个为例)
(转二进制过程略)
AABB 字节长度为 2,已经超过第4个参数1个字节的长度,因此截断,只取第1个字节AA
010203 字节长度为 3
从第5个字节开始覆写,已经超过原始数据长度,差值为2,因此010203先左移1个字节,得 01020300
由于还未到第5个字节,所以加上填充码FF,得010203FF,
再左移1个字节,得010203FF00,在第5位加上覆写码AA,得010203FFAA

14.COMPARE

作用:
按字节从左至右的顺序比较两个raw的差异,返回第一个不匹配的字节位置,如果完全匹配则返回0
用法:
第一个参数raw,第二个参数raw,第3个参数为填充码(默认值为00),返回一个整数

SELECT utl_raw.compare( '010203', '01020304', '04' ) from dual
--输出 0
SELECT utl_raw.compare( '01050304', '01020304' )
--输出 2

算法:
(转二进制过程略)
第一例,由于前两个参数长度不一致,且第一个参数长度较短,因此使用第三个参数对其进行填充至相同长度,得 01020304,所以两个完全一致,返回0
第二例,可以发现第二个字节 05 不等于 02,因此返回2

15.CAST_TO_RAW

作用:
将varchar2类型的数据的二进制数据转换成raw类型
例:

select  UTL_RAW.cast_to_RAW('DarkAthena') from dual
--输出 4461726B417468656E61
select  UTL_RAW.cast_to_RAW('123') from dual
--输出 313233
select  UTL_RAW.cast_to_RAW(convert('测试','AL32UTF8')) from dual
--输出 E6B58BE8AF95

算法:
略,
注意第二例,传入的参数是字符,不是数字,
第三例中,对于非单字节字符,强制指定字符集进行写入,否则写入的raw不可控

16.CAST_TO_VARCHAR2

作用:
将raw格式的字符串使用数据库默认字符集转换回VARCHAR2(其实就是CAST_TO_RAW函数的逆向函数)
例:

select  UTL_RAW.cast_to_RAW('4461726B417468656E61') from dual
--输出 DarkAthena

算法:
略,
注意,由于此函数不能指定字符集,因此转换回来可能会乱码,可以使用utl_i18n.raw_to_char这个函数进行替代

17.CONVERT

作用:
转换RAW的字符集
例:

--先用cast_to_RAW获得一个raw
select  UTL_RAW.cast_to_RAW(convert('测试','AL32UTF8')) from dual
--输出 E6B58BE8AF95
--转换成gbk
select  UTL_RAW.CONVERT('E6B58BE8AF95','ZHS16GBK','AL32UTF8') FROM DUAL
--输出 B2E2CAD4

算法:略
可用下列方式验证

select  UTL_RAW.cast_to_RAW(convert('测试','ZHS16GBK')) from dual
--输出 B2E2CAD4

18.CAST_FROM_BINARY_INTEGER

作用:
将一个整数的二进制数据转换成RAW
用法:第1个参数为要进行转换的整数,第二个参数为大端还是小端,默认大端为1,小端为2,传3为取机器配置的端

select utl_raw.cast_from_binary_integer(65280) from dual
--输出 0000FF00
select utl_raw.cast_from_binary_integer(65280,2) from dual
--输出 00FF0000

算法:
其实就是把十进制的整数转换成其二进制数据的十六进制展现形式,但最长只能4个字节,可转换的整数范围为
-2147483648~2147483647
即大端7FFFFFFF~80000000
(7FFFFFFF及以下为正整数,80000000及以上为负整数,00000000为0,FFFFFFFF为-1)
但是要注意第2个参数,关于大小端是什么意思,可自行搜索,不过在结果展现上来看,其实小端就是把大端按字节给翻转了

19.CAST_TO_BINARY_INTEGER

作用:
将一个整数的raw转换回整数,其实就是CAST\FROM\BINARY\INTEGER的逆向函数
用法:第1个参数为要进行转换的raw,第二个参数为大端还是小端,默认大端为1,小端为2,传3为取机器配置的端

select utl_raw.cast_to_binary_integer('18FCFFFF') from dual
--输出 419233791
select utl_raw.cast_to_binary_integer('18FCFFFF') from dual
--输出 -1000

算法:
略,注意此处可以传入小于或者等于4个字节的raw,超过4个字节会报错

20.CAST_FROM_NUMBER

作用:
获得一个number类型值存储的二进制数据
例:

select utl_raw.cast_from_number(1.1) from dual
--输出 C1020B
select utl_raw.cast_from_number(-1.1) from dual
--输出 3E645B66

算法:
这里内容会比较多,目前国内很难找到关于这个算法的中文说明,用英文关键词去搜也很难找到,但是我搜到了一个CSDN上08年的帖子
关于utl_raw.cast_to_number

这位兄弟启发了我,让我能稍微看懂了一点,于是我模拟了几组数据,尝试在excel中按照此方式进行转换
从RAW解析正数

从RAW解析负数

模拟成功,但是这只是RAW到NUMBER,如果要计算NUMBER到RAW,得把这个步骤完全反过来

另外,我还根据193、101这些特殊的数字,找到了oracle的官方文档
3 Datatypes

模拟计算表格文件下载

21.CAST_TO_NUMBER

作用:
将一个number值存储的二进制数据转换回number,其实就是CAST\FROM\NUMBER的逆向函数
例:

select utl_raw.CAST_TO_NUMBER (C1020B) from dual
--输出 1.1
select utl_raw.cast_from_number(3E645B66) from dual
--输出 -1.1

算法:
略,见上面的CAST\FROM\NUMBER

22.CAST_TO_NVARCHAR2

作用:
把RAW转换回NVARCHAR2
例:

select utl_raw.CAST_TO_NVARCHAR2('6D4B8BD5') from dual
--输出:测试

算法:
其实NVARCHAR2类型就是用unicode编码来存储数据,可以使用unistr或者asciistr函数验证

select asciistr('测试') from dual
--输出: \6D4B\8BD5
select unistr('\6D4B\8BD5') from dual
--输出: 测试

但是,要注意的是,对于ascii字符集中的字符(单字节字符),NVARCHAR2存储的每个字符的字节前面都要补1个00字节,确保每个字符占的存储空间都是2个字节,比如
NVARCHAR2类型的 "测试12ab" 存储的二进制数据(十六进制形式)为 6D4B 8BD5 0031 0032 0061 0062
而asciistr函数并不会对除"\"以外的单字节字符进行转换

23.

CAST\FROM\BINARY\FLOAT/CAST\TO\BINARY\FLOAT/CAST\FROM\BINARY\DOUBLE/CAST\TO\BINARY\DOUBLE

最后还剩4个函数,分别是转换单精度浮点和双精度浮点的
这个其实是遵循 IEEE二进制浮点数算术标准(IEEE 754)(百度百科)
所以暂不细说了,前面能看懂的自然能看懂百科里说的算法。
但要注意的是,由于这两种浮点数值存在精度问题,经常会出现类似10.00000000000000001或者9.9999999999999999这种奇葩的数据,因此不建议在需要准确数值的场景下使用,建议使用number类型

总结

有人可能会说,既然oracle提供了这些函数,直接用就好了么,干嘛去分析原理?
首先,如果要让oracle数据库里的对象能实现真正的无缝迁移至其他数据库,肯定需要在其他数据库内对oracle里的逻辑进行模拟,此时必须清楚oracle里的逻辑是怎样的;
其次,了解这些原理后,能对以后开发时应该选择哪种数据类型或者使用哪个函数提供指导依据,比如哪种方式计算快或者怎么存储更节省空间。比如这个问答中的回复就使用到了相关知识 oracle里面number(20,2)数据类型,数据精度是20,小数位数是2。那它的数据长度是多少?

最后修改:2022 年 04 月 02 日
如果觉得我的文章对你有用,请随意赞赏