Zum Thema Oracle TEXT Name Matching hatten wir ja bereits einige Blog Postings. Heute möchte ich das
Thema nochmal aufgreifen und besonders die Erweiterbarkeit mit einem Thesaurus vorstellen.
Zunächst zur Ausgangssituation: Wir legen eine Tabelle an und füllen diese mit ein paar Namen:
create table tab_namen ( id number(10), vorname varchar2(100), nachname varchar2(100) ) / insert into tab_namen values (1, 'Carsten','Czarski'); insert into tab_namen values (2, 'Ulrike', 'Schwinn'); insert into tab_namen values (3, 'Max', 'Meier'); insert into tab_namen values (4, 'Moritz', 'Meyer'); insert into tab_namen values (5, 'Franz', 'Mayer'); insert into tab_namen values (6, 'Fritz', 'Maier');
Damit man einen Oracle TEXT Index anlegen kann, der auch alle Namen enthält, muss man nun
mit einem Multicolumn-Datastore oder einem User-Datastore arbeiten. Da ich die Namen aber gerne per NDATA (Name Matching)
und per "klassischer" Fuzzy-Suche (FUZZY-Operator) finden möchte, brauche ich einen
User-Datastore (Blog-Posting). Also zuerst die PL/SQL-Prozedur anlegen ...
create or replace procedure namen_uds_proc( rid in rowid, tlob in out nocopy varchar2 ) is l_vorname tab_namen.vorname%type; l_nachname tab_namen.nachname%type; begin select vorname, nachname into l_vorname, l_nachname from tab_namen where rowid = rid; tlob := '<VORNAME>' || l_vorname || '</VORNAME>' || '<NACHNAME>' || l_nachname || '</NACHNAME>' || '<NAME>' || l_vorname || ' ' || l_nachname || '</NAME>'; end; / sho err
Die Prozedur als User Datastore registrieren ...
begin ctx_ddl.create_preference('names_ds', 'user_datastore'); ctx_ddl.set_attribute('names_ds', 'procedure', 'namen_uds_proc'); end; / sho err
Mit dem Section Group-Objekt wird festgelegt, wie die einzelnen Sections (XML-Tags) von
Oracle TEXT behandelt werden sollen ... VORNAME und NACHNAME werden normal behandelt; der
zusammengesetzte "NAME" wird als NDATA-Section deklariert.
begin ctx_ddl.create_section_group('names_sg', 'xml_section_group'); ctx_ddl.add_field_section('names_sg', 'VORNAME', 'VORNAME', false); ctx_ddl.add_field_section('names_sg', 'NACHNAME', 'NACHNAME', false); ctx_ddl.add_ndata_section('names_sg', 'NAME', 'NAME'); end; /
Schließlich den Index anlegen ...
create index ft_names on tab_namen (nachname) indextype is ctxsys.context parameters(' datastore names_ds section group names_sg memory 100m ') /
Und dann kann man abfragen - zuerst mit "Meier" ...
select * from tab_namen where contains(nachname, 'ndata(name, Meier)')>0 / ID VORNAME NACHNAME ---------- --------------- --------------- 3 Max Meier 4 Moritz Meyer 6 Fritz Maier
Ups ... da fehlt doch einer ... der "Mayer" ist für den NDATA-Algorithmus wohl "zu weit" weg ... Probieren wir
noch ein wenig mit dem "Czarski".
select * from tab_namen where contains(nachname, 'ndata(name, Czarsky)')>0 / ID VORNAME NACHNAME ---------- --------------- ------------- 1 Carsten Czarski select * from tab_namen where contains(nachname, 'ndata(name, Tsarski)')>0 / ID VORNAME NACHNAME ---------- --------------- ------------- 1 Carsten Czarski select * from tab_namen where contains(nachname, 'ndata(name, Tsarsky)')>0 / Es wurden keine Zeilen ausgewählt
Funktioniert ganz gut, lässt aber doch noch Wünsche offen. Eine Ähnlichkeitssuche wird aber
immer Wünsche offenlassen, gerade wenn sie nicht für eine bestimmte Sprache optimiert ist
und global funktionieren soll. Die gute Nachricht ist aber, dass NDATA durch einen Thesaurus
erweitert werden kann.
In diesem Thesaurus können unterschiedliche Schreibweisen von Namen
als Synonymbeziehungen hinterlegt und die Treffermenge von NDATA so erweitert werden. Und das Gute ist, dass NDATA dann auch zu den Synonymen ähnliche (!) Namen
finden wird. Die Treffermenge wird also nicht nur um die Synonyme selbst, sondern auch um den
Synonymen (nach NDATA) ähnliche Namen erweitert.
Wie man
einen Thesaurus anlegt und pflegt, ist in diesem Blog-Posting beschrieben. Also erzeugen wir
einen Thesaurus wie folgt ...
begin ctx_thes.create_thesaurus('namesthes'); ctx_thes.create_relation('namesthes', 'meyer', 'SYN', 'maier'); ctx_thes.create_relation('namesthes', 'meyer', 'SYN', 'meier'); ctx_thes.create_relation('namesthes', 'meyer', 'SYN', 'mayer'); ctx_thes.create_relation('namesthes', 'czarski', 'SYN', 'czarsky'); end; /
Als nächstes müssen wir Oracle TEXT sagen, dass es den Thesaurus für NDATA nutzen soll - das geschieht
mit einer Wordlist Preference.
begin ctx_ddl.create_preference('names_wl', 'BASIC_WORDLIST'); ctx_ddl.set_attribute('names_wl', 'NDATA_ALTERNATE_SPELLING', 'FALSE'); ctx_ddl.set_attribute('names_wl', 'NDATA_BASE_LETTER', 'TRUE'); ctx_ddl.set_attribute('names_wl', 'NDATA_THESAURUS', 'namesthes'); end; /
Und dann muss der Index gelöscht und neu angelegt werden ...
drop index ft_names / create index ft_names on tab_namen (nachname) indextype is ctxsys.context parameters(' datastore names_ds section group names_sg wordlist names_wl memory 100m ') /
Und dann probieren wir die Abfragen erneut ...
select * from tab_namen where contains(nachname, 'ndata(name, Mayer)')>0 / ID VORNAME NACHNAME ---------- --------------- --------------- 3 Max Meier 4 Moritz Meyer 5 Franz Mayer 6 Fritz Maier select * from tab_namen where contains(nachname, 'ndata(name, Tsarsky)')>0 / ID VORNAME NACHNAME ---------- --------------- --------------- 1 Carsten Czarski
Voilá - man sieht, dass man die NDATA-Funktionalität sehr gut erweitern kann. Wenn man das von vorneherein
in seinen Index einbaut, so kann man den Index aufgrund von Nutzerfeedback "lernen" lassen. Mit einem
Namens-Thesaurus lässt sich die NDATA-Funktinalität auf jeden Fall sehr gut abrunden.