Posts mit dem Label xml werden angezeigt. Alle Posts anzeigen
Posts mit dem Label xml werden angezeigt. Alle Posts anzeigen

Montag, 2. Dezember 2013

Oracle12c: Unterstützung für XML-Namespaces in Oracle TEXT

Heute geht es nochmals um Oracle12c: Oracle TEXT hat ein hochinteressantes neues Feature dazubekommen: Und zwar die Unterstützung für XQuery Full Text. Damit bietet Oracle TEXT nun wirklich eine vollständige Unterstützung für XML. Aber Moment mal: Konnte Oracle TEXT nicht schon immer mit XML umgehen? Im Prinzip ja - aber einige XML-Besonderheiten wurden von Oracle TEXT nicht unterstützt - zum Beispiel XML Namespaces. Dazu ein kleines Beispiel: Zunächst füllen wir eine Tabelle mit ein paar XML-Dokumenten:
create table tab_xml (id number, docs xmltype)
/

/*
 * Das Dokument enthält ein Tag "tag" aus dem Namespace "http://mynamespaces.com/ns1"
 */
insert into tab_xml values (
  1, 
  '<ns1:dokument xmlns:ns1="http://mynamespaces.com/ns1" 
                 xmlns:ns2="http://mynamespaces.com/ns2">
    <ns1:tag att="attr1">Das ist ein Text</ns1:tag>
   </ns1:dokument>'
)
/

/*
 * Das Dokument enthält ein Tag "tag" aus dem Namespace "http://mynamespaces.com/ns2"
 * Also ein anderer Namespace als Dokument 1
 */
insert into tab_xml values (
  2, 
  '<ns1:dokument xmlns:ns1="http://mynamespaces.com/ns1" 
                 xmlns:ns2="http://mynamespaces.com/ns2">
    <ns2:tag att="attr1">Das ist ein Text</ns2:tag>
   </ns1:dokument>'
)
/

/*
 * Das Dokument enthält ein Tag "tag" aus dem Namespace "http://mynamespaces.com/ns2"
 * Also gleicher Namespace wie Dokument 2 - ABER: anderer Präfix!
 */
insert into tab_xml values (
  3, 
  '<ns_1:dokument xmlns:ns_1="http://mynamespaces.com/ns1" 
                  xmlns:ns_2="http://mynamespaces.com/ns2">
    <ns_2:tag att="attr1">Das ist ein Text</ns_2:tag>
   </ns_1:dokument>'
)
/

commit
/
Das Besondere an diesen Dokumenten sind die XML Namespaces. Die XML-Tags enthalten ein sogenanntes Namespace-Präfix. Das ist aber noch nicht der Namespace selbst - denn oben im ersten Tag wird dieser Präfix mit dem Attribut xmlns auf den tatsächlichen Namespace abgebildet. Das erste Dokument definiert also zwei Namespaces: http://mynamespaces.com/ns1 und http://mynamespaces.com/ns2. Das XML-Tag names tag trägt den Präfix ns1, gehört also zum Namespace http://mynamespaces.com/ns1. Im zweiten Dokument werden die gleichen Namespaxes und auch die gleichen Namespace-Präfixe verwendet - nur gehört das Tag tag hier zum anderen Namespace http://mynamespaces.com/ns2.
Im dritten Dokument kommt es nun: Auch hier werden die gleichen Namespaces verwendet, aber mit anderen Präfixen. Das XML Tag tag trägt nun den Präfix ns_2 - dieser ist aber auf den Namespace http://mynamespaces.com/ns2 gemappt - es ist also tatsächlich das gleiche Tag wie in Dokument 2.
In XML kann der gleiche Namespace tatsächlich mit unterschiedlichen Präfixen angesprochen werden; wenn man wissen möchte, ob zwei XML Tags identisch sind, reicht es also nicht aus, nur auf die Präfixe zu schauen; man muss nachsehen, auf welche Namespaces diese Präfixe gemappt wurden. Und genau das tut Oracle TEXT bis einschließlich Oracle11g nicht. Legen wir zunächst einen "klassischen" Oracle TEXT-Index mit "XML-Unterstützung" an.
create index ft_tabxml on tab_xml (docs)
indextype is ctxsys.context
parameters ('section group ctxsys.path_section_group')
/
Ein mit der PATH_SECTION_GROUP erzeugter Volltextindex eröffnet die Möglichkeit, mit den CONTAINS-Operatoren INPATH und HASPATH zu arbeiten. Diese unterstützen XML auch ganz wunderbar; man kann ganz ähnlich zu XPath arbeiten und auch Attribute werden unterstützt. Nur ... mit den Eigenheiten der XML-Namespaces kann die PATH_SECTION_GROUP in Oracle11g nicht umgehen ...
select id from tab_xml
where contains(docs, 'text INPATH(/ns1:dokument/ns2:tag)') > 0
/

        ID
