Dienstag, 28. Juli 2009

CTX_QUERY.EXPLAIN: Erklärungen für eine TEXT-Abfrage

Vor einiger Zeit hatten wir ein Blog Posting zum Thema "Fuzzy-Suche". Darin könnt Ihr nachlesen, wie eine Ähnlichkeitssuche mit Oracle TEXT funktioniert und wie Ihr sie parametrisieren könnt. Wie beschrieben, führt Oracle TEXT eine sog. Termexpansion durch. Die Suchabfrage wird also zunächst um Tokens aus dem Textindex, die dem gesuchten Begriff ähnlich sind, erweitert und anschließend wird mit dieser Token-Liste eine OR-Suche durchgeführt. Die Parameter des FUZZY-Operators steuern diese Termexpansion.
Nun wäre es schön, wenn man sich ansehen könnte, was er da tut - welche Tokens also in die Liste mit aufgenommen werden. Und genau das ist mit der Prozedur CTX_QUERY.EXPLAIN möglich (übrigens nicht nur für die Fuzzy-Suche, sondern für alle TEXT-Abfragen). Die EXPLAIN-Funktion generiert eine Art "Ausführungsplan" für den Oracle TEXT Index.
Zunächst benötigen wir eine EXPLAIN TABLE - dort schreibt die Prozedur die Erklärungen zu einer Textquery hinein. In der Oracle-Dokumentation "Text Reference" findet Ihr Erlärungen zu Aufbau und Inhalt. Erzeugt wird sie mit diesem CREATE TABLE-Kommando:
create table meine_explain_tabelle(
  explain_id  varchar2(30),
  id          number,
  parent_id   number,
  operation   varchar2(30),
  options     varchar2(30),
  object_name varchar2(64),
  position    number,
  cardinality number
);
Wenn Ihr die Tabelle erstellt habt, könnt Ihr euch eine Textquery beschreiben lassen.
begin
  ctx_query.explain(
    index_name    => 'IDX_DOKUMENTE',
    text_query    => '?sptial or geodaten',
    explain_table => 'MEINE_EXPLAIN_TABELLE'
  );
end;
/
Anschließend stehen die Erklärungen in der erzeugten EXPLAIN-Tabelle. Wichtig ist der Parameter SHARELEVEL, der in diesem Aufruf nicht angegeben wurde. Der Default ist "0" (Null), was bedeutet, dass die Tabelle vorher leergeräumt wurd (TRUNCATE). Wenn Ihr EXPLAIN-Ergebnisse aufheben möchtet, setzt den Parameter auf "1" - zusätzlich benötigt Ihr dann eine EXPLAIN_ID, damit Ihr die Erklärungen später wiederfinden könnt.
Die Einträge in der Tabelle sind hierarchisch organisiert - am besten fragt Ihr sie daher mit einem START WITH - CONNECT BY wie folgt ab.
select
  lpad(' ',level * 2)|| to_char(id, '99') id, 
  operation,
  options,
  object_name,
  position
from meine_explain_tabelle
start with parent_id =0 
connect by parent_id = prior id
/
Das Ergebnis sieht dann etwa so aus
ID         OPERATION       OPTIONS    OBJECT_NAME     POSITION 
---------- --------------- ---------- --------------- --------
    1      OR                                                1
      2    EQUIVALENCE     (?)        sptial                 1
        3  WORD                       Spatial                1
        4  WORD                       special                2
        5  WORD                       Special                3
        6  WORD                       Spezial                4
        7  WORD                       Spiel                  5
        8  WORD                       spielt                 6
        9  WORD                       spielte                7
       10  WORD                       Spitze                 8
       11  WORD                       sptial                 9
     12    WORD                       geodaten               2
Man erkennt sehr schön die ähnlichen Worte, die Oracle TEXT in die Abfrage eingebunden hat; man könnte nun, wie im Blog Posting zur "Fuzzy-Suche" beschrieben, das Schlüsselwort FUZZY nutzen und parametrisieren: Wie versuchen also die Abfrage FUZZY(sptial,70,5) or geodaten. Die in Frage kommenden Wörter müssen sich also ähnlicher sein (Wert "70" im Gegensatz zum Default von "60") und es werden maximal fünf Wörter in die Termexpansion einbezogen. Das Ergebnis ...
ID         OPERATION       OPTIONS    OBJECT_NAME     POSITION 
---------- --------------- ---------- --------------- -------- 
    1      OR                                                1
      2    EQUIVALENCE     (?)        sptial                 1
        3  WORD                       Spatial                1
        4  WORD                       sptial                 2
      5    WORD                       geodaten               2
Und wenn man nach "Spatial" im Zusammenhang mit "Geodaten" sucht, erkennt man sofort, dass diese Suche viel zielgenauer ist. Gerade wenn es um das Spielen mit den Parametern für eine FUZZY-Suche geht, ist die EXPLAIN-Funktion eine wertvolle Hilfe.
Viel Spaß beim Ausprobieren!

Dienstag, 7. Juli 2009

Mixed Queries in 11g

