2

テスト ケースのスキーマとデータは次のとおりです。

create table tmp
(
 vals varchar(8),
 mask varchar(8)
);


insert into tmp values ('12345678','        ');

insert into tmp values ('12_45678','  _     ');

insert into tmp values ('12345678','   _    ');

insert into tmp values ('92345678','        ');

insert into tmp values ('92345678','     _  ');

今のところ mask 列を無視し、specialmatch 関数が存在すると仮定します。

select VALS from tmp where specialmatch(vals,'12345678');

生成する必要があります:

VALS    
12345678
12_45678
12345678

次、

select VALS from tmp where specialmatch(vals,'92345678');

生成する必要があります:

VALS     
92345678 
92345678 

次、

select VALS from tmp where specialmatch(vals,'_2345678');

生成する必要があります:

VALS     
12345678 
12_45678 
12345678 
92345678 
92345678 

次、

select VALS from tmp where specialmatch(vals,'12945678');

生成する必要があります:

VALS     
12_45678 

特別なマッチ機能を作成する方法についてのアイデアはありますか?

私の単純なアプローチは、特別な文字列比較 udf (疑似コード) を記述することです。

bool function specialmatch(str1,str2) DETERMINISITC
{
 return false if either are null;
 for each char1,char2 of str1,str2
 {
  if (char1<>char2 && char1<>'_' && char2<>'_') return false;
 }
 return true;
}

また、一致を行う前に、マスクを val にオーバーレイする必要があります。

例: val='1_345678', mask=' _ _' => 1_34567_ で、12345678 と 19345679 には一致しますが、92345678 には一致しません。

しかし、インデックスやオプティマイザーなどを活用するためにこれを行うにはどうすればよいでしょうか...

4

5 に答える 5

0

参考までに、%var%で一致を実行し、大量のデータに対して高速に動作させる必要がある場合は、OracleのOracleTextIndexesを使用するのが最善の方法であることがわかりました。

于 2009-08-18T19:51:40.750 に答える
0

Oracle 10g には、この状況で役立つ正規表現機能があります。 http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14251/adfns_regexp.htm

さらに、データベースでこれを行う必要がある場合は、Java ストアド プロシージャを調べることができます。

_ は最初の文字を含むどこにでも発生する可能性がありますが、この状況で役立つインデックスは知りません。

于 2009-08-14T17:07:32.633 に答える
0

マスクは一文字だけですか?もしそうなら、次のような方法で可能性を制限できます

select VALS from tmp 
where specialmatch(vals,'12945678')
and (substr(vals,1,4) = substr('12945678',1,4) 
      or substr(vals,5) = substr('12945678',5));

次に、substr(vals,1,4) と substr(vals,5) に関数ベースのインデックスがあります。FBIがこれらの最善の計画を取得していないという問題がある可能性があることを読んだことを思い出したようです。したがって、代替SQLは次のようになります

    select VALS from tmp 
    where specialmatch(vals,'12945678')
    and substr(vals,1,4) = substr('12945678',1,4)
    union 
    select VALS from tmp 
    where specialmatch(vals,'12945678')
    substr(vals,5) = substr('12945678',5));
于 2009-08-15T21:50:50.030 に答える
0

テーブルを 2 つの異なるセットに「分割」しました。マスクを持っていない人 (v1) とマスクを持っている人 (v2) です。

select * from (select * from tmp where mask = '        ') v1 where vals like :srch
 union all
select * from (select * from tmp where mask > '        ') v2 where vals like maskmerge(mask,:srch);

ここで、オプティマイザは次のように言います。

Operation                                Object Name       Rows Bytes Cost
SELECT STATEMENT Optimizer Mode=ALL_ROWS                      2          5    
  UNION-ALL
    TABLE ACCESS BY INDEX ROWID          SCHEMA.TMP           1    90    2  
      INDEX RANGE SCAN                   SCHEMA.I_TMP_MASK    1          1
    TABLE ACCESS BY INDEX ROWID          SCHEMA.TMP           1    90    3
      INDEX RANGE SCAN                   SCHEMA.I_TMP_MASK    2          1

私の :srch にワイルドカードが含まれている場合でも、Oracleは最適化して取り除くことができます。

最終的には、ヒントがなくても、val と mask col の標準インデックスで十分でした。10gでテスト。注: v1 と v2 は常に相互に排他的であるため、union all を使用します。

ご参考までに:

CREATE OR REPLACE FUNCTION maskmerge (A IN VARCHAR, B IN VARCHAR)
 RETURN VARCHAR deterministic parallel_enable
 IS  
 alen int;
 blen int;
 mlen int;   
 res varchar(4000);
 ca char;
 cb char;