----------
         2

1 Zeile wurde ausgewählt.
Es gibt von der Syntax her keine Möglichkeit, einen Namespace "richtig" anzugeben. Man kann nur den Namespace-Präfix angeben (und der wird so behandelt, als wäre er Teil des Tag-Namens). Aber Oracle TEXT kann in 11g nicht nachsehen, auf welchen Namespace er gemappt wurde und ob der gleiche Namespace noch von anderen Präfixen verwendet wird. Dokument 3 verwendet einen anderen Präfix, daher wird es nicht gefunden - es müsste aber gefunden werden, denn es ist das gleiche Tag. Doch Oracle12c schafft Abhilfe - zunächst muss der Index etwas anders erzeugt werden ...
begin
  ctx_ddl.create_section_group('my_sg_xquery', 'PATH_SECTION_GROUP');
  ctx_ddl.set_sec_grp_attr('my_sg_xquery', 'xml_enable', 'true');
end;
/
sho err

create index ft_tabxml on tab_xml (docs)
indextype is ctxsys.context
parameters ('section group my_sg_xquery')
/
Vor dem CREATE INDEX muss eine eigene Section Group vom Typ PATH_SECTION_GROUP erstellt werden. Das muss sein, denn bei dieser muss das Attribut XML_AWARE auf TRUE gestellt sein. Danach kann der Index normal erzeugt werden.
Nun liegt ein Textindex vor, der auch XML-Namespaces vollständig unterstützt. Nicht erweitert wurde dagegen die Syntax des CONTAINS-Operators - und das hat einen guten Grund: Denn für die Volltextsuche in XML-Dokumenten gibt es inzwischen einen Standard - XQuery Full Text - und diesen sollte man auch verwenden. Es machte also keinen Sinn, mit einer Erweiterung für CONTAINS neue, proprietäre Syntax zu schaffen, man entschied sich, auf den Standard zu setzen. Und der sieht so aus:
SELECT id
FROM tab_xml
WHERE XMLExists('declare namespace ns="http://mynamespaces.com/ns2";
                 //ns:tag[. contains text "text"]'
                  PASSING docs)
/
Achtung: Wenn Ihr mit SQL*Plus arbeitet, kann euch das Semikolon in der dritten Zeile in die Query kommen - SQL*Plus betrachtet die Abfrage als dort zu Ende. Das kann man mit einem set sqlterminator # umstellen. Diese Abfrage findet nun alles, was gefunden werden soll ...
        ID
----------
         2
         3

2 Zeilen ausgewählt.
Schauen wir nun mal in die Indexstrukturen hinein. Wie immer bei Oracle TEXT, ist der Index tatsächlich in den DR$-Tabellen abgelegt.
SQL> select tname from tab where tname like 'DR$%'
  2  /

TNAME
-------------------------------------------------------
DR$FT_TABXML$R
DR$FT_TABXML$N
DR$FT_TABXML$K
DR$FT_TABXML$I
DR$FT_TABXML$E
DR$FT_TABXML$D

6 Zeilen ausgewählt.
Die XML-Unterstützung ist nun in den $D- und $E-Tabellen enthalten. Die $E-Tabelle kann man lesen: Wie die folgende Abfrage zeigt, speichert Oracle TEXT dort alle XML-Tags mitsamt Ihren Namespaces ab.
SQL> select * from DR$FT_TABXML$E
/

ID           NAMESPACE                    LNAME        NS
------------ ---------------------------- ------------ ----------------------------
_-_1         http://mynamespaces.com/ns1  dokument     http://mynamespaces.com/ns1
_-_2         http://mynamespaces.com/ns1  tag          http://mynamespaces.com/ns1
_-_3         http://mynamespaces.com/ns2  tag          http://mynamespaces.com/ns2

3 Zeilen ausgewählt.
Die $D-Tabelle gehört eigentlich zum Save Copy Feature von Oracle TEXT, welches hier gar nicht explizit angesprochen wurde. In dieser Tabelle speichert Oracle TEXT, aktiviert man das Feature, gefilterte Versionen von Binärdokumenten ab, um Snippet- oder Markup-Operationen schneller durchführen zu können (auch dazu gibt es noch ein Blog Posting). Ein "XML-Aware" Textindex nutzt diese Tabellen aber ebenfalls für seine interne Verarbeitung. Die Spalte DOC der $D-Tabelle enthält eine nochmals aufbereitete Variante des XML-Dokumentes.
Zusammenfassend kann man also sagen, dass Oracle TEXT in der Version 12c eine umfangreiche und wirklich komplette Volltextsuche für XML anbietet - mit der Unterstützung für XQuery Full Text. Weitere Informationen zum Thema findet Ihr im XML DB Developers' Guide: Indexing XML Data for Full-Text Queries. Viel Spaß beim Ausprobieren.

