Donnerstag, 29. Januar 2009

Viele Abfragen auf einmal: Query Relaxation

Wenn man in Dokumentbeständen sucht, ist es ja vielfach so, dass man zunächst mit recht vielen Suchbegriffen anfängt und dann (wenn man nichts findet), die Abfrage allgemeiner macht, eben bis etwas kommt ...
Für eine CONTAINS-Abfrage mit Oracle TEXT könnte das in etwa folgendes bedeuten:
  • 1. Abfrage (keine Treffer)
    select * from dokument_tabelle
    where contains (dokument, 'oracle and datenbank and text and contains and parameters and 11g') > 0
    /
      
  • 2. Abfrage (bspw. 1 Treffer, passt aber nicht)
    select * from dokument_tabelle
    where contains (dokument, 'oracle and datenbank and (text or contains)') > 0
    /
      
  • 3. Abfrage (ausreichend Treffer)
    select * from dokument_tabelle
    where contains (dokument, 'oracle or datenbank') > 0
    /
    
Das bedeutet nun allerdings, dass man drei Abfragen absetzt. Man kann nun darüber nachdenken, einen solchen Prozeß zu automatisieren - wenn ein Suchwort in eine Maske eingegeben wird, wird zunächst genau danach gesucht, wenn nicht genug Treffer gefunden werden, wird das Stemming ($-Operator) verwendet und wenn es dann immer noch nicht reicht, probiert man es mit Fuzzy (?).
Es ist allerdings recht aufwändig, das selbst zu tun - man müsste jedesmal die Treffer zählen und bei Bedarf eine neue Abfrage absetzen: Die Antwortzeiten wären damit sicherlich irgendwann nicht mehr akzeptabel ...
Es gibt hierfür allerdings seit Oracle10g eine Funktion: Query Relaxation. Man kann alle diese Abfragen auf einmal übergeben und auch festlegen, wieviele Treffer man gerne haben möchte. Oracle TEXT erledigt dann alles mit nur einem CONTAINS-Aufruf.
select * from dokument_tabelle
where contains (
  dokument, 
  '<query>
     <textquery lang="ENGLISH" grammar="CONTEXT">
       <progression>
         <seq>{oracle} and {datenbank} and {text} and {contains} and {parameters} and {11g}</seq>
         <seq>{oracle} and {datenbank} and ({text} or {contains})</seq>
         <seq>{oracle} or {datenbank}</seq>
       </progression>
     </textquery>
     <score datatype="INTEGER" algorithm="DEFAULT"/>
  </query>'
)>0
and rownum <= 10;
Die CONTAINS-Abfrage wird im XML-Format übergeben; die sog. Query Templates werden hier verwendet (mehr Informationen). Man sieht sehr schön, dass die Abfragen der Reihe nach aufgeführt werden. Oracle TEXT führt alle Abfragen der Reihe nach aus, bis entweder das Ende erreicht ist (letztes seq-Tag oder bis die gewünschte Zahl der Treffer gefunden wurde. Die gewünschte Anzahl Treffer steckt in der zusätzlichen Bedingung and rownum <= 10.
Da nun alle Abfragen in ein und demselben CONTAINS Aufruf stecken, ist diese Variante wesentlich schneller und effizienter als das manuelle "Nacheinander-Aufrufen" von CONTAINS-Abfragen. Die Funktionalität zum Zugriff auf den Index wird eben nur einmal anstatt mehrmals aufgerufen; alle evtl. Initialisierungen finden nur einmal statt.
Das im Text oben beschriebene Beispiel (zuerst "normal", dann mit Stemming, danach Fuzzy) würde als Aufruf dann so aussehen ...
select * from dokument_tabelle
where contains (
  dokument, 
  '<query>
     <textquery lang="GERMAN" grammar="CONTEXT">
       <progression>
         <seq>{[suchwort]}</seq>
         <seq>{$[suchwort]}</seq>
         <seq>{FUZZY([suchwort], 75, 100, weight)}</seq>
         <seq>{FUZZY([suchwort], 60, 200, weight)}</seq>
         <seq>{FUZZY([suchwort], 40, 300, weight)}</seq>
       </progression>
     </textquery>
     <score datatype="INTEGER" algorithm="DEFAULT"/>
  </query>'
)>0
where rownum < 10;
Hier habe ich mal den Operator FUZZY anstelle des einfachen Fragezeichens (?) verwendet - der Unterschied ist, dass FUZZY parametrisiert werden kann. Mehr Informationen zur Syntax des FUZZY-Operators finden sich in der Oracle Dokumentation: TEXT Reference.

Beliebte Postings