跨越Oracle和MSSQL关系数据库开发 -- 03不同数据库的位运算

1. 概述

  本文探讨如何在MSSQLSybase,及Oracle三种数据库中进行位运算。由于系统设计原因,MSSQLSybase可以很好的支持位运算,但是Oracle的愿生类型并不支持位运算。本文将介绍MSSQLSybase位运算的不同之处,然后介绍如何在Oracle中模拟位运算。

 

2. MSSQLSybase中的位运算

2.1 支持位运算的数据类型

  通常,对于应用程序来说,位运算仅对整数有意义。在MSSQLSYBASE中,有三种数据类型用来表示整数。其定义和值范围见下表描述。

类型

字节数

值范围

tinyint

1

028-1

smallint

2

-215215-1

int

4

-231231-1

 

可以看出,这些数据类型正好使用了字节的倍数来容纳其值,如同C语言一样。这样的数据类型是最适合于位运算的。

MSSQL中还有一个bigint类型,它使用8个字节来容纳更大范围的整数。但是在Sybase中没有对应的类型。

2.2 整数的二进制表达

要介绍位运算,必须首先知道如何用二进制来表达整数。在MSSQL中,二进制常量均以 0x(一个零和小写字母x)开始,后面跟着位模式的十六进制表示。在Sybase中,以0x开始的数表示的仅为二进制,即只能出现0或者1

因此,对于一些数字来说,虽然MSSQLSybase采用同样的表示方式,但是含义有所不同。原因在于MSSQL的十六进制数的形式对应于SYBASE的二进制数,而不是SYBASE的十六进制数。 是不是感到这话有点饶口?还是让例子来澄清一切吧。 打开MSSQL的查询分析器或者SYBASESQL Advantage,运行语句,结果如下表所示。

数据库

表达式

结果

MSSQL

Select 0x1 + 0

1

SYBASE

Select 0x1 + 0

16777216

MSSQL

Select 0x001 + 0

1

SYBASE

Select 0x001 + 0

65536

MSSQL

Select 0x00001 + 0

1

SYBASE

Select 0x00001 + 0

256

MSSQL

Select 0x0000001 + 0

1

SYBASE

Select 0x0000001 + 0

1

为何值的差异如此之大?原因在于上面表格中SYBASE的值是二进制数,不是十六进制数。SYBASE的二进制数以0x开头,最长允许8位。如果不足8位,则在右边添加0;如果超过8位,则截取左边8位。其值的计算可以用下面的公式来表示:

v = v12 * 2^24 + v34 * 2^16 + v56 * 2^8 + v78 * 2^0

其中:v12表示第12位组成的数的十进制值。v34v56v78的含义依次类推。 还必须说明,v12的填充方式是左填零,即101的值是相同的。SYBASE二进制数这种表示方式十分拗口。根据这个规则,来分析下面的SYBASE数字:

Sybase数字

右填充0后的结果

V12

V34

V56

V78

结果

0x1

01000000

1

0

0

0

1 * 2^24

0x10

10000000

2

0

0

0

2 * 2^24

0x000011

00001100

0

0

3

0

3 * 2^8

  本人认为,上面最难理解的是0x1为何没有填充成0x10000000!本人建议在书写Sybase的二进制数字时最好写成偶数位,以便理解。

 

那么SYBASE的十六进制到底怎么表示呢?很简单,把MSSQL的十六进制数变成字符串即可,并且用hextoint函数来取得它的值。

例如,MSSQL的语句

if @action_in & 0x0ff

可以变成SYBASE的语句

      if @action_in & hextoint(‘0x0ff’)

 注意,十六进制数采用左边填充零,’0x0ff’ 等于’0x000000ff’ 将十进制转为十六进制的函数是inttohex

SYBASE之所以采用字符串的形式来表示十六进制时为了做到平台无关性。MSSQL完全不用考虑这个问题,因为它从来就只支持Windows

 

