Benutzer-Werkzeuge

Webseiten-Werkzeuge


prog:oracle_apex_oracle_text_document_archive

Mit Oracle APEX 5 und Oracle Text ein Dokumentenarchive für technische Dokumentation aufbauen

Oracle 12c, APEX 5.0

Erstellt März 2016

Apex macht Katzen glücklich

Aufgabe:

In Oracle APEX 5 soll ein einfaches Dokumentenarchive für die Volltext Suche über technischer Dokumentation erstellt werden.

Details:

  • Die Dokumente verbleiben auf dem Fileserver im Dateisystem
  • Der Text der binären Dokumente wie PDF, PowerPoint, Word etc. kann frei durchsucht werden
  • Für das Wandeln in Text werden die Oracle Filter und eigene PDF Filter eingesetzt
  • Die Dokumente werden über den Inhalt in in Kategorien, wie Java, Datenbank Entwicklung, Python etc. eingeordnet
  • Die Suche findet über eine APEX Suchmaske statt

Als Datenbank kommt eine Oracle 12c R1 als Standard Edition zum Einsatz, prinzipiell reicht aber auch eine Oracle XE.

Für den Volltext Index wird Oracle Text eingesetzt, mehr dazu im Detail siehe hier ⇒ Oracle Text - Volltext Suche über Text Dokumente

Vortrag

Die Ergebnisse aus diesen kleinen Projekt werden auf der APEX Connect 2016 vorgestellt.

DOAG - Apex Connect 2016 Konferenz- April 2016
26. bis 28. April.2016 in Berlin - Die größte Entwicklerkonferenz rund um Oracle PL/SQL und Oracle APEX in Deutschland

Mein Thema: Oracle Text - Übersicht - Neue Möglichkeiten unter 12c - Integration in Apex - Indizieren von binären Daten

Ablauf

  • Datenmodell für die Dokumenttabelle und die Pflege eines eigenen Thesaurus
  • Ladeskript für die Dokumente in Python erstellen und Daten laden
  • Filter Script für eigenen PDF Filter erstellen
  • APEX Pflege Oberfläche für den Thesaurus
  • Thesaurus pflegen und für Oracle Text erstellen
  • Theme Index Information in der Datenbank hinterlegen
  • Oracle Text Index konfigurieren
  • Oracle Text Index auf den Dokumenten anlegen
  • APEX Suchmaske für die Dokumente erstellen

Datenmodell

Die Dokumente werden als BFILE Referenz gespeichert.

Beim Laden werde soviel Metadaten wie möglich gleich mitgespeichert.

Dokumente - Tabelle DOCUMENTS

 Dokumententabelle für ein APEX Dokumenten Archiv

Documents.sql
---------------------------------------------------------
-- Document table
---------------------------------------------------------
 
CREATE TABLE Documents
  (
    ID                  NUMBER (30) NOT NULL ,
    Filename            VARCHAR2 (512 CHAR) NOT NULL ,
    FileTyp             VARCHAR2 (32 CHAR) ,
    FileDirectory       VARCHAR2 (2000 CHAR) NOT NULL ,
    FilePointer         BFILE   NOT NULL ,
    MD5Hash             VARCHAR2 (32 CHAR) NOT NULL ,
    FileCreateDate      DATE ,
    FileLastModify      DATE ,
    LANGUAGE            VARCHAR2 (32 CHAR) ,
    CreateDate          DATE DEFAULT sysdate,
    CreateUser          VARCHAR2 (32 CHAR) DEFAULT USER,
    ChangeDate          DATE DEFAULT sysdate,
    ChangeUser          VARCHAR2 (32 CHAR) DEFAULT USER,
    Theme_data_avaiable VARCHAR2 (1 CHAR)  DEFAULT 'N'
  ) ;
 
CREATE UNIQUE INDEX IDX_Documents_ID_PK ON Documents (  ID ASC );
 
ALTER TABLE Documents ADD CONSTRAINT Document_PK PRIMARY KEY ( ID ) ;
 
COMMENT ON TABLE Documents IS   'Store the file information' ;
COMMENT ON COLUMN Documents.ID IS  'Primary Key' ;
COMMENT ON COLUMN Documents.Filename IS  'Name of the file on disk' ;
COMMENT ON COLUMN Documents.FileTyp IS  'Typ of the file' ;
COMMENT ON COLUMN Documents.FileDirectory IS  'Directory of the file' ;
COMMENT ON COLUMN Documents.FilePointer IS  'Bfile Pointer to the File' ;
COMMENT ON COLUMN Documents.MD5Hash IS  'Hash of the files to indentify dublicate files' ;
COMMENT ON COLUMN Documents.FileCreateDate IS  'File Create Time from the file' ;
COMMENT ON COLUMN Documents.FileLastModify IS  'Last modificatoin date from the file' ;
COMMENT ON COLUMN Documents.Language IS  'Language of the file (Oracle NLS Format String!)' ;
COMMENT ON COLUMN Documents.CreateDate IS  'Date when the record was created' ;
COMMENT ON COLUMN Documents.CreateUser IS  'User create the record' ;
COMMENT ON COLUMN Documents.ChangeDate IS  'Last Change on the record' ;
COMMENT ON COLUMN Documents.ChangeUser IS  'User change the record' ;
COMMENT ON COLUMN Documents.Theme_data_avaiable IS  'If Themdata is there => Y, if not N' ;
 
