Oracle利用位运算实现一个字段表达多种状态
背景
由于业务原因,系统的数据需要发送到多个用户定义的Kafka的不同Topic中,同时需要确认发送成功后修改发送的状态,由于数据量很大,不可能新建表记录每个用户的发送状态,只能考虑同一个字段表达多个用户发送状态的解决方案。
位运算
最终考虑的解决方案是使用二进制表达不同用户的发送状态,每个用户占其中一位,使用0和1表达未发送和已发送的状态。
如0101,表示第一个和第三个用户已发送,第二个和第四个用户未发送。
- 查询指定用户未发送的可以使用与运算
- 修改用户已发送可以使用或运算
- 将已发送状态改为未发送可以使用异或运算
按位与(&)
按位与的运算规则
操作数1 | 0 | 0 | 1 | 1 |
---|---|---|---|---|
操作数2 | 0 | 1 | 0 | 1 |
按位与结果 | 0 | 0 | 0 | 1 |
规则总结:只有两个操作数对应位同为1时,结果为1,其余全为0. (或者是只要有一个操作数为0,结果就为0)。
从以上规则可知,如果要查询第三个用户未发送的记录,操作数应该是0100,满足
[任意数字] & 0100 = 0
条件的就是第三个用户未发送的记录了。
按位或(|)
按位或的运算规则
操作数1 | 0 | 0 | 1 | 1 |
---|---|---|---|---|
操作数2 | 0 | 1 | 0 | 1 |
按位或 | 0 | 1 | 1 | 1 |
规则总结:只有两个操作数对应位同为0时,结果为0,其余全为1.(或者是只要有一个操作数为1,结果就为1)。
从以上规则可知,修改第三个用户的记录为已发送,操作数应该是0100,直接设置状态为
[任意数字] | 0100
。
按位异或(^)
按位异或的运算规则
操作数1 | 0 | 0 | 1 | 1 |
---|---|---|---|---|
操作数2 | 0 | 1 | 0 | 1 |
按位异或 | 0 | 1 | 1 | 0 |
规则总结:异:1。
从以上规则可知,将第三个用户的已发送的记录修改为未发送,操作数应该是0100,直接设置状态为
[任意数字] ^ 0100
。
Oracle的操作
对于与运算,Oracle原生支持。
select BitAnd(6, 4) from dual;
或运算和异或运算需要自定义存储过程
-- 或运算
Create or replace function BitOr(a in int, b in int) return int
is
begin
return a + b - bitand(a, b);
end;
-- 异或运算
Create or replace function BitXor(a in int, b in int) return int
is
begin
return a + b - 2 * bitand(a, b);
end;
Oracle也不支持十进制直接转二进制,对于debug来说很不友好,同样需要自定义存储过程
CREATE OR REPLACE FUNCTION F_NUM_TO_BIN(p_num NUMBER) RETURN VARCHAR2
IS
r_binstr VARCHAR2(32767);
l_num NUMBER := p_num;
BEGIN
WHILE l_num != 0
LOOP
r_binstr := TO_CHAR(MOD(l_num, 2)) || r_binstr;
l_num := TRUNC(l_num / 2);
END LOOP;
RETURN r_binstr;
END F_NUM_TO_BIN;
Demo演示
为了演示效果,创建一张简单的表:
create table DPM_TEST_BINARY
(
ID NUMBER(22),
STATUS NUMBER
)
插入几条数据
INSERT INTO DDSJ.DPM_TEST_BINARY (ID, STATUS) VALUES (1, 32768);
INSERT INTO DDSJ.DPM_TEST_BINARY (ID, STATUS) VALUES (2, 42258);
INSERT INTO DDSJ.DPM_TEST_BINARY (ID, STATUS) VALUES (3, 65535);
INSERT INTO DDSJ.DPM_TEST_BINARY (ID, STATUS) VALUES (4, 65535);
INSERT INTO DDSJ.DPM_TEST_BINARY (ID, STATUS) VALUES (5, 0);
这里对第五个用户进行操作,二进制是:0000000000010000,转为十进制的值是:16
查询未发送的记录
Select ID, STATUS, F_NUM_TO_BIN(STATUS)
from DPM_TEST_BINARY
where BitAnd(STATUS, 16) = 0;
结果查询除了ID为1和5的数据,符合我们的预期。
将未发送修改为已发送
update DPM_TEST_BINARY set STATUS=BITOR(STATUS, 16) where BitAnd(STATUS, 16) = 0;
commit;
再次查询,发现第五位修改为1,其他位都没有变化,符合预期。
将已发送修改为未发送
update DPM_TEST_BINARY set STATUS=BITXOR(STATUS, 16);
commit;
除了第五位修改为0,其他位都没有变化,符合预期。
综上,完成了一个字段表达多种状态的目标。