Posts mit dem Label thesaurus werden angezeigt. Alle Posts anzeigen
Posts mit dem Label thesaurus werden angezeigt. Alle Posts anzeigen

Mittwoch, 6. Juni 2012

Name Matching (NDATA) mit einem Thesaurus "erweitern"

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.

Dienstag, 16. März 2010

Arbeiten mit einem Thesaurus in Oracle TEXT

Oracle TEXT bietet Thesaurus-Unterstützung out-of-the-box an. Das bedeutet, dass man nicht nur nach einem Wort oder einer Phrase suchen kann, sondern auch nach "verwandten" Wörtern. Die Beziehungen zwischen verwandten Wörtern müssen allerdings in der Datenbank hinterlegt sein. Dieses Posting stellt kurz vor, wie man einen einfachen Thesaurus anlegt und nutzt. Oracle selbst stellt Thesauri aber nur für die englische Sprache zur Verfügung; im deutschsprachigen Bereich muss man sich diesen selbst erstellen.
Thesauri werden angelegt und verwaltet mit dem PL/SQL-Paket CTX_THES. Der folgende Aufruf legt einen neuen (noch leeren) Thesaurus an. Wie für andere administrative Aufgaben benötigt Ihr auch zum Verwalten von Thesauri die Rolle CTXAPP:
begin
  ctx_thes.create_thesaurus(
    name     => 'MEIN_THESAURUS',
    casesens => false
  );
end;
/
Nun geht es daran, Wortbeziehungen in den Thesaurus einzutragen. Dabei werden folgende Beziehungen unterstützt:
  • Narrower Term (NT): Ein Begriff wird enger gefasst. Eine solche Beziehung könnte sein Sport NT Fußball.
  • Broader Term (BT): Ein Begriff wird weiter gefasst. Das ist das Gegenteil zum Narrower Term. Eine solche Beziehung könnte sein AktieBT Wertpapier.
  • Broader Term (BT): Ein Begriff wird weiter gefasst. Das ist das Gegenteil zum Narrower Term. Eine solche Beziehung könnte sein AktieBT Wertpapier.
  • Synonym (SYN): Eine Synonymbeziehung meint, dass zwei Begriffe das gleiche bedeuten. Geld SYN Moneten ist ein Beispiel für eine solche Beziehung.
  • Übersetzungen (TR): Wie der Name schon sagt; im Thesaurus lassen sich auch Übersetzungen eines Begriffs in andere Sprachen verwalten. Ein Beispiel wäre Geld ENGLISH: Money.
Das folgende Beispiel generiert einige Beziehungen in den soeben erstellten Thesaurus:
begin
  ctx_thes.create_relation('MEIN_THESAURUS', 'Elfmeter', 'SYN', 'Strafstoß');
  ctx_thes.create_relation('MEIN_THESAURUS', 'Schiedsrichter', 'SYN', 'Unparteiischer');
  ctx_thes.create_relation('MEIN_THESAURUS', 'Trainer', 'SYN', 'Coach');

  ctx_thes.create_relation('MEIN_THESAURUS', 'Ballspiel', 'NT', 'Fußball');
  ctx_thes.create_relation('MEIN_THESAURUS', 'Liga', 'NT', 'Bundesliga');
  ctx_thes.create_relation('MEIN_THESAURUS', 'Liga', 'NT', 'Regionalliga');
  ctx_thes.create_relation('MEIN_THESAURUS', 'Liga', 'NT', 'Champions League');

  ctx_thes.create_relation('MEIN_THESAURUS', 'Handspiel', 'BT', 'Foul');
  ctx_thes.create_relation('MEIN_THESAURUS', 'Stürmer', 'BT', 'Spieler');
  ctx_thes.create_relation('MEIN_THESAURUS', 'Libero', 'BT', 'Spieler');
end;
/
Nun können die Beziehungen schon getestet werden. Zunächst wollen wir prüfen, ob die Synonyme für "Trainer" funktionieren.
select ctx_thes.syn('Trainer', 'MEIN_THESAURUS') from dual;

CTX_THES.SYN('TRAINER','MEIN_THESAURUS')
---------------------------------------------
{TRAINER}|{COACH}