---------------------------------------------------------
 
CREATE SEQUENCE documents_seq;

Thesaurus - Tabelle THESAURUS

thesaurus.sql
---------------------------------------------------------
-- thesaurus tables
---------------------------------------------------------
DROP TABLE thesaurus cascade CONSTRAINT
/
CREATE TABLE thesaurus(
 id		NUMBER(11) NOT NULL
,name		varchar2(255) NOT NULL
,description 	varchar2(2000)
)
/
--pk
CREATE UNIQUE INDEX idx_thesaurus_id_pk ON thesaurus ( id ASC );
ALTER TABLE thesaurus ADD CONSTRAINT thesaurus_pk PRIMARY KEY ( id ) ;
--uk
CREATE UNIQUE INDEX idx_thesaurus_name_uk ON thesaurus (name);
--comment
comment ON TABLE thesaurus IS 'the name and the meaning of the thesaurus' ;
comment ON COLUMN thesaurus.id IS 'primary key' ;
comment ON COLUMN thesaurus.name IS 'name of the thesaurus' ;
comment ON COLUMN thesaurus.description IS 'description of the thesaurus' ;
 
---------------------
DROP TABLE thesaurus_phrases  cascade CONSTRAINT
/
 
CREATE TABLE thesaurus_phrases(
 id		NUMBER(11) NOT NULL
,ths_id 	NUMBER(11) NOT NULL
,phrase 	varchar2(255) NOT NULL
)
/
--pk
CREATE UNIQUE INDEX idx_thesaurus_phrases_id_pk ON thesaurus_phrases ( id ASC );
ALTER TABLE thesaurus_phrases ADD CONSTRAINT thesaurus_phrases_pk PRIMARY KEY ( id ) ;
--uk
CREATE UNIQUE INDEX idx_thesaurus_phrases_uk ON thesaurus_phrases (phrase);
--comment
comment ON TABLE thesaurus_phrases IS 'all phrases of this thesaurus';
comment ON COLUMN thesaurus_phrases.id IS 'primary key' ;
comment ON COLUMN thesaurus_phrases.ths_id IS 'fk to the thesaurus name' ;
comment ON COLUMN thesaurus_phrases.phrase IS 'the phrase' ;
---------------------
DROP TABLE thesaurus_relations cascade CONSTRAINT
/
 
CREATE TABLE thesaurus_relations(
id 		NUMBER(11) NOT NULL
,ths_id 	NUMBER(11)
,thp_phrase 	NUMBER(11) NOT NULL
,relation 	varchar2(3) NOT NULL
,thp_rel_phrase NUMBER(11) NOT NULL
)
/
CREATE UNIQUE INDEX idx_thesaurus_relations_pk ON thesaurus_relations ( id ASC );
ALTER TABLE thesaurus_relations ADD CONSTRAINT thesaurus_relations_pk PRIMARY KEY ( id ) ;
 
--uk
CREATE UNIQUE INDEX idx_thesaurus_relations_uk ON thesaurus_relations (ths_id,thp_phrase,relation,thp_rel_phrase);
 
--comment
comment ON TABLE thesaurus_relations IS 'all relations of this thesaurus';
comment ON COLUMN thesaurus_relations.id IS 'primary key' ;
comment ON COLUMN thesaurus_relations.ths_id IS 'fk to the thesaurus name' ;
comment ON COLUMN thesaurus_relations.relation IS 'the relation between the phrases' ;
comment ON COLUMN thesaurus_relations.thp_phrase IS 'from this phrase' ;
comment ON COLUMN thesaurus_relations.thp_rel_phrase IS 'to this phrase' ;
---------------------
-- fk
---------------------
ALTER TABLE thesaurus_relations ADD CONSTRAINT thesaurus_relations_t_fk FOREIGN KEY (ths_id) REFERENCES thesaurus (id);
ALTER TABLE thesaurus_relations ADD CONSTRAINT thesaurus_relations_p1_fk FOREIGN KEY (thp_phrase) REFERENCES thesaurus_phrases (id);
ALTER TABLE thesaurus_relations ADD CONSTRAINT thesaurus_relations_p2_fk FOREIGN KEY (thp_rel_phrase) REFERENCES thesaurus_phrases (id);
-------
ALTER TABLE thesaurus_phrases ADD CONSTRAINT thesaurus_phrases_t_fk FOREIGN KEY (ths_id) REFERENCES thesaurus (id);
 
