Heute möchte ich mich dem Thema "Suche nach Sonderzeichen" und dem damit verbundenen
  Thema Printjoins in Oracle TEXT widmen. Vorab schon soviel: Dieses Posting wird
  eine Warnung vor Printjoins - mit diesem Feature sollte sehr vorsichtig umgegangen werden.
  Printjoins werden mitunter verwendet, wenn
  man "Strukturen" wie Aktenzeichen oder "Autonummern" in den Dokumenten hat.
  Das könnte in etwa so aussehen.
create table dokumente( id number(10), doc varchar2(4000) ) / insert into dokumente values ( 1, 'Aktenzeichen 67.MEIER.1455-2012: Steuersache Meier.Erklärung abgegeben' ); insert into dokumente values ( 2, 'Aktenzeichen 12.MUSTER.1455-2012: Steuersache Muster.Erklärung abgegeben.' ); commit /
 Immer wieder kommt die Anforderung, exakt nach dem
 Aktenzeichen suchen zu können. Oracle Text erkennt diese Struktur jedoch nicht und indiziert 
 wie folgt:
create index ft_dokumente on dokumente(doc) indextype is ctxsys.context / select token_text from dr$ft_dokumente$i / TOKEN_TEXT ---------------------- 12 1455 2012 67 ABGEGEBEN AKTENZEICHEN ERKLÄRUNG MEIER MUSTER STEUERSACHE
  Wenn man nun nach dem Term 1455 sucht, werden beide Dokumente zurückgeliefert. Fachlich
  ist das eigentlich falsch, denn die 1455 kommt alleinstehend nirgends vor - sie ist überall
  Teil des Aktenzeichens.
SQL> select * from dokumente where contains(doc, '1455') > 0
/
        ID DOC
---------- --------------------------------------------------
         1 Aktenzeichen 67.MEIER.1455-2012: Steuersache Meier
           .Erklärung abgegeben
         2 Aktenzeichen 12.MUSTER.1455-2012: Steuersache Must
           er.Erklärung abgegeben.
 Um diesen Effekt zu verhindern, werden dann gerne Printjoins eingesetzt. Zeichen, die als
 Printjoins deklariert werden, trennen Wörter nicht mehr voneinander - sie werden dann 
 (nicht ganz) wie Buchstaben behandelt. Ist also das Zeichen "-" als Printjoin deklariert,
 dann wird der Willy-Brandt-Platz als ein Token "Willy-Brandt-Platz" indiziert und nicht 
 als drei Tokens "Willy", "Brandt" und "Platz".
begin
  ctx_ddl.drop_preference('MY_PJ_PREF');
end;
/
sho err
begin
  ctx_ddl.create_preference('MY_PJ_PREF', 'BASIC_LEXER');
  ctx_ddl.set_attribute('MY_PJ_PREF', 'PRINTJOINS', '.-');
end;
/
sho err
create index ft_dokumente on dokumente(doc)
indextype is ctxsys.context
parameters  ('lexer MY_PJ_PREF')
/
select token_text from dr$ft_dokumente$i
/
TOKEN_TEXT
-----------------------------
12.MUSTER.1455-2012
67.MEIER.1455-2012
ABGEGEBEN
AKTENZEICHEN
MEIER.ERKLÄRUNG
MUSTER.ERKLÄRUNG
STEUERSACHE
 Die Anforderung, dass Teile des Aktenzeichens nicht mehr das Aktenzeichen finden, ist erfüllt.
 Auf den ersten Blick ist das doch eine gute Lösung, oder ...?
  
select * from dokumente where contains(doc, '1455') > 0;
Es wurden keine Zeilen ausgewählt
select * from dokumente where contains(doc, '{67.MEIER.1455-2012}') > 0;
        ID DOC
---------- ----------------------------------------------------------------------
         1 Aktenzeichen 67.MEIER.1455-2012: Steuersache Meier.Erklärung abgegeben
 
 Wie man aber schon am Inhalt der Token-Tabelle erkennen kann, hat das ganze einige
 "Nebenwirkungen" ... die Suche nach dem Meier schlägt nun fehl.