select ctx_thes.syn('Coach', 'MEIN_THESAURUS') from dual;

CTX_THES.SYN('COACH','MEIN_THESAURUS')
----------------------------------------------------
{COACH}|{TRAINER}
Nun geht es an einen richtigen Test. Der Thesaurus soll in einer Volltextabfrage mit CONTAINS genutzt werden. Die folgenden Anweisungen erzeugen eine Tabelle, einige Dokumente und schließlich einen Volltextindex. Thesaurus und Index sind aber voneinander unabhängig! Ein vorhandener Volltextindex muss nach Erstellen oder Ändern eines Thesaurus nicht neu gebaut werden.
create table test_thes_tab(text varchar2(4000))
/

insert into test_thes_tab values ('Der Trainer wurde entlassen');
insert into test_thes_tab values ('Der Verein war zurück in der Bundesliga');
insert into test_thes_tab values ('Der Stürmer lieferte eine Glanzleistung ab.');
insert into test_thes_tab values ('In der 15. Minute gab es einen Elfmeter');
insert into test_thes_tab values ('Der Schiedsrichter blieb hart.');

create index idx_thestest_volltext on test_thes_tab (text) 
indextype is ctxsys.context
/
Nun einige Abfragen ...
  • Die einfache Suche nach Coach bringt keine Ergebnisse:
    select text from test_thes_tab where contains(text, 'Coach') > 0;
    
    No rows selected.
    
  • Die Synonymbeziehung bringt den Erfolg:
    select text from test_thes_tab where contains(text, 'SYN(Coach, MEIN_THESAURUS)') > 0;
    
    TEXT
    --------------------------------------------------------------------------------
    Der Trainer wurde entlassen
    
  • Die Suche nach Spieler bringt keine Treffer:
    select text from test_thes_tab where contains(text, 'Spieler') > 0;
    
    Es wurden keine Zeilen ausgewählt.
    
  • Sucht man nach den Narrower Terms (hier: bis zu 10 Stufen), so findet man auch die Stürmer.
    select text from test_thes_tab where contains(text, 'NT(Spieler,10,MEIN_THESAURUS)') > 0;
    
    TEXT
    --------------------------------------------------------------------------------
    Der Stürmer lieferte eine Glanzleistung ab.
    
In diesen Beispielen wurde der Thesaurus mit MEIN_THESAURUS überall mit angegeben. Es ist jedoch auch möglich, einen Thesaurus namens DEFAULT zu erzeugen oder einen bestehenden in DEFAULT umzubenennen. Dieser wird dann immer verwendet, wenn kein Thesaurus-Name angegeben wird. Das folgende Kommando benennt den Thesaurus MEIN_THESAURUS in DEFAULT um.
begin
  ctx_thes.alter_thesaurus(
    tname   => 'MEIN_THESAURUS',
    op      => ctx_thes.op_rename, 
    operand => 'DEFAULT'
  );
end;
/
Nun muss der Thesaurus-Name nicht mehr explizit angegeben werden.
select text from test_thes_tab where contains(text, 'NT(Spieler,10)') > 0;

TEXT
--------------------------------------------------------------------------------
Der Stürmer lieferte eine Glanzleistung ab.
Wie für alles gibt es auch für Thesauri in der Datenbank Dictionary Views:
  • CTX_USER_THESAURI enthält die definierten Thesauri.
  • CTX_USER_THES_PHRASES gibt die in den Thesauri enthaltenen Begriff zurück. Die Beziehungen selbst sind darin aber nicht enthalten; diese können mit dem Paket CTX_THES und den darin enthaltenen Funktionen wie NT oder BT oder SYN ermittelt werden.
Schließlich können auch Übersetzungen im Thesaurus gespeichert werden. Hier ein Beispiel:
begin
  ctx_thes.create_relation('DEFAULT', 'Referee', 'GERMAN:', 'Schiedsrichter');
end;  
Nun kann man auch nach den Übersetzungen von Referee suchen ...
select text from test_thes_tab where contains(text, 'TR(Referee)') > 0;

TEXT
-------------------------------------------------------------------------------
Der Schiedsrichter blieb hart.
Weitere Informationen zum Thema findet Ihr hier:

Beliebte Postings