BEGIN
 if (a is null) then
  return b;
 end if;
 if (b is null) then
  return a;
 end if;
 alen:=length(a);
 blen:=length(b);
 if (alen<blen) then
  mlen:=alen;
 else
  mlen:=blen;
 end if;
 for i in 1 .. mlen loop
  ca:=substr(a,i,1);
  cb:=substr(b,i,1);
  if (ca='_' or cb='_') then
   res:=res||'_';
  elsif (ca=' ') then
   res:=res||cb;
  elsif (cb=' ') then
   res:=res||ca;
  else
   res:=res||cb;
  end if;
 end loop;
 return res;
END;

完全なテスト ケース (典型的なデータ分布):

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

CREATE OR REPLACE FUNCTION maskmerge (A IN VARCHAR, B IN VARCHAR)
 RETURN VARCHAR deterministic parallel_enable
 IS  
 alen int;
 blen int;
 mlen int;   
 res varchar(4000);
 ca char;
 cb char;
BEGIN
 if (a is null) then
  return b;
 end if;
 if (b is null) then
  return a;
 end if;
 alen:=length(a);
 blen:=length(b);
 if (alen<blen) then
  mlen:=alen;
 else
  mlen:=blen;
 end if;
 for i in 1 .. mlen loop
  ca:=substr(a,i,1);
  cb:=substr(b,i,1);
  if (ca='_' or cb='_') then
   res:=res||'_';
  elsif (ca=' ') then
   res:=res||cb;
  elsif (cb=' ') then
   res:=res||ca;
  else
   res:=res||cb;
  end if;
 end loop;
 return res;
END;
/

create table tmp
(
id int not null primary key,
ipv6address varchar(32) not null,
ipv6addressmask varchar(32) default ('                                ') not null
);

create sequence s_tmp;

create index i_tmp_addr on tmp(ipv6address);

create index i_tmp_mask on tmp(ipv6addressmask);

create or replace trigger t_i_tmp before insert on tmp referencing new as new old as old FOR EACH ROW
DECLARE
    tmpVar tmp.id%TYPE;
begin
SELECT s_tmp.NEXTVAL INTO tmpVar FROM dual;
:new.id:=tmpVar;
end;

exec dbms_random.initialize(17809465);

insert into tmp (ipv6address) 
select decode(trunc(dbms_random.value(0,2)),0,'20010db80000000000000000',1,'00000000000000000000ffff','00000000000000000000ffff') 
||trim(to_char(dbms_random.value(0, 4294967296),'0000000x')) 
as val from dual
connect by level <= 10000;

insert into tmp
SELECT * FROM
( SELECT * FROM tmp
ORDER BY dbms_random.value )
WHERE rownum <= 200;

insert into tmp values (null,'00000000000000000000ffff12345678','                                ');

insert into tmp values (null,'00000000000000000000ffff12345678','                              _ ');

insert into tmp values (null,'00000000000000000000ffff1234567_','                              __');

--select * from tmp order by ipv6address

-- network redaction of ipv4 
update tmp set ipv6addressmask=maskmerge('                        ______  ',ipv6addressmask),ipv6address=maskmerge('                        ______  ',ipv6address) where length(ipv6address)/32*dbms_random.value<0.005;

-- host redaction of ipv4 
update tmp set ipv6addressmask=maskmerge('                              __',ipv6addressmask),ipv6address=maskmerge('                              __',ipv6address) where length(ipv6address)/32*dbms_random.value<0.005;

-- full redaction of ipv4 
update tmp set ipv6addressmask=maskmerge('                              __',ipv6addressmask),ipv6address=maskmerge('                              __',ipv6address) where ipv6addressmask='                        ______  ' and length(ipv6address)/32*dbms_random.value<0.04;

-- network report redaction of ipv4 
update tmp set ipv6addressmask=maskmerge('                        ______  ',ipv6addressmask) where length(ipv6address)/32*dbms_random.value<0.005;

-- host report redaction of ipv4 
update tmp set ipv6addressmask=maskmerge('                              __',ipv6addressmask) where length(ipv6address)/32*dbms_random.value<0.005;

-- full report redaction of ipv4 
update tmp set ipv6addressmask=maskmerge('                              __',ipv6addressmask) where ipv6addressmask='                        ______  ' and length(ipv6address)/32*dbms_random.value<0.04;

select count(*) from tmp where instr(ipv6address,'_')>0;

select count(*) from tmp where ipv6addressmask > '                                '; 

-- srch := '00000000000000000000ffff12345678';  

select * from (select * from tmp where ipv6addressmask = '                                ') v1 where ipv6address like :srch
 union all
