2

Testfall Schema und die Daten folgen, wie haben:String-Matching in Oracle 10g, wo jeder Seite können Platzhalter

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','  _ '); 

Ignorieren der Maske Spalte für jetzt und unter der Annahme der specialmatch Funktion vorhanden ist:

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

Sollte produzieren:

VALS  
12345678 
12_45678 
12345678 

Als nächstes

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

Sollte produzieren:

VALS  
92345678 
92345678 

Als nächstes

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

Sollte produzieren:

VALS  
12345678 
12_45678 
12345678 
92345678 
92345678 

Als nächstes

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

Sollte produzieren:

VALS  
12_45678 

Alle Ideen, wie die besondere Match-Funktion zu machen?

Mein naiver Ansatz ist eine spezielle Zeichenfolge vergleichen UDF (Pseudo-Code) zu schreiben:

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; 
} 

auch vor zu tun, das Spiel der Maske auf der val überlagert werden muss.

Ex: val = '1_345678', mask = '_ _' => 1_34567_ und würde 12345678 und 19345679 passen, aber nicht 92345678.

Aber wie kann dies zu Leverage Indizes, Optimierer usw. durchgeführt werden ..

Antwort

0

I "aufgeteilt", um die Tabelle in zwei verschiedene Sätze, diejenigen, die nicht über eine Maske haben (v1) und diejenigen, die tun (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); 

nun der Optimierer sagt:

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 

Das ist sehr gut, auch wenn mein: Surch Wildcards darin hat, kann Oracle weg optimieren.

Am Ende waren die Standardindizes für Vals und Mask Cols ausreichend, um den Trick auch ohne Hinweise zu machen. Getestet auf 10g. Hinweis: Wir verwenden union all, da sich v1 und v2 immer gegenseitig ausschließen.

Fyi:

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; 

Volltestfall (mit typischen Datenverteilung):

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

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; 

----------------------------------------------------------------- 
0

Oracle 10g hat eine reguläre Ausdrücke Funktion, die Ihnen in dieser Situation helfen kann. http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14251/adfns_regexp.htm

Zusätzlich können Sie eine Java gespeicherte Prozedur betrachten, wenn Sie dies in den Datenbanken tun müssen.

Ich bin mir nicht bewusst, ein Index, der Ihnen in dieser Situation helfen wird, obwohl die _ überall auftreten können, einschließlich des ersten Zeichens.

+0

Ja, ich habe mir auch eine Regex-Lösung angeschaut. Das Problem dabei ist der erhebliche Overhead, während auf beiden Seiten immer noch Probleme mit Platzhaltern auftreten. .2345 würde^(.) Werden (2 | \.) (3 | \.) (4 | \.) (5 | \.) $ nicht für Klarheit optimiert –

0

Ist die Maske nur ein einzelnes Zeichen? Wenn ja, können Sie die Möglichkeiten, die so etwas wie

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

Dann begrenzen Sie haben funktionsbasierte Indizes auf substr (vals, 1,4) und substr (vals, 5). Ich scheine zu lesen, daran zu erinnern, dass es möglicherweise ein Problem mit FBIs den besten Plan für diese nicht bekommen, so eine Alternative 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)); 
+0

Ich mag Ihre Idee, aber es kann sei eine beliebige Anzahl von Jokern, hätte ich die Testdaten widerspiegeln sollen. –

0

Nächste Vorschläge wären. Einfache Option: Der _ ist das einzige Charakter Spiel für LIKE, so die einfache Lösung

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

Es ist wird eine vollständige Tabelle jedes Mal scannen, sondern speichert die Umschaltung zwischen SQL und PL/SQL-Schichten

Komplexe Option Bitmap indizes auf substr für jedes einzelne Zeichen. Diese Art von Multi-Index Tuff ist, was Bitmaps gut sind. Bitmaps sind ein Bugger für Spalten mit schweren Updates oder Tabellen mit vielen kleinen Einfügungen.

Ich habe einen Test Test gebaut. Zuerst habe ich 10.000 Werte in TMP geladen, ziemlich zufällig generiert. Nicht sicher, wie groß Ihr Datensatz sein wird oder wie viele Einträge ohne Platzhalter, Platzhalter oder mehrere Platzhalter enthalten sind. Das hätte große Auswirkungen auf die Ergebnisse.

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

insert into tmp 
select new_val, translate(new_val,'','__________') 
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; 
/

Ich habe die 'doppelköpfigen LIKE' gegen Maske merge verglichen. Im Test erreichte der LIKE typischerweise 200-250 (Hundertstelsekunden), während der Maskenmerge etwa zehnmal länger dauerte. Wie gesagt, es wird sehr stark von der Datenverteilung abhängen.

+0

wurde der _ Charakter aus diesem Grund gewählt. Ich bin googleing über Bitmap-Indizes, aber bis jetzt bin ich mir nicht sicher, wie es zutreffen würde. –

+0

Was denkst du über die Lösung, die ich endlich herausgefunden habe? Es hilft, Venn-Diagramme auf einer weißen Tafel zu verwenden ... –

+0

das Dual-Like-Muster schlägt fehl für: wählen Sie * aus Dual wo '1_' wie '_1' oder '_1' wie '1_'; –

0

FYI, haben Ich fand, dass, wenn Sie benötigen Matches auf% var% zu tun und es haben arbeiten Sie schnell gegen viele Daten, am besten verwenden Sie Oracle Oracle Text Indexes.

+0

wahr ist, trägt es eine erhebliche Last von Nicht-Standard-Abfragesyntax und Overhead von periodischen Indexneuberechnungen. An diesem Punkt sind die Daten gut indiziert und es findet kein vollständiger Tabellenscan statt. Siehe den EXPLAIN-Plan in meiner gewählten Lösung. –

Verwandte Themen