SQL> select * from dokumente where contains(doc, 'Meier') > 0 / Es wurden keine Zeilen ausgewählt
 Das ist logisch, weil das Token Meier gar nicht indiziert wurde. In den Dokumenten
 fehlt dummerweise das Leerzeichen nach dem Punkt zwischen Meier und Erklärung. Da
 der Punkt selbst ein Printjoin ist, wurde Meier.Erklärung indiziert. Und eine Suche
 nach dem Meier schlägt nun fehl. 
 Printjoins werden stets global für den ganzen Index definiert. Wenn also
 der Bindestrich eines Aktenzeichens als Printjoin deklariert wird, gilt das nicht
 nur für die Aktenzeichen, sondern für alle Bindestriche im gesamten Dokumentbestand:
 
 Das Aufnehmen zusätzlicher Zeichen zu den Printjoins sollte also stets mit Vorsicht
    gemacht werden, es führt fast immer zu unerwünschten Nebenwirkungen, für die dann aufwändige
    Workarounds mit Wildcards ("Meier%") nötig werden. 
 Doch wie geht man mit dem Thema Aktenzeichen um?
 Eine denkbare Lösung könnte ein 
 PROCEDURE_FILTER sein. Dieser sucht mit einem regulären Ausdruck
 nach dem Aktenzeichen und wandelt die Bindestriche und Punkte in ein Zeichen, welches
 definitiv keine Probleme macht, um - das könnte bspw. der Underscore ("_") sein.
  Zunächst erstellen wir also die Prozedur für
 den PROCEDURE_FILTER.
create or replace function escape_aktenzeichen(p_az in varchar2) return varchar2 deterministic is begin return regexp_replace(p_az, '(\d\d)(.)([A-Z]*)(.)(\d*)(-)(\d*)', '\1_\3_\5_\7'); end escape_aktenzeichen; / sho err create or replace procedure aktenzeichen_filter( p_src IN VARCHAR2, p_dst IN OUT NOCOPY VARCHAR2 ) is begin p_dst := escape_aktenzeichen(p_src); end aktenzeichen_filter; / sho err
 Dass die eigentliche Funktionalität in eine separate Funktion gepackt wurde, hat einen Sinn - dazu weiter unten mehr. Dann erstellen wir die Filter Preference ...
begin
  ctx_ddl.create_preference('MY_AZ_FILTER', 'procedure_filter');
  ctx_ddl.set_attribute('MY_AZ_FILTER', 'procedure', 'aktenzeichen_filter');
  ctx_ddl.set_attribute('MY_AZ_FILTER', 'input_type', 'varchar2');
  ctx_ddl.set_attribute('MY_AZ_FILTER', 'output_type', 'varchar2');
  ctx_ddl.set_attribute('MY_AZ_FILTER', 'rowid_parameter', 'false');
  ctx_ddl.set_attribute('MY_AZ_FILTER', 'charset_parameter', 'false');
end;
/
sho err
 
  ... und nicht zu vergessen: Wir definieren die Lexer Preference neu, damit der
  Underscore (und nur der Underscore) das neue Printjoin wird. 
begin
  ctx_ddl.drop_preference('MY_PJ_PREF');
end;
/
sho err
begin
  ctx_ddl.create_preference('MY_PJ_PREF', 'BASIC_LEXER');
  ctx_ddl.set_attribute('MY_PJ_PREF', 'PRINTJOINS', '_');
end;
/
sho err
 
 Nun noch indizieren ...
create index ft_dokumente on dokumente(doc)
indextype is ctxsys.context
parameters  ('lexer MY_PJ_PREF filter MY_AZ_FILTER')
/
 Und jetzt sieht die Token-Tabelle so aus:
TOKEN_TEXT ------------------------- 12_MUSTER_1455_2012 67_MEIER_1455_2012 ABGEGEBEN AKTENZEICHEN ERKLÄRUNG MEIER MUSTER STEUERSACHE
 Eine Suche nach 1455 schlägt nun fehl, so wie es sein soll.
select * from dokumente where contains(doc, '1455') > 0; Es wurden keine Zeilen ausgewählt
 Wenn nun nach einem Aktenzeichen gesucht werden soll, muss man das Aktenzeichen
 in der Suchanfrage natürlich auch umwandeln - es darf also nicht mehr nach 12.MUSTER.1455-2012,
 vielmehr muss nach 12_MUSTER_1455_2012 gesucht werden. Und jetzt ist es sehr nützlich, dass
 wir vorhin die Funktion ESCAPE_AKTENZEICHEN gebaut haben ...
select * from dokumente where contains(doc, escape_aktenzeichen('12.MUSTER.1455-2012')) > 0;
        ID DOC
---------- --------------------------------------------------
         2 Aktenzeichen 12.MUSTER.1455-2012: Steuersache Must
           er, Erklärung abgegeben.
 Voilá. Und das ganze lässt sich natürlich auch mit binären (PDF, Office)-Dokumenten
 kombinieren - in diesem Fall muss der PROCEDURE_FILTER vor dem Anwenden des regulären
 Ausdrucks mit 
 CTX_DOC.POLICY_FILTER das eigentliche Umwandeln des Binärformats
 in ASCII-Text machen.
create or replace procedure aktenzeichen_filter( p_src IN VARCHAR2, p_dst IN OUT NOCOPY VARCHAR2 ) is begin CTX_DOC.POLICY_FILTER( ... ); p_dst := escape_aktenzeichen(p_src); end aktenzeichen_filter; / sho err