Neben dem "normalen" Context Index gibt es schon seit geraumer Zeit den kaum bekannten
"kleinen Bruder" CTXCAT. Dieses Posting möchte ich daher gerne dem CTXCAT-Index widmen.
Möchte man die Unterschiede in einem Satz herausarbeiten, so bietet der CTXCAT-Index zunächst mal
weniger Features, ist für kleinere Dokumente gedacht und arbeitet synchron, ist also einfacher zu
verwalten. Im einzelnen ...
- Der CTXCAT Index ist für kleinere Textfragmente gedacht - in der Dokumentation ist von "wenigen Zeilen" die Rede; um das auf den Punkt zu bringen; das Maximum sollten so 50 Worte sein ...
- Der Index arbeitet komplett synchron - per DML gemachte Änderungen sind sofort im Index sichtbar. Der Prozess des Index-Synchronisierens und -Optimierens fällt hier also weg.
- Es werden weniger "Features" unterstützt - so gibt es keine Data Stores wie beim CONTEXT-Index. Die zu indizierenden Daten müssen also genau so in der Tabellenspalte drinstehen.
- XML wird nicht unterstützt, da CTXCAT keine Section Groups kennt
- CTXCAT unterstützt allerdings sog. Sub-Indexes, mit denen man Mixed Queries unterstützen kann. Eine Mixed Query ist eine Abfrage, die sowohl relationale (strukturierte) als auch Volltextkriterien enthält.
Der CTXCAT-Index wird genauso angelegt wie ein CONTEXT-Index, nur mit einem
anderen Indextypen ... Als Beispiel nehmen wir eine Art Umfragetabelle, in der
die Ergebnisse einer Umfrage zur "Servicequalität" gespeichert werden ...
create table umfrageergebnis( id number(10), altersang number(3), note number(1), kommentar varchar2(1000) );
Der Index kann sofort erzeugt werden ...
create index suche_idx on UMFRAGEERGEBNIS(KOMMENTAR) indextype is ctxsys.ctxcat;
Wie der CONTEXT-Index unterstützt auch der CTXCAT-Index linguistische
Features. Das folgende Beispiel legt den Index explizit als Case-Sensitiv
fest und definiert den Bindestrich als sog. Printjoin.
begin ctx_ddl.create_preference('CAT_LEXER','BASIC_LEXER'); ctx_ddl.set_attribute('CAT_LEXER','MIXED_CASE','YES'); ctx_ddl.set_attribute('CAT_LEXER','PRINTJOINS', '-'); end; / create index suche_idx on UMFRAGEERGEBNIS(KOMMENTAR) indextype is ctxsys.ctxcat parameters ('LEXER CAT_LEXER');
Stopwortlisten werden natürlich analog unterstützt. Nun kann man ein paar
Dokumente einpflegen ...
insert into umfrageergebnis values (1, 35, 1, 'service war ausgezeichnet'); insert into umfrageergebnis values (2, 55, 3, 'ganz gut'); insert into umfrageergebnis values (3, 45, 5, 'ich wurde unfreundlich bedient'); insert into umfrageergebnis values (4, 42, 2, 'etwas unfreundlich, aber fehlerfrei und korrekt');
Und man kann sofort abfragen ...
select * from umfrageergebnis where catsearch(kommentar, 'unfreundlich', null) > 0; ID ALTERSANG NOTE KOMMENTAR ---------- ---------- ---------- ------------------------------------------------ 3 45 5 ich wurde unfreundlich bedient 4 42 2 etwas unfreundlich, aber fehlerfrei und korrekt
Interessant wären nun die Mixed Queries. Angenommen, wir interessieren
uns für die Ergebnisse, die aufgrund von "Unfreundlichkeit" schlecht waren. Also
brauchen wir noch ein relationales Kriterium auf der Schulnote. Zunächst also
den Index wieder droppen ...
drop index suche_idx;
Und nun erzeugen wir ein Index Set; dieses enthält die strukturierten
"Unter-Indizes". Dem Index-Set wird dann ein Index auf die Spalten "Note", "Alter" und
auf die Kombination "Note und Alter" hinzugefügt.
begin ctx_ddl.create_index_set('umfrage_iset'); ctx_ddl.add_index('umfrage_iset','note'); ctx_ddl.add_index('umfrage_iset','altersang'); ctx_ddl.add_index('umfrage_iset','note, altersang'); end;
Danach den Index neu anlegen ...
create index suche_idx on UMFRAGEERGEBNIS(KOMMENTAR) indextype is ctxsys.ctxcat parameters ('LEXER CAT_LEXER INDEX SET UMFRAGE_ISET');
Nun wieder abfragen ... mit dem CATSEARCH-Operator ...
select * from umfrageergebnis where catsearch(kommentar, 'unfreundlich', 'note >= 4') > 0; ID ALTERSANG NOTE KOMMENTAR ---------- ---------- ---------- ------------------------------------------------ 3 45 5 ich wurde unfreundlich bedient
Schauen wir uns mal den Index genauer an. Wie der CONTEXT-Index
besteht auch der CTXCAT-Index aus Tabellen, die ins Schema
gelegt werden, und die mit dem Präfix DR$ beginnen. Nur
ist es beim CTXCAT-Index genau eine-Tabelle ...
SQL> desc DR$SUCHE_IDX$I Name Null? Typ ----------------------------------------- -------- ------------------ DR$TOKEN NOT NULL VARCHAR2(64) DR$TOKEN_TYPE NOT NULL NUMBER(3) DR$ROWID NOT NULL ROWID DR$TOKEN_INFO NOT NULL RAW(2000) ALTERSANG NOT NULL NUMBER(3) NOTE NOT NULL NUMBER(1)
Und hier seht Ihr, dass der CTXCAT-Index wesentlich simpler aufgebaut ist als
der CONTEXT-Index. So werden ROWIDs direkt verwendet (der CONTEXT-Index generiert
die kompakteren DOCIDs). Die soeben erzeugten "Sub-Indexes" führen dazu, dass
zusätzliche Spalten angelegt werden. Das alles führt dazu, dass CTXCAT-Indizes
recht viel Platz verbrauchen - die ROWIDs belegen
10 Byte und auch die Werte der zusätzlichen Spalten werden für jedes Wort (Token)
dupliziert. Es ist einleuchtend, warum dieser Index nur für kleine Textfragmente
geeignet ist - bei großen Dokumenten würde er schlicht explodieren ...
Hierzu ein kleiner Test. Wir haben eine Tabelle mit ca. 300.000 kleineren Texten
(ein CTXCAT-Index kommt also in Frage). Die Tabelle ist (festgestellt mit
diesem Skript)
ca. 30MB groß.
select * from table(get_space_info('TEST_CTXCAT')); SEGMENT_NAME COLUMN_NAME PARTITION_NAME SEGMENT_TYPE ALLOC_BYTES FREE_BYTES -------------------------------- ------------------------------ -------------------------------- -------------------------------- ----------- ---------- TEST_CTXCAT TABLE 31457280 65536 1 Zeile wurde ausgewählt.
Auf diese Tabelle legen wir nun einen CTXCAT-Index und einen CONTEXT-Index und
stellen auch hierfür die jeweilige Größe fest. Zunächst der CTXCAT-Index (er besteht nur
aus einer einzigen "DR$"-Tabelle.
select segment_name, column_name, segment_type, alloc_bytes / 1048576 alloc_mb, free_bytes / 1048576 free_mb from table(get_space_info('DR$IDX_TEST_CTXCAT$I')) SEGMENT_NAME COLUMN_NAME SEGMENT_TYPE ALLOC_MB FREE_MB -------------------------------- ---------------------------------------- -------------------------------- -------- -------- DR$IDX_TEST_CTXCAT$I TABLE 80,00 0,00 DR$IDX_TEST_CTXCAT$R DR$ROWID INDEX 62,00 0,56 DR$IDX_TEST_CTXCAT$X DR$TOKEN,DR$TOKEN_TYPE,DR$ROWID INDEX 59,00 0,14
Das wären also ca. 200MB für den CTXCAT-Index. Schauen wir uns den CONTEXT-Index an. Hierfür
muss die GET_SPACE_INFO-Funktion für alle zum CONTEXT-Index gehörenden DR$-Tabellen
ausgeführt werden - das wären die $I, die $N, die $R und die $K-Tabelle.
TABLE SEGMENT_NAME COLUMN_NAME SEGMENT_TYPE ALLOC_MB FREE_MB ----- -------------------------------- ---------------------------------------- -------------------------------- -------- -------- $I DR$IDX_TEST_CONTEXT$I TABLE 11,00 0,95 $I SYS_LOB0000225699C00006$$ TOKEN_INFO LOBSEGMENT 0,06 0,00 $I DR$IDX_TEST_CONTEXT$X TOKEN_TEXT,TOKEN_TYPE,TOKEN_FIRST, ... INDEX 0,19 0,03 $I SYS_IL0000225699C00006$$ LOBINDEX 0,06 0,03 $N SYS_IOT_TOP_225707 NLT_DOCID INDEX 0,06 0,03 $R DR$IDX_TEST_CONTEXT$R TABLE 0,06 0,00 $R SYS_LOB0000225704C00002$$ DATA LOBSEGMENT 6,00 0,13 $R SYS_IL0000225704C00002$$ LOBINDEX 0,06 0,00 $K SYS_IOT_TOP_225702 TEXTKEY INDEX 9,00 0,59
Zusammengezählt in etwa 25MB. Alle linguistischen Einstellungen Stopwortlisten, Lexer
sind identisch. Man sieht hier sehr deutlich den Unterschied.
Wann ist also ein CTXCAT-Index sinnvoll ... ? Naja, vor allem dann, wenn
Ihr kleine Datenmengen in der Tabellenspalte habt, auf einen ständig
synchronen Volltextindex Wert legt und Mixed Queries benötigt. Letzteres
ist ab Oracle11g allerdings auch mit dem CONTEXT Index möglich (Composite Domain Index).
Wenn der Text aber in mehreren Spalten ist oder die Textfragmente größer
werden, dann werden CTXCAT-Indizes schnell zu groß. Ein
CONTEXT-Index
mit Multi-Column Datastore wesentlich kompakter und effizienter. Der CTXCAT-Index ist halt, wie eingangs gesagt,
der "kleine Bruder" des CONTEXT Index und eignet sich nur für bestimmte Fälle ...