select * from (select * from tmp where ipv6addressmask > '                                ') v2 where ipv6address like maskmerge(ipv6addressmask,:srch);

/*
Operation                                Object Name Rows Bytes Cost
---------------------------------------- ----------- ---- ----- ----
SELECT STATEMENT Optimizer Mode=ALL_ROWS              510         29                                 
  UNION-ALL                                              
    TABLE ACCESS BY INDEX ROWID          TMP          500   23K   10                                 
      INDEX RANGE SCAN                   I_TMP_ADDR    92          2                                 
    TABLE ACCESS BY INDEX ROWID          TMP           10   490   19                                 
      INDEX RANGE SCAN                   I_TMP_MASK   207          2                                 

*/

SELECT * FROM tmp WHERE ipv6address LIKE :srch OR :srch LIKE ipv6address

/*

Operation                                Object Name Rows Bytes Cost
---------------------------------------- ----------- ---- ----- ----
SELECT STATEMENT Optimizer Mode=ALL_ROWS              995         22                                 
  TABLE ACCESS FULL                      TMP          995   47K   22                                 

*/

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

drop table tmp;

drop sequence s_tmp;

drop function maskmerge;

-----------------------------------------------------------------
于 2009-08-17T17:01:09.603 に答える
0

次の提案。簡単なオプション: _ は LIKE の単一文字の一致なので、簡単な解決策は次のとおりです。

SELECT * FROM tmp WHERE vals LIKE v_param OR v_param LIKE vals;

毎回全表スキャンになりますが、SQL レイヤーと PL/SQL レイヤーの切り替えを節約できます。

複雑なオプション 各文字の substr のビットマップ インデックス。この種のマルチインデックス tuff は、ビットマップが得意とするところです。ただし、ビットマップは、大量の更新を伴う列や小さな挿入が多数含まれるテーブルのバグです。

テストテストを作成しました。まず、ほぼランダムに生成された 10,000 個の値を TMP にロードしました。データ セットの大きさや、ワイルドカードなし、ワイルドカード 1 つまたは複数のワイルドカードを含むエントリの割合がわからない。それが結果に大きく影響します。

create table tmp ( vals varchar(8),  mask varchar(8));

insert into tmp
select new_val, translate(new_val,'0123456789','__________')
from
  (select case 
         when rn_3  is not null then translate(val,'34','__')
         when rn_5  is not null then translate(val,'2','_')
         when rn_7  is not null then translate(val,'78','__')
         when rn_11 is not null then translate(val,'12345','_____')
         else val end new_val
  from
    (select lpad(trunc(dbms_random.value(1,99999999)),8,'0') val, 
            decode(mod(rownum,3),0,1) rn_3, decode(mod(rownum,5),0,1) rn_5, 
            decode(mod(rownum,7),0,1) rn_7, decode(mod(rownum,11),0,1) rn_11
     from dual connect by level < 10000)
  )

declare
  cursor c_1 is
    select case 
         when rn_3  is not null then translate(val,'34','__')
         when rn_5  is not null then translate(val,'2','_')
         when rn_7  is not null then translate(val,'78','__')
         when rn_11 is not null then translate(val,'12345','_____')
         else val end try_val
    from
      (select lpad(trunc(dbms_random.value(1,99999999)),8,'0') val, 
              decode(mod(rownum,3),0,1) rn_3, decode(mod(rownum,5),0,1) rn_5, 
              decode(mod(rownum,7),0,1) rn_7, decode(mod(rownum,11),0,1) rn_11
       from dual connect by level < 1000);
  v_cnt number;
  v_start number;
  v_end number;
begin
 v_start := dbms_utility.get_time;
 for c_rec in c_1 loop
    select count(*) into v_cnt 
    from tmp 
    where (c_rec.try_val like vals or vals like c_rec.try_val);
 end loop;
 v_end := dbms_utility.get_time;
 dbms_output.put_line('Meth 1 :'||(v_end - v_start));
 v_start := dbms_utility.get_time;
 for c_rec in c_1 loop
    select count(*) into v_cnt from
      (select * from (select * from tmp where mask = '        ') v1 
       where vals like c_rec.try_val
       union all
       select * from (select * from tmp where mask > '        ') v2 
       where vals like maskmerge(mask,c_rec.try_val));
 end loop;
 v_end := dbms_utility.get_time;
 dbms_output.put_line('Meth 2 :'||(v_end - v_start));
end;
/

「双頭の LIKE」とマスク マージを比較しました。テストでは、通常、LIKE は約 200 ~ 250 (100 分の 1 秒) になりましたが、maskmerge には約 10 倍の時間がかかりました。私が言ったように、それはデータ分布に大きく依存します。

于 2009-08-17T00:09:53.940 に答える