Donnerstag, 18. April 2013

Indexstatistiken (INDEX_STATS) im XML-Format: Oracle TEXT Management einfach automatisieren!

Heute geht es um Statistiken für einen Oracle TEXT Index - vor einiger Zeit gab es dazu schon mal ein Posting - heute soll es darum gehen, wie man diese Statistiken so zur Verfügung stellt, dass man mit Reporting- oder Management-Tools einfach darauf zugreifen oder diese zur Automatisierung von Aufgaben verwenden kann. Indexstatistiken kann man mit CTX_REPORT.INDEX_STATS abrufen. In nahezu allen Beispielen (so auch im erwähnten Blog-Posting) werden die Statistiken als Textausgabe erzeugt - in etwa wie folgt ...
===========================================================================
                    STATISTICS FOR "TESTIT"."IDX_TEXT"
===========================================================================

indexed documents:                                                     10
allocated docids:                                                      10
$I rows:                                                               56

---------------------------------------------------------------------------
                             TOKEN STATISTICS
---------------------------------------------------------------------------
:
Dieses Format ist zwar gut lesbar, zur Automatisierung jedoch völlig ungeeignet. Allerdings bietet Oracle TEXT noch eine andere Variante zur Ausgabe der Statistiken an: XML - und das geht wie folgt.
DROP TABLE ausgabe
/

CREATE TABLE ausgabe (
  index_name  varchar2(200),
  zeitstempel date,
  resultat    xmltype
)
xmltype column resultat store as binary xml
/

 
declare
  ergebnis clob := null;
begin
  ctx_report.index_stats(
    index_name     => 'IDX_TEXT',
    report         => ergebnis,
    report_format  => ctx_report.fmt_xml,
    stat_type      => null
  );
  insert into ausgabe values ('IDX_TEXT', sysdate, xmltype(ergebnis));
  dbms_lob.freetemporary(ergebnis);
end;
/

 
set long 32000
set head off
set pagesize 10000

SELECT * FROM ausgabe
/
Das generierte Format sieht nun wie folgt aus ...
<CTXREPORT>
  <INDEX_STATS>
    <STAT_INDEX_NAME>"TESTIT"."IDX_TEXT"</STAT_INDEX_NAME>
    <STAT_INDEX_STATS>
      <STAT_STATISTIC NAME="indexed documents">10</STAT_STATISTIC>
      <STAT_STATISTIC NAME="allocated docids">10</STAT_STATISTIC>
      <STAT_STATISTIC NAME="$I rows">56</STAT_STATISTIC>
      :
Bei großen Indizes braucht der Aufruf von INDEX_STATS sehr lange - hier ist es sicher sinnvoll, mit Hilfe von DBMS_SCHEDULER einen Job zu erzeugen, welcher die Statistiken regelmäßig (bspw. über Nacht) aktualisiert. Das Interessante am XML-Format ist nun, dass es sich maschinell auswerten lässt. Dazu nutzen wir die XML-Funktionen in der Oracle-Datenbank und erstellen eine (relationale) View auf die XML-Ausgabe von CTX_REPORT.INDEX_STATS.
create or replace view view_index_stats as
select
  a.index_name,
  a.zeitstempel,
  x.indexed_documents,
  x.allocated_docids,
  x.dollar_i_rows,
  x.dollar_i_dsize,
  x.index_frag,
  x.garbage_docids,
  x.garbage_size
from 
  ausgabe a,
  xmltable(
    '/CTXREPORT/INDEX_STATS'
    passing a.resultat
    columns 
      indexed_documents number path '/INDEX_STATS/STAT_INDEX_STATS/STAT_STATISTIC[@NAME="indexed documents"]',
      allocated_docids  number path '/INDEX_STATS/STAT_INDEX_STATS/STAT_STATISTIC[@NAME="allocated docids"]',
      dollar_i_rows     number path '/INDEX_STATS/STAT_INDEX_STATS/STAT_STATISTIC[@NAME="$I rows"]',
      dollar_i_dsize    number path '/INDEX_STATS/STAT_FRAG_STATS/STAT_STATISTIC[@NAME="total size of $I data"]',
      index_frag        varchar2(10) path '/INDEX_STATS/STAT_FRAG_STATS/STAT_STATISTIC[@NAME="estimated row fragmentation"]',
      garbage_docids    number path '/INDEX_STATS/STAT_FRAG_STATS/STAT_STATISTIC[@NAME="garbage docids"]',
      garbage_size      number path '/INDEX_STATS/STAT_FRAG_STATS/STAT_STATISTIC[@NAME="estimated garbage size"]'
  ) x