---------------------
-- Sequence
---------------------
DROP SEQUENCE thesaurus_seq;
DROP SEQUENCE thesaurus_phrases_seq;
DROP SEQUENCE thesaurus_relations_seq;
 
CREATE SEQUENCE thesaurus_seq;
CREATE SEQUENCE thesaurus_phrases_seq;
CREATE SEQUENCE thesaurus_relations_seq;

Über diese Tabellenstruktur lassen sich mehrere Thesaurus aufbauen und abbilden.

Aus den Daten in den Tabellen wird dann mit dem PL/SQL Package CTX_THES der eigentliche Thesaurus aufgebaut.


Ladeskript für die Dokumente in Python erstellen und Daten laden

Das Lade Script wird in Python erstellt.

Vorbereitung -Directory Objekt anlegen und Rechte vergeben

Über ein Directory Object wird der Einstieg in die Verzeichnisstruktur auf der Festplatte definiert.

In unseren Test Fall liegen die Daten unter „d:\data\info-archiv“ in einer Verzeichnisstruktur.

Anlegen als SYS und Rechte vergeben:

sqlplus / AS sysdba
#Directory anlegen
CREATE directory INFO_ARCHIVE AS 'D:\data\info-archiv';
#Rechte vergeben
GRANT READ,WRITE ON directory INFO_ARCHIVE TO GPI;

Testdaten manuell einfügen

Um den prinzipiellen Aufbau zu testen, können natürlich am Anfang ein paar Dokument zum Testen dort abgelegt werden.

Dazu einige auf Platte existierende Dokument unter dem Directory „INFO_ARCHIVE“ angeben. Hash und weitere Werte füllen wir später richtig mit der Laderoutine, hier geht es erstmal nur darum etwas testen zu können!

Um den Bfile Pointer zu setzen die Methode BFILENAME verwenden:

INSERT INTO documents (ID,filename,filetyp,FileDirectory,FilePointer,MD5Hash ) 
       VALUES  (documents_seq.nextval,'apex41-new-features-487382.ppt','PPT','apex',BFILENAME('INFO_ARCHIVE', 'apex\apex41-new-features-487382.ppt'),'HASH1');
INSERT INTO documents (ID,filename,filetyp,FileDirectory,FilePointer,MD5Hash ) 
        VALUES  (documents_seq.nextval,'Oracle-Dojo10-web.pdf','PDF','.', BFILENAME('INFO_ARCHIVE', 'Oracle-Dojo10-web.pdf'),'HASH4');
commit;

Lade Skript erstellen

Filter Script für eigenen PDF Filter von "Foxit PDF IFilter" erstellen

Über ein Script wird der Inhalt einer Datei in reinen Text bzw. HTML Text gewandelt. Auf diesen Text basiert dann auch die Volltext Suche über die binären Dokumente wie PDF,PowerPoint oder Word.

Siehe dazu auch ⇒ Oracle Text für die Indizierung binärer Daten verwenden

Für die PDF Dateien wird auf den Filter von https://www.foxitsoftware.com gesetzt.

