PL/SQL比较位字符串,需要AND操作吗?
我现在有一个脚本,可以计算化学图书馆指纹上的塔尼莫托系数。然而,在测试过程中,我发现我的实现无法切实扩展,因为我比较位字符串的方法太长了。见下文。这是我需要改进的循环。我已经简化了这一点,所以它只是看两个结构——真实的脚本对结构数据集进行排列,但这会使我这里的问题过于复杂PL/SQL比较位字符串,需要AND操作吗?,sql,oracle,plsql,bit-manipulation,logical-operators,Sql,Oracle,Plsql,Bit Manipulation,Logical Operators,我现在有一个脚本,可以计算化学图书馆指纹上的塔尼莫托系数。然而,在测试过程中,我发现我的实现无法切实扩展,因为我比较位字符串的方法太长了。见下文。这是我需要改进的循环。我已经简化了这一点,所以它只是看两个结构——真实的脚本对结构数据集进行排列,但这会使我这里的问题过于复杂 LOOP -- Find the NA bit SELECT SUBSTR(qsar_kb.fingerprint.fingerprint, var_fragment_id ,1) INTO var_na FROM qsar_
LOOP
-- Find the NA bit
SELECT SUBSTR(qsar_kb.fingerprint.fingerprint, var_fragment_id ,1) INTO var_na FROM qsar_kb.fingerprint where project_id = 1 AND structure_id = 1;
-- FIND the NB bit
SELECT SUBSTR(qsar_kb.fingerprint.fingerprint, var_fragment_id ,1) INTO var_nb FROM qsar_kb.fingerprint where project_id = 1 AND structure_id = 2;
-- Test for both bits the same
IF var_na > 0 AND var_nb > 0 then
var_tally := var_tally + 1;
END IF;
-- Test for bit in A on and B off
IF var_na > var_nb then
var_tna := var_tna + 1;
END IF
-- Test for bit in B on and A off.
IF var_nb > var_na then
var_tnb := var_tnb + 1;
END IF;
var_fragment_id := var_fragment_id + 1;
EXIT WHEN var_fragment_id > var_maxfragment_id;
END LOOP;
举个简单的例子
结构A='101010'
结构B='011001'
在我的真实数据集中,二进制文件的长度是500位以上。
我需要知道:
1两个端口共用的位数
2 A中打开但B中关闭的位数
3在B中打开但在B中关闭的位数
所以在这种情况下
1=1
2=2
3=2
理想情况下,我想改变我的做法。我不想沉浸在每个字符串中的每一位,当我用数千个结构(每个结构的指纹位字符串的长度在500-1000之间)来扩展整个系统时,它的时间开销太大了
我解决这个问题的逻辑是:
取两个表中的总位数
A=3
B=3
然后执行AND操作,并找出两者中有多少位开启
=1
然后从总数中减去这个,就可以得到其中一个的位数,而不是另一个
那么,如何在0和1的两个字符串上执行AND like操作以查找公共1的数量呢?请查看该函数
BITAND函数将其输入和输出视为位向量;输出是输入的按位AND
但是,根据文档,这只对2^128有效。您应该将SELECT移出循环。我敢肯定你99%的时间都在选择1位500次,你可以一次选择500位,然后循环字符串:
DECLARE
l_structure_a LONG;
l_structure_b LONG;
var_na VARCHAR2(1);
var_nb VARCHAR2(1);
BEGIN
SELECT MAX(decode(structure_id, 1, fingerprint)),
MAX(decode(structure_id, 2, fingerprint))
INTO l_structure_a, l_structure_b
FROM qsar_kb.fingerprint
WHERE project_id = 1
AND structure_id IN (1,2);
LOOP
var_na := substr(l_structure_a, var_fragment_id, 1);
var_nb := substr(l_structure_b, var_fragment_id, 1);
-- Test for both bits the same
IF var_na > 0 AND var_nb > 0 THEN
var_tally := var_tally + 1;
END IF;
-- Test for bit in A on and B off
IF var_na > var_nb THEN
var_tna := var_tna + 1;
END IF;
-- Test for bit in B on and A off.
IF var_nb > var_na THEN
var_tnb := var_tnb + 1;
END IF;
var_fragment_id := var_fragment_id + 1;
EXIT WHEN var_fragment_id > var_maxfragment_id;
END LOOP;
END;
编辑:
您也可以在一条SQL语句中执行此操作:
SQL> WITH DATA AS (
2 SELECT '101010' fingerprint,1 project_id, 1 structure_id FROM dual
3 UNION ALL SELECT '011001', 1, 2 FROM dual),
4 transpose AS (SELECT ROWNUM fragment_id FROM dual CONNECT BY LEVEL <= 1000)
5 SELECT COUNT(CASE WHEN var_na = 1 AND var_nb = 1 THEN 1 END) nb_1,
6 COUNT(CASE WHEN var_na > var_nb THEN 1 END) nb_2,
7 COUNT(CASE WHEN var_na < var_nb THEN 1 END) nb_3
8 FROM (SELECT to_number(substr(struct_a, fragment_id, 1)) var_na,
9 to_number(substr(struct_b, fragment_id, 1)) var_nb
10 FROM (SELECT MAX(decode(structure_id, 1, fingerprint)) struct_a,
11 MAX(decode(structure_id, 2, fingerprint)) struct_b
12 FROM DATA
13 WHERE project_id = 1
14 AND structure_id IN (1, 2))
15 CROSS JOIN transpose);
NB_1 NB_2 NB_3
---------- ---------- ----------
1 2 2
我将进一步介绍卢卡斯的回答,并提供更多的信息 Tom Kyte通过Jonathan Lewis在互联网上搜索一点,在基地之间转换。这里有一个函数,它将获取一个字符串并将其转换为一个数字。我复制了以下代码: 将基数转换为十进制:
create or replace function to_dec(
p_str in varchar2,
p_from_base in number default 16) return number
is
l_num number default 0;
l_hex varchar2(16) default '0123456789ABCDEF';
begin
for i in 1 .. length(p_str) loop
l_num := l_num * p_from_base + instr(l_hex,upper(substr(p_str,i,1)))-1;
end loop;
return l_num;
end to_dec;
将十进制数转换为基数:
create or replace function to_base( p_dec in number, p_base in number )
return varchar2
is
l_str varchar2(255) default NULL;
l_num number default p_dec;
l_hex varchar2(16) default '0123456789ABCDEF';
begin
if ( trunc(p_dec) <> p_dec OR p_dec < 0 ) then
raise PROGRAM_ERROR;
end if;
loop
l_str := substr( l_hex, mod(l_num,p_base)+1, 1 ) || l_str;
l_num := trunc( l_num/p_base );
exit when ( l_num = 0 );
end loop;
return l_str;
end to_base;
Oracle只提供了BITAND和BIT_TO_NUM,这在这里并不是一种真正相关的逻辑操作方式,但是这里需要的操作是a和B,a和B,而不是a和B。所以我们需要一个将a转换为非a的变量。一个简单的方法是使用translate
所以。。。。最终结果是:
select
length(translate(to_base(bitand(data_A, data_B),2),'10','1')) as nb_1,
length(translate(to_base(bitand(data_A, data_NOT_B),2),'10','1')) as nb_2,
length(translate(to_base(bitand(data_NOT_A, data_B),2),'10','1')) as nb_3
from (
select
to_dec(data_A,2) as data_A,
to_dec(data_b,2) as data_B,
to_dec(translate(data_A, '01', '10'),2) as data_NOT_A,
to_dec(translate(data_B, '01', '10'),2) as data_NOT_B
from (
select '101010' as data_A, '011001' as data_B from dual
)
)
这比我开始写这个答案时希望的要复杂一些,但它似乎确实起作用了。可以很简单地用这样的东西来完成:
SELECT utl_raw.BIT_AND( t.A, t.B ) SET_IN_A_AND_B,
length(replace(utl_raw.BIT_AND( t.A, t.B ), '0', '')) SET_IN_A_AND_B_COUNT,
utl_raw.BIT_AND( t.A, utl_raw.bit_complement(t.B) ) ONLY_SET_IN_A,
length(replace(utl_raw.BIT_AND( t.A, utl_raw.bit_complement(t.B) ),'0','')) ONLY_SET_IN_A_COUNT,
utl_raw.BIT_AND( t.B, utl_raw.bit_complement(t.A) ) ONLY_SET_IN_A,
length(replace(utl_raw.BIT_AND( t.B, utl_raw.bit_complement(t.A) ),'0','')) ONLY_SET_IN_A_COUNT
FROM (SELECT '1111100000111110101010' A, '1101011010101010100101' B FROM dual) t
确保您的位字符串的长度为偶数,如果它的长度为奇数,就用零填充它。Structure a='101010'。这些字符串是由字符0和1组成,还是由位组成?它是由0和1组成的varchar2。+1,尽管在这种情况下,我认为OP处理的是“1”和“0”字符串,而不是真正的二进制数据。是的,他还提到,实际上他需要多达1000位的长度,因此我的函数可能无法工作,除非他把字符串分成128位的块。。。
SELECT utl_raw.BIT_AND( t.A, t.B ) SET_IN_A_AND_B,
length(replace(utl_raw.BIT_AND( t.A, t.B ), '0', '')) SET_IN_A_AND_B_COUNT,
utl_raw.BIT_AND( t.A, utl_raw.bit_complement(t.B) ) ONLY_SET_IN_A,
length(replace(utl_raw.BIT_AND( t.A, utl_raw.bit_complement(t.B) ),'0','')) ONLY_SET_IN_A_COUNT,
utl_raw.BIT_AND( t.B, utl_raw.bit_complement(t.A) ) ONLY_SET_IN_A,
length(replace(utl_raw.BIT_AND( t.B, utl_raw.bit_complement(t.A) ),'0','')) ONLY_SET_IN_A_COUNT
FROM (SELECT '1111100000111110101010' A, '1101011010101010100101' B FROM dual) t