/
Diese View lässt sich nun wie eine Tabelle verwenden ...
SQL> select * from view_index_stats

INDEX_NAME ZEITSTEMPEL          INDEXED_DOCUMENTS ALLOCATED_DOCIDS
---------- -------------------- ----------------- ----------------
DOLLAR_I_ROWS DOLLAR_I_DSIZE INDEX_FRAG GARBAGE_DOCIDS GARBAGE_SIZE
------------- -------------- ---------- -------------- ------------
IDX_TEXT   18.04.2013 14:43:49                 10               10
           56            198  0 %                    0            0


1 Zeile wurde ausgewählt.
Nicht so schön ist die Spalte INDEX_FRAG - denn das XML, welches von INDEX_STATS generiert wurde, enthält hier tatsächlich das Prozentzeichen - weshalb es nicht als NUMBER aufgefasst werden kann - hierfür müssen wir in der View-Definition noch ein wenig was tun ...
create or replace view view_index_stats as
select
  a.index_name,
  a.zeitstempel,
  x.indexed_documents,
  x.allocated_docids,
  x.dollar_i_rows,
  x.dollar_i_dsize,
  to_number(replace(x.index_frag, ' %', '')) as index_frag,
  x.garbage_docids,
 :
Danach ist auch die Spalte INDEX_FRAG vom Typ NUMBER und man kann nun numerische Vergleiche durchführen. Analog dazu lassen sich auch die Token-Statistiken entsprechend aufbereiten ... Dazu bauen wir eine zweite View ...
create or replace view index_stats_frag_tokens as
select
  a.index_name,
  a.zeitstempel,
  x.token_text,
  x.token_type,
  to_number(replace(x.token_frag, ' %', '')) as token_frag
from 
  ausgabe a,
  xmltable(
    '/CTXREPORT/INDEX_STATS/STAT_FRAG_STATS/STAT_TOKEN_LIST/STAT_TOKEN'
    passing a.resultat
    columns 
      token_text   varchar2(64) path '/STAT_TOKEN/STAT_TOKEN_TEXT',
      token_type   varchar2(50) path '/STAT_TOKEN/STAT_TOKEN_TYPE',
      token_frag   varchar2(20) path '/STAT_TOKEN/STAT_TOKEN_STATISTIC'
  ) x
/
  
select * from index_stats_frag_tokens
/
... deren Inhalt nun wie folgt aussieht ...
SQL> select * from index_stats_frag_tokens

INDEX_NAME ZEITSTEMPEL          TOKEN_TEXT           TOKEN_TYPE           TOKEN_FRAG
---------- -------------------- -------------------- -------------------- ----------
IDX_TEXT   18.04.2013 14:55:28  ZU                   0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WOLLEN               0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WIRTSCHAFT           0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WIRD                 0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WICHTIGER            0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WERDEN               0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WER                  0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WEITER               0:TEXT                        0
IDX_TEXT   18.04.2013 14:55:28  WAHLKAMPF            0:TEXT                        0
:
Lässt man das XML nun, wie schon erwähnt, per DBMS_SCHEDULER regelmäßig aktualisieren, so hat man (wie im Data Dictionary) bequem nutzbare Views mit Statistiken zum Index. Diese können nun natürlich auch für eine automatisierte Verarbeitung genutzt werden. So könnte ein Job regelmäßig alle Tokens optimieren (CTX_DDL.OPTIMIZE mit OPTLEVEL_TOKEN), die eine bestimmte Fragmentierung überschreiten. Gleiches gilt natürlich auf Indexebene.
Auch die Integration mit Management-Werkzeugen wie dem Oracle Enterprise Manager ist kein Problem. In der deutschsprachigen DBA Community ist beschrieben, wie man eine eigene "Metrik", basierend auf einer SQL-Abfrage einrichtet. Nimmt man hierfür die erstellten Views, so kann der Enterprise Manager einen Alert senden, sobald die Indexfragmentierung einen bestimmten Schwellenwert übersteigt. Die professionelle Wartung einer Oracle TEXT-Installation ist damit kein Problem mehr ...

Beliebte Postings