Download Foxit PDF IFilter (30 Tage Trial für den ersten Test von https://www.foxitsoftware.com/downloads/#Foxit-PDF-IFilter-Server - Angabe der Kontaktdaten notwendig, Datei ca 9MB groß.

Installation Foxit PDF IFilter

Testsystem ist eine Windows 10 Workstation

  • Für die Installation Datei FoxitPDFIFilter311_Server_x64_enu.msi starten
  • Dialoge nacheinander bestätigen, alles Default belassen, keine Besonderen Einstellung
  • Am Ende wird dann noch nach der Lizenz gefragt, nach 30 Tage läuft dann das ab

Test ob der Filter auch registriert ist:

 Foxit PDF IFilter Test

Microsoft Windows Search per Script ansprechen

Der PDF Filter ist für das Microsoft "IFilter" Interface ausgelegt und muss normalerweise darüber auch angesprochen werden.

Um das in einem Script zu verwenden kann dazu das Werkzeug filtdump.exe von Microsoft verwandt werden. Das Programm dient eigentlich dazu die Filter zu testen.

Das Programm ist unter anderen in der „Microsoft Windows Search 3.x SDK“ enthalten und kann über https://www.microsoft.com/en-us/download/details.aspx?id=7388 geladen werden.

Datei herunterladen und entpacken, z.b. nach D:\tools\WindowsSearchSDK3x.

Erster Test führt aber zum Fehler Error 0x80004005 loading IFilter:

cd D:\tools\WindowsSearchSDK3x\Indexing\Filtdump
 
.\FiltDump.exe -b D:\data\info-archiv\linux_805.pdf
 
FILE: D:\data\info-archiv\linux_805.pdf
 
Error 0x80004005 loading IFilter

Das ist dann aber nicht so recht das gewünschte Ergebnis … ist ein 64bit Problem, Filtdump.exe ist in diesem Download nur 32Bit tauglich!

Nach etwas Internet Recherche könnte das Programm auch in MS SDK enthalten sein. Leider ist das auch nicht so klar was und wo da nun genau das richtige ist.

Habe daher als nächstes das Windows 10 SDK über https://developer.microsoft.com/de-de/windows/downloads/windows-10-sdk heruntergeladen und installiert.

Leider ist es nicht so klar in welchen Packet dort das Programm FiltDump.exe wirklich steckt.

Mit den folgenden ausgewählten Featuren war es dann aber dabei:

Installation Windows SDK für Windows 10

Nächster Test:

cd C:\Program Files (x86)\Windows Kits\10\bin\x64
 
 
.\FiltDump.exe -b  D:\data\info-archiv\linux_805.pdf
 
# viel Text wird angezeigt, aber keine echten Strukturen ....
# hmm hatte mehr erwartet

Das kann nun in eine Script eingebaut werden und damit lassen sich nun alle Filter die Microsoft auf dem System kennt verwenden!

Nun kann auch einfach überprüft werden welcher Filter mit welcher DDL für was eingesetzt wird:

 .\filtreg.exe
 
...
.pdf --> Foxit PDF Filter (C:\Program Files\Foxit Software\Foxit PDF IFilter\PDFFilt.dll)
...

USER_FILTER Script für den IFilter mit FiltDump

Der ersten Entwurf, der aber so nicht funktioniert, das hier die Endung fehlt:

@echo off
echo PARAM 1: %1 >> d:\temp\ctx_test.log
echo PARAM 2: %2 >> d:\temp\ctx_test.log
 
set FILTDUMP_HOME=C:\Program Files (x86)\Windows Kits\10\bin\x64
 
"%FILTDUMP_HOME%\FiltDump.exe"  -b -o %2 %1 >> d:\temp\ctx_test.log

Testweise aufrufen und pürfen ob Daten erzeugt wurden

D:\oracle\products\12.1.0.2\dbhome_1\ctx\bin\readDocument.cmd D:\data\info-archiv\linux_805.pdf d:\temp\pdf_export.txt

Und schon das nächste Problem, wird der Index angelegt und damit das Script im Scope des Oracle Users aufgerufen, erhalten ich eine „FILTDUMP failed, hr == 0x80070006“ Fehlermeldung.

Das Problem liegt wohl daran das Windows eine Datei Endung erwartet und von Oracle aber nur eine Datei OHNE Endung erhält.

Jetzt wird es kompliziert, jetzt rächt sich die Architektur vom USER_FILTER, das hier keine weiteren Parameter übergeben werden können.

Wir müssen nun mit einem Tool wie „file“ (aus der Unix Welt) erkennen um was es sich handelt. die passende Endung aus dem Mime Type ermitteln und dann diese anfügen um den passenden Filter auszuwählen.

Version 2, nun mit Erkennung des Datei Types und beiden Filteren:

@echo off
 
echo -- Start Filter at : %date% %time% >> d:\temp\ctx_test.log
echo -- Info: PARAM 0   : %0 >> d:\temp\ctx_test.log
echo -- Info: PARAM 1   : %1 >> d:\temp\ctx_test.log
echo -- Info: PARAM 2   : %2 >> d:\temp\ctx_test.log
 
set ORACLE_HOME=D:\oracle\products\12.1.0.2\dbhome_1
set FILTDUMP_HOME=C:\Program Files (x86)\Windows Kits\10\bin\x64
set MAGIC_HOME=D:\tools\file\bin

rem use fixed path, error get result as oracle job???
for /f %%i in ('D:\tools\file\bin\file.exe -b %1') do set FILE_TYPE=%%i
 
echo -- Info: File type : %FILE_TYPE% >> d:\temp\ctx_test.log
 
IF %FILE_TYPE% NEQ PDF goto ORACLE_FILTER
 
:FILTER_PDF
echo -- Info: Use Filter: iFilter  >> d:\temp\ctx_test.log
copy %1 %1.pdf 2>> d:\temp\ctx_test.log 1>nul
"%FILTDUMP_HOME%\FiltDump.exe" -b -o %2 %1.pdf >> d:\temp\ctx_test.log
del %1.pdf 2>> d:\temp\ctx_test.log 1>nul
goto END
 
:ORACLE_FILTER
echo -- Info: Use Filter: Oracle   >> d:\temp\ctx_test.log
%ORACLE_HOME%/bin/ctxhx.exe %1 %2 >> d:\temp\ctx_test.log
 
:END
echo -- End Filter at   : %date% %time% >> d:\temp\ctx_test.log
echo -- =============================  >> d:\temp\ctx_test.log

Allerdings ist das ganze nicht wirklich stabil, nach ein paar Versuchen wieder ORA-07445 Fehler erhalten!

Nach einiger Suche sind das PDF Dokument mit Password!!, die bleiben anscheinend dann wohl im Hintergrund hängen und warten auf ein Passwort!

Besser wäre natürlich eine direkte Integration in der PowerShell oder ähnlich, hier dazu ein paar Anregungen dazu:


APEX Pflege Oberfläche für den Thesaurus

Für die Pflege des Thesaurus wird eine einfache Formular Maske in APEX generiert.

siehe http://docs.oracle.com/html/E39147_04/tree_query.htm

 Ein Tree in Apex um einen Thesaurus darzustellen

Beispiel für das SQL um den Baum aufzubauen:

SELECT  CASE WHEN connect_by_isleaf = 1 THEN 0 WHEN level = 1 THEN 1 ELSE -1 END AS STATUS
        , level
        , name AS title
        , 'icon-tree-folder' AS icon                    
        , id AS VALUE
        , tooltip AS tooltip
        , CASE WHEN item_type = 'S' THEN 
               apex_util.prepare_url('f?p='||:app_id||':65:'||:app_session||':T:::P_ELEMENT_ID:'||id||','||link)
		  ELSE
		    NULL
          END AS link 
   FROM (SELECT 'P' AS item_type
                , thes.name AS label
                , to_char (thes.id) AS id
                , NULL AS parent
                , thes.name name
                , thes.name AS tooltip
                , NULL link
          FROM thesaurus thes
          UNION ALL
          SELECT 'T' AS item_type
               , tp.phrase AS label
               , to_char (tp.id) || '-' || to_char (tp.ths_id) AS id
               , to_char (tp.ths_id) parent
               , tp.phrase AS name
               , tp.phrase AS tooltip
               , to_char (tp.id) link
           FROM thesaurus_phrases tp
           UNION ALL
           SELECT 'S' item_type
                , rs.relation label
                , to_char (rs.id)||'-'||to_char (rs.thp_phrase)||'-'||to_char(rs.thp_rel_phrase)  id
                , to_char (rs.thp_phrase) || '-' || to_char (rs.ths_id) AS parent
                , '-> Relation :'||rs.relation || ' to ' || tp_rel.phrase name
                , rs.relation || ' ' || tp_rel.phrase tooltip
                , to_char (rs.id) idlink
           FROM thesaurus_relations rs INNER JOIN thesaurus_phrases tp_rel ON (rs.thp_rel_phrase = tp_rel.id)
        )
  START WITH parent IS NULL
CONNECT BY prior id = parent
  ORDER siblings BY name

Der eigentliche Thesaurus wird dann über PL/SQL aus den Texteinträgen in dieser Thesaurus Tabelle erzeugt.


Thesaurus pflegen und für Oracle Text erstellen, Theme Information in der Datenbank hinterlegen

Aus den Tabelleneinträgen für unseren Thesaurus wird mit dem PL/SQL Package „CTX_THES“ der eigentliche Thesaurus erstellt.

Das hätte auch über eine Text Datei stattfinden können, so ist das aber deutlich einfacher in der Pflege.

Siehe dazu auch ⇒ Mit einem Thesaurus und Oracle Text arbeiten

Die ersten Daten des Thesaurus werden per SQL in die DB eingespielt, das Feintuning kann dann über die APEX Oberfläche erfolgen:

variable THS_ID NUMBER
variable THP_PHRASE NUMBER
variable THP_REL_PHRASE NUMBER
 
 
INSERT INTO THESAURUS ( id ,name  ,description,lang) 
    VALUES (  thesaurus_seq.nextval,'T_PROD_DOCUMENT','Programming Dokumentation','EN') RETURN id INTO :THS_ID;
 
DOC
-----------------------------------
# 
Pascal
 SYN Delphi
 RT  Borland
SQL
 RT PL/SQL
Java
 NT SCALA 
 NT Groovy
JavaScript
 NT CoffeeScript
Python
 NT Juliactx
-----------------------------------
#
 
------------------------- Pascal
 
INSERT INTO  thesaurus_phrases  (id,ths_id,phrase)	VALUES(thesaurus_phrases_seq.nextval,:THS_ID,'Pascal') RETURN id INTO :THP_PHRASE;	
INSERT INTO  thesaurus_phrases  (id,ths_id,phrase)	VALUES(thesaurus_phrases_seq.nextval,:THS_ID,'Delphi') RETURN id INTO :THP_REL_PHRASE;
 
INSERT INTO thesaurus_relations (id,ths_id,thp_phrase,relation,thp_rel_phrase) VALUES(thesaurus_relations_seq.nextval,:THS_ID,:THP_PHRASE,'SYN',:THP_REL_PHRASE);
 
INSERT INTO  thesaurus_phrases  (id,ths_id,phrase)	VALUES(thesaurus_phrases_seq.nextval,:THS_ID,'Borland')RETURN id INTO :THP_REL_PHRASE;
INSERT INTO thesaurus_relations (id,ths_id,thp_phrase,relation,thp_rel_phrase) VALUES(thesaurus_relations_seq.nextval,:THS_ID,:THP_PHRASE,'SYN',:THP_REL_PHRASE);
 
----------------------- SQL
 
INSERT INTO  thesaurus_phrases  (id,ths_id,phrase)	VALUES(thesaurus_phrases_seq.nextval,:THS_ID,'SQL') RETURN id INTO :THP_PHRASE;	
INSERT INTO  thesaurus_phrases  (id,ths_id,phrase)	VALUES(thesaurus_phrases_seq.nextval,:THS_ID,'PL/SQL') RETURN id INTO :THP_REL_PHRASE;
 
INSERT INTO thesaurus_relations (id,ths_id,thp_phrase,relation,thp_rel_phrase) VALUES(thesaurus_relations_seq.nextval,:THS_ID,:THP_PHRASE,'RT',:THP_REL_PHRASE);
 
----------------------- Java
 
INSERT INTO  thesaurus_phrases  (id,ths_id,phrase)	VALUES(thesaurus_phrases_seq.nextval,:THS_ID,'Java') RETURN id INTO :THP_PHRASE;	
INSERT INTO  thesaurus_phrases  (id,ths_id,phrase)	VALUES(thesaurus_phrases_seq.nextval,:THS_ID,'SCALA') RETURN id INTO :THP_REL_PHRASE;
 
INSERT INTO thesaurus_relations (id,ths_id,thp_phrase,relation,thp_rel_phrase) VALUES(thesaurus_relations_seq.nextval,:THS_ID,:THP_PHRASE,'NT',:THP_REL_PHRASE);
 
INSERT INTO  thesaurus_phrases  (id,ths_id,phrase)	VALUES(thesaurus_phrases_seq.nextval,:THS_ID,'Groovy')RETURN id INTO :THP_REL_PHRASE;
INSERT INTO thesaurus_relations (id,ths_id,thp_phrase,relation,thp_rel_phrase) VALUES(thesaurus_relations_seq.nextval,:THS_ID,:THP_PHRASE,'NT',:THP_REL_PHRASE);
 
----------------------- JavaScript
INSERT INTO  thesaurus_phrases  (id,ths_id,phrase)	VALUES(thesaurus_phrases_seq.nextval,:THS_ID,'JavaScript') RETURN id INTO :THP_PHRASE;	
INSERT INTO  thesaurus_phrases  (id,ths_id,phrase)	VALUES(thesaurus_phrases_seq.nextval,:THS_ID,'CoffeeScript') RETURN id INTO :THP_REL_PHRASE;
 
INSERT INTO thesaurus_relations (id,ths_id,thp_phrase,relation,thp_rel_phrase) VALUES(thesaurus_relations_seq.nextval,:THS_ID,:THP_PHRASE,'NT',:THP_REL_PHRASE);
 
----------------------- Python
INSERT INTO  thesaurus_phrases  (id,ths_id,phrase)	VALUES(thesaurus_phrases_seq.nextval,:THS_ID,'Python') RETURN id INTO :THP_PHRASE;	
INSERT INTO  thesaurus_phrases  (id,ths_id,phrase)	VALUES(thesaurus_phrases_seq.nextval,:THS_ID,'Juliactx') RETURN id INTO :THP_REL_PHRASE;
 
INSERT INTO thesaurus_relations (id,ths_id,thp_phrase,relation,thp_rel_phrase) VALUES(thesaurus_relations_seq.nextval,:THS_ID,:THP_PHRASE,'NT',:THP_REL_PHRASE);
 
commit;

Um dann den eigentlichen Thesaurus aufbauen zu können, wird eine PL/SQL Procedure verwandt, hier die einfachste Variante:

SET serverout put ON
 
CREATE OR REPLACE PROCEDURE createthesaurus
IS
	cursor c_thes
	IS
		SELECT	*
			 FROM thesaurus
		ORDER BY id;
 
	cursor c_pharse (p_ths_id NUMBER)
	IS
		SELECT	*
			 FROM thesaurus_phrases
			WHERE ths_id = p_ths_id
		ORDER BY phrase;
 
	cursor c_relation (p_ths_id NUMBER)
	IS
		SELECT p1.phrase
			  , p2.phrase AS rel_phrase
			  , r.relation
		  FROM thesaurus_relations r
				 INNER JOIN thesaurus_phrases p1
					 ON (r.thp_phrase = p1.id)
				 INNER JOIN thesaurus_phrases p2
					 ON (r.thp_rel_phrase = p2.id)
		 WHERE r.ths_id = p_ths_id;
BEGIN
	FOR trec IN c_thes
	loop
		-- löschen und neu anlegen
		dbms_output.put_line ('-- Info :: drop thesaurus ::' || trec.name);
 
		BEGIN
			ctx_thes.drop_thesaurus (name => trec.name);
		exception
			WHEN others
			THEN
				dbms_output.put_line ('-- Info :: drop thesaurus Error ::' || sqlerrm);
		END;
 
		-- create the empty thesaurus case insensitiv
		dbms_output.put_line ('-- Info :: create thesaurus ::' || trec.name);
		ctx_thes.create_thesaurus (name		  => trec.name
										 , casesens   => FALSE
										  );
 
		--create a Phrase to the thesaurus
		FOR prec IN c_pharse (p_ths_id => trec.id)
		loop
			dbms_output.put_line ('-- Info :: create pharse ::' || prec.phrase);
			ctx_thes.create_phrase (tname 	=> trec.name
										 , phrase	=> prec.phrase
										  );
		END loop;
 
		-- define a relation to this phrases
		FOR rrec IN c_relation (p_ths_id => trec.id)
		loop
			dbms_output.put_line (
				'-- Info :: create relation ::' || rrec.phrase || ' - ' || rrec.relation || ' - ' || rrec.rel_phrase);
			ctx_thes.create_relation (tname		  => trec.name
											, phrase 	  => rrec.phrase
											, rel 		  => rrec.relation
											, relphrase   => rrec.rel_phrase
											 );
		END loop;
	END loop;
END;
/

Nach dem Aufruf mit „exec createthesaurus“ kann der Thesaurus verwandt werden.

Spannende Frage: Muss der Index neu aufgebaut werden?

Wenn nur die Thesaurus Funktion verwandt werden soll, wohl nicht, wird auf Theme gesetzt dann schon

Gerade am Testen, nur der Thesaurus kann auch ohne Neuaufbau verwendet werden, d.h. es können auch mehrere davon gleichzeitig eingesetzt werden.


Thesaurus für den Theme Index übersetzen

Vorhandenen Thesarus für den Theme Index übersetzen:

 ctxkbtc -user ctxsys/ctxsys -name T_PROD_DOCUMENT

Bzgl. Theme siehe auch Einen Oracle Theme Index aufbauen


Oracle Text Index konfigurieren

Unser User GPI, unter dem der Oracle Text Index aufgebaut wird, benötigt die Rolle CTXAPP.

Vor dem Aufbau einer Oracle Text Indexes können die verschiedenen Parameter für den Index Aufbau konfiguriert werden.

-- zuvor als sys
sqlplus / AS sysdba
 
GRANT CTXAPP TO GPI;
GRANT ALL ON ctxsys.ctx_thes TO GPI;
 
-- als context User die Eigenschaften einstellen
CONNECT admin
 
--- LEXER
EXEC ctx_ddl.create_preference( 'gpi_lexer', 'BASIC_LEXER' );
EXEC ctx_ddl.set_attribute ('gpi_lexer', 'INDEX_THEMES', 'YES');
 
-- STORAGE 
-- Forward Indexing
-- Plain Text speichern
BEGIN
ctx_ddl.create_preference
   (
    preference_name => 'GPI_BASIC_STORAGE',
    object_name     => 'BASIC_STORAGE'
    );
END;
/   
EXEC ctx_ddl.set_attribute('GPI_BASIC_STORAGE','forward_index','TRUE');
EXEC ctx_ddl.set_attribute('GPI_BASIC_STORAGE','save_copy','PLAINTEXT');

Oracle Text Index auf den Dokumenten anlegen

Index mit entsprechenden Eigenschaften dann anlegen

-- create the Oracle Text Index 
-- Frist try with AUTO_FILTER
 
DROP INDEX idx_doc_files;
 
CREATE INDEX idx_doc_files ON documents(FilePointer)  
             INDEXTYPE IS CTXSYS.CONTEXT 
             PARAMETERS ('FILTER   USER_FILTER_GPI_PREF
                          LEXER    GPI_LEXER
                          STORAGE  GPI_BASIC_STORAGE
			  STOPLIST GPI_STOPLIST')
/
 
 
--Auf Fehler prüfen
SELECT * FROM ctx_user_index_errors;
 
 
--
--Index tabellen anzeigen lassen
 
SELECT TABLE_NAME FROM user_tables WHERE TABLE_NAME LIKE '%IDX_DO%';
 
TABLE_NAME
-------------------
DR$IDX_DOC_FILES$I
DR$IDX_DOC_FILES$R
DR$IDX_DOC_FILES$K
DR$IDX_DOC_FILES$N
 
 
--testen ob Text Tockens auch eingetragen wurden
 
SELECT TOKEN_TEXT FROM DR$IDX_DOC_FILES$I WHERE rownum < 10;
 
 
--prüfen ob der Theme Index auch geklappt hat
SELECT token_type FROM dr$idx_documents_docs$i GROUP BY token_type;

Nun kann getestet werden ob eine erste Suche mit Hilfe eines Thesaurus auch erfolgreich ist:

SELECT * FROM DOCUMENTS WHERE contains(filepointer, 'NT(Java,1,T_PROD_DOCUMENT)') >0;

APEX Suchmaske für die Dokumente erstellen

Über eine APEX Maske wird die Volltextsuche über die Dokumente gesteuert

Für die Suche über den Index wird dazu der Oracle Contains Operator (siehe Oracle Text - In Texten suchen) eingesetzt, in der Suchmaske kann der Operator aus einer Werte Liste ausgewählt werden.

In der Treffermenge soll ein Stück des gefundenen Text als Preview in jeder Trefferzeile angezeigt werden.

Der gefundene Text wird dabei farblich hervorgehoben.

Für die Aufbereitung der Trefferliste wird das CTX_DOC Package eingesetzt, siehe dazu Oracle Text - Die Treffer in der Ergebnismenge hervorheben

Tree Ansicht über die Einträge im Thesaurus

Einfache Varianten nur auf Vorkommen der Suchwörter:

SELECT  CASE WHEN connect_by_isleaf = 1 THEN 0 WHEN level = 1 THEN 1 ELSE -1 END AS STATUS
        , level
        , name AS title
        , 'icon-tree-folder' AS icon                    
        , id AS VALUE
        , tooltip AS tooltip
        , CASE WHEN item_type = 'S' THEN 
               apex_util.prepare_url('f?p='||:app_id||':13:'||:app_session||':T:::P13_TEXT_KEY:'||id||','||link)
		  ELSE
		    NULL
          END AS link 
   FROM (SELECT 'P' AS item_type
                , thes.name AS label
                , to_char (thes.id) AS id
                , NULL AS parent
                , thes.name||' Search for :: '||nvl(:P15_SEARCH_TEXT,'oracle') name
                , thes.name AS tooltip
                , NULL link
          FROM thesaurus thes
          UNION ALL
          SELECT 'T' AS item_type
               , tp.phrase AS label
               , to_char (tp.id) || '-' || to_char (tp.ths_id) AS id
               , to_char (tp.ths_id) parent
               , tp.phrase AS name
               , tp.phrase AS tooltip
               , to_char (tp.id) link
           FROM thesaurus_phrases tp
           UNION ALL
           SELECT 'S' item_type
                , d.filename
                , to_char (d.id)||'-'||to_char (tp.phrase)    id
                , to_char (tp.id) || '-' || to_char (tp.ths_id) AS parent
                , '-> Found :'||d.filename || ' in ' || tp.phrase name
                , score(1) || '  to ' ||score(2) || tp.phrase tooltip
                , to_char (d.id) idlink
           FROM documents d,thesaurus_phrases tp 
          WHERE contains(d.filepointer,tp.phrase,1)>0 
            AND contains(d.filepointer,nvl(:P15_SEARCH_TEXT,'oracle'),2)>0
        )
  START WITH parent IS NULL
CONNECT BY prior id = parent
  ORDER siblings BY name

Sieht dann so aus:

 Oracle Apex  Tree zur Dokumentensuche


Quellen

Siehe jeweils die Detail Artikel

Cookies helfen bei der Bereitstellung von Inhalten. Durch die Nutzung dieser Seiten erklären Sie sich damit einverstanden, dass Cookies auf Ihrem Rechner gespeichert werden. Weitere Information
"Autor: Gunther Pipperr"
prog/oracle_apex_oracle_text_document_archive.txt · Zuletzt geändert: 2016/04/26 14:28 von Gunther Pippèrr