位运算符通常对整数有效。这里必须明确指出,二进制值本身不是整数,所以不能直接写以下语句:

select 0x001 & 0x011

MSSQL会提示出错。

但是如果写成

select 1 & 0x011

就可以进行计算。原因在于在这个语句中0x011会被自动转换成1的数据类型tinyint

 

MSSQL中,要查看一个整数的二进制形式,可以使用CAST函数或者CONVERT函数。

例如:

select convert(varbinary, 435455)

或者

select cast(435455 as varbinary)

显示结果为:

0x0006A4FF

 

Sybase中,可以使用inttohex函数来显示整数的二进制形式。

2.3 位运算操作

有四种基本的位操作运算:按位与&,按位或|,取反~,异或^

本文不介绍这些操作的详细用法,因为在操作手册上已经说明清楚。

通常不建议在数据库中大量使用位操作。数据库是进行数据存取的合适地点,但是不是进行数据运算的合适地点。

在基本操作的基础上,可以发展出其他一些有用的操作。本人在实际应用中使用过很多操作,以下是其中一些例子:

·         取某一位的值

·         设置某一位的值

·         计算非零位个数

·         交换两个位的值

下面占用一些篇幅来实现一下上面的几个例子。

2.3.1 取某一位的值

由于二进制是由01组成,取某一位的值等价于判断该位是01。因此,我们可以使用“按位与”的方法来取值。

·         如果想取第n位值,可使用:

case @v_value & power(2,n-1) when 0 then 0 else 1 end

例如,我们要取一个变量的第四位的值,可以另其与24-1即二进制值1000即十六进制值0x08进行与运算,如果结果是0,则该位值是0,否则为1

MSSQLSybase中,可用以下语句实现:

select (case @v_value & power(2,4-1) when 0 then 0 else 1 end)

Sybase中,还可以更加明确的直接写出二进制:

select (case @v_value & 0x00001000 when 0 then 0 else 1 end)

2.3.2 设置某一位的值

在设置某一位的值时,由于结果取决于给定值(01),不取决于原始值,因此给定值必须能够屏蔽原始值的影响。给定值不同,采用的方法也不同。

·         如果想把第n位值设置成1,可以使用“或”方法:

@v_value | power(2, n-1)

例如,执行以下语句将把数值2(二进制0010)的第4位变成1,因此结果变成10(二进制1010)

select 2 | power(2, 4-1)

·         如果想把第n位值设置成0,可以使用“与”方法:

@v_value & ~power(2, n-1)

例如,执行以下语句将把数值10(二进制1010)的第4位变成0,因此结果变成2(二进制0010)

select 10 & ~power(2, 4-1)

2.3.3计算非零位个数

计算非零个数用一句SQL是做不到了,可以用下面的一段T-SQL代码来实现:

declare @v_result int,

        @v_value  int

select @v_value = 11

 

select @v_result = 0

while @v_value != 0

begin

    select @v_result = @v_result +

                      (case @v_value & 1 when 0 then 0 else 1 end)

    select @v_value = @v_value / 2

end

 

select @v_result

上面代码中假设@v_value是要计算的值,变量@v_result存放结果并在最后显示。

例如上面的11(二进制1011),其结果是3

2.3.4 交换两个位的值

交换两个位的值可以用一段代码来实现:

declare @v_value int,

        @m       int,

        @n       int

       

select @v_value = 11, @m=2, @n=3

if @v_value & power(2,@n-1) <> @v_value & power(2,@m-1)

begin

    if @v_value & power(2,@n-1) = 0

    begin

        select @v_value = @v_value | power(2, @n-1)

        select @v_value = @v_value & ~power(2, @m-1)

    end

    else

    begin

        select @v_value = @v_value | power(2, @m-1)

        select @v_value = @v_value & ~power(2, @n-1)

    end

end

 

select @v_value

上述代码假设变量@v_value是将被变换的值,@m@n指明了位置。只有在两个位置的值不同时才进行交换。交换实际上是将两个不同位置的值分别取反。