MDATA Sections sind in 10g eingeführt worden, um gemischte Abfragen (auch mixed queries genannt) - also Abfragen mit Text- und relationalen Anteilen, besser handhaben zu können. Generell können damit kurze Textfelder(sogenannte Metadaten), die als Ganzes im Textindex indiziert wurden, einfach abgefragt werden. Mehr Informationen zur MDATA-Nutzung finden Sie Metadatensuche mit MDATA Blog und im MData Section und MULTI_COLUMN_DATASTORE Blog. Abfragen auf die Metadaten wie prod_list_price und flag sehen dann beispielsweise folgendermassen aus:

SELECT prod_id, prod_list_price, prod_desc
FROM products
WHERE contains (prod_desc, 'Card AND MDATA(prod_list_price, 69.99)
AND MDATA(flag,N)') > 0;

Ein wichtiger Unterschied zu gewohntem Sectionverhalten ist, dass MDATA Bereiche transaktionell verändert werden können, ohne den Rest des Index zu beeinträchtigen bzw. zu re-indizieren. Nachteile dieser Technologie ist die Tatsache, dass nur auf Gleichheit abgfragt werden kann und zusätzlich die MDATA Werte als einziges Token behandelt und minimal normalisiert werden können (Whitespace-Entfernung etc). Daher ist in 11g eine weitere Form der Section Suche eingeführt worden - die SDATA Section (SDATA steht dabei für Structured Data). Die Indizierung der SDATA Section erlaubt Operationen wie Range Scans, Nutzung von Funktionen, Projektionen usw. So können neue Kombinationen aus Text und strukturierte Anteilen abgefragt werden. Um die Unterschiede aufzuzeigen, nehmen wir das Beispiel aus MData Section und MULTI_COLUMN_DATASTORE und verwenden dabei die neue SDATA Section. Wir belassen den MULTI_COLUMN_DATASTORE my_multi_pref, und erzeugen eine SDATA Section mit Namen prod_list_price.

connect sh/sh
execute ctx_ddl.drop_section_group('my_seg');
begin
ctx_ddl.create_section_group(group_name=>'my_seg',group_type=>'basic_section_group');
ctx_ddl.add_sdata_section('my_seg','PROD_LIST_PRICE','prod_list_price', 'NUMBER');
end;
/
DROP INDEX mdata_index;
DROP INDEX sdata_index;
CREATE INDEX sdata_index ON products(prod_desc)
INDEXTYPE IS ctxsys.context
PARAMETERS ('DATASTORE my_multi_pref SECTION GROUP my_seg sync (on commit)');

SELECT err_text FROM ctx_user_index_errors WHERE err_index_name = 'SDATA_INDEX';
no rows selected

Folgende Abfrageart mit dem SDATA-Operator ist nun möglich.

SELECT prod_id, prod_list_price, prod_desc FROM products
WHERE contains (prod_desc, 'Card AND SDATA(prod_list_price >= 69.99)') > 0;

   PROD_ID PROD_LIST_PRICE
---------- ---------------
PROD_DESC
--------------------------------------------------------------------------------
        25          112.99
SIMM- 8MB PCMCIAII card

        26          149.99
SIMM- 16MB PCMCIAII card

       138           69.99
256MB Memory Card

Untersucht man genauer die neuangelegten Objekte, wird man feststellen, dass ein zusätzliches Indexsegment, eine IOT-Tabelle mit Namen DR$SDATA_INDEX$S, erzeugt wurde.

Nun stellt sich die Frage, ob das Ganze nicht einfacher zu bewerkstelligen ist, ohne zusätzlich Sections zu verwenden. Die Antwort dazu gibt die neue Composite Domain Index Technologie. Mit nur einem CREATE INDEX-Kommando ohne zusätzliche SDATA Sections kann dies erreicht werden. Folgendes Kommando zeigt die Implementierung in unserem Fall. Die FILTER BY Klausel ermöglicht dabei die Teilabfrage auf die Spalte PROD_LIST_PRICE vollständig im Text-Index durchzuführen.

DROP INDEX sdata_index;

CREATE INDEX comp_index ON products(prod_desc)
INDEXTYPE IS ctxsys.context
FILTER BY prod_list_price;

Ein kurzer Blick auf die erzeugten Objekte, gibt den Hinweis darauf, dass die SDATA Technologie offensichtlich als Grundlage dient, da wir nun eine zusätzliche IOT-Tabelle DR$COMP_INDEX$S besitzen. Die Abfragen können nun im gewohnten Stil ohne Verwendung von speziellen Operatoren verwendet werden.

SELECT prod_id, prod_list_price, prod_desc 
FROM products
WHERE contains (prod_desc, 'Card')>0  AND prod_list_price <= 69.99;

   PROD_ID PROD_LIST_PRICE
---------- ---------------
PROD_DESC
--------------------------------------------------------------------------------
       136           32.99
64MB Memory Card

       137           52.99
128MB Memory Card

       138           69.99
256MB Memory Card

 
Mehr zur Composite Domain Index Technik in einem der nächsten Blogs.....

Beliebte Postings