例如,13(二进制1011)2位和第3位交换后的结果是15(二进制1101)

3. ORACLE中进行位运算

3.1 PL/SQL不直接支持位操作

在将包含位操作的T-SQL应用程序迁移到PL/SQL时,开发者发现Oracle并没有提供类似的位操作运算。这造成了一定的麻烦。

由于Oracle在表示整数时,采用了与MSSQLSybase不同的方式,并不采用2的次方的字节数来容纳,这实际上从本质上限制了位操作。

应用程序是否应该在数据库代码中使用位操作本身是一个设计问题,一般来说应该尽量避免。但是对于一些迁移自其他数据库的应用程序,要摒弃位操作会导致大量修改,甚至造成结构上的调整。因此,必须在Oracle中也实现位操作。

通常有三种方式可以用来实现位操作,根据我的使用经验,其优先级别分别为:

  1. 使用Java存储过程
  2. 使用C外部过程
  3. 使用PL/SQL的函数进行模拟位运算

3.2使用bitand函数

虽然Oracle不直接支持位操作,单是为了方便起见,Oracle8i开始还是提供了一个函数bitand来模拟“与”操作。

但是Oracle并没有提供其他位操作函数。因此,我们无法认为Oracle直接提供了位操作。

3.3使用Java存储过程进行位运算

Oracle有理由不直接提供位操作支持。由于Oracle提供了极为方便的Java存储过程,我们可以很方便的用来实现所有位操作。

下面我们准备实现四种基本的位操作。关于Java存储过程的内容,本文不给予太多描述,只引用必要的部分用于阐述问题。

首先,我们需要一个Java类,里面实现了位运算的四种基本功能。

public class BitOper {

    public static int bitAnd(int a, int b) {

        return a & b;

    }

    public static int bitOr(int a, int b) {

        return a | b;

    }

    public static int bitXor(int a, int b) {

        return a ^ b;

    }

    public static int bitNot(int a) {

        return ~a;

    }

}

为了便于描述问题,我将这个类设计的相当简单。

将其编译后得到BitOper.class

接下来,使用Oracle提供的一个工具loadjava将上述类加载到数据库中。该工具在安装Oracle时已经自动安装。

loadjava –user user_name/password@sid BitOper.class

该命令执行完后,如果没有报错则表示加载成功。

最后一步工作是创建PL/SQL包装函数。这里有两种选择,一种是创建独立的函数,一种是创建用包封装的函数。为了减少篇幅,我选择使用第一种。

create or replace function bit_and

(

  int1        number,

  int2        number

) return number

  DETERMINISTIC

AS LANGUAGE JAVA

NAME 'BitOper.bitAnd(int, int) return int';

/

create or replace function bit_or

(

  int1        number,

  int2        number

) return number

  DETERMINISTIC

AS LANGUAGE JAVA

NAME 'BitOper.bitOr(int, int) return int';

/

create or replace function bit_xor

(

  int1        number,

  int2        number

) return number

  DETERMINISTIC

AS LANGUAGE JAVA

NAME 'BitOper.bitXor(int, int) return int';

/

create or replace function bit_not

(

  int1        number

) return number

  DETERMINISTIC

AS LANGUAGE JAVA

NAME 'BitOper.bitNot(int) return int';

/

将上述代码在SQLPLUS中执行后,当前用户就具备了四种基本位操作的功能。

不妨使用几个例子来测试一下:

select bit_and(9,8) "9and8",

       bit_or(3,4) "3or4",

       bit_xor(3,5) "3xor5",

       bit_not(3) "not3"

  from dual;

其结果为:

     9and8       3or4      3xor5       not3

---------- ---------- ---------- ----------

         8          7          6         -4

 

  看起来一切都很简单,你是否这么认为呢?

你可能感兴趣的:(跨越Oracle和MSSQL关系数据库开发 -- 03不同数据库的位运算)