2012-07-20

PL/SQL - Eine Liste von Tokens aus einem Text mit Hilfe von Regular Expressions erstellen

Problem:

Ein Text soll in PL/SQL nach Auftreten von Ausdrücken durchsucht werden. Als Ergebnis sollen die Treffer als Collection zurückgeben werden.

Lösung:

Der hier vorgestellte Lösungsansatz basiert auf der Verwendung von Regular Expressions. Wir nutzen eine Nested Table zum Sammeln der gefundenen Token und nutzen diesen Typ auch als Rückgabe unserer Splitfunktion.
Mit der Standard-PL/SQL-Funktion regexp_substr lassen sich Bereiche aus einem String herauslesen:
REGEXP_SUBSTR(
   source_string,
   pattern [,
   position [,
   occurrence [,
   match_parameter ]]]
)
source_string:       Zu untersuchender Text
pattern:                 regular expression
position:                Position in source_string, ab der gesucht werden soll
occurence:            Nr. des Tokens, welches als Ergebnis zurückgeben
                            werden soll

match_parameter:  kann eine beliebige Kombination der folgenden
                             Angaben sein:

                                   i: Groß-/Kleinschreibung wird ignoriert
                                  c: Groß-/Kleinschreibung wird beachtet
                                  n: Punkt (.) im Pattern gibt an, dass neue
                                      Zeilen ebenfalls beachtet werden sollen

                                  m: ^ und $ markieren den Anfang bzw.
                                      das Ende einer Zeile bei Multi-Line Strings
Des Weiteren wird die Funktion regexp_instr verwendet, die die Position (Anfang oder Ende) eines Teilstrings ermittelt:
REGEXP_INSTR(
   source_string,
   pattern [,
   position [,
   occurrence [,
   return_option[,
   match_parameter ]]]]
)
source_string:       Zu untersuchender Text
pattern:                regular expression
position:               Position in source_string, ab der gesucht werden soll
occurence:           Nr. des Tokens, welches als Ergebnis zurückgeben
                           werden soll

return_option:      0, wenn die erste Position eines gefundenen Tokens
                               zurückgegeben werden soll;

                           1, wenn die Position nach dem gefundenen Token
                               ausgegeben werden soll

match_parameter: kann eine beliebige Kombination der folgenden
                            Angaben sein:

                                   i: Groß-/Kleinschreibung wird ignoriert
                                  c: Groß-/Kleinschreibung wird beachtet 
                                  n: Punkt (.) im Pattern gibt an, dass neue
                                      Zeilen ebenfalls beachtet werden sollen

                                  m: ^ und $ markieren den Anfang bzw. das
                                      Ende einer Zeile bei Multi-Line Strings
Im Folgenden werden hier zwei Lösungswege vorgestellt. In der ersten Lösung (reg_split(p_string, p_pattern)) wird der String über ein CONNECT-BY-SELECT-Statement mehrfach untersucht. Dabei wird die LEVEL-Variable dazu genutzt den Occurence-Parameter zu füllen.
Die zweite Lösung (reg_split_rec(p_string, p_pattern, p_pos)) verwendet einen rekursiven Ansatz, bei dem der nächste verfügbare Startpunkt an die nächste Rekursionsebene übergeben wird.
IMPLEMENTIERUNG ALS ANONYMER PL/SQL-BLOCK:
==========================================

declare
   type nt_tab_vc2 is table of varchar2(4000);
   v_return nt_tab_vc2;

   function reg_split(p_string in varchar2, 
                      p_pattern in varchar2)
   return nt_tab_vc2 is
      v_ret nt_tab_vc2;
   begin
      v_ret := nt_tab_vc2();
      select
         regexp_substr(p_string, p_pattern, 1, level) token
      bulk collect into
         v_ret
      from
         dual
      where
         regexp_substr(p_string, 
                       p_pattern, 
                       1,
                       level,
                       'i') is not null
      connect by
         regexp_instr(p_string, 
                      p_pattern, 
                      1, 
                      level) > 0;
      return v_ret;
   end;

   function reg_split_rec(p_string in varchar2, 
                          p_pattern in varchar2,
                          p_pos in number default 1)
   return nt_tab_vc2 is
      v_ret nt_tab_vc2;
      v_token varchar2(4000);
      v_pos_next number;
   begin
      v_token := regexp_substr(p_string, 
                               p_pattern, 
                               p_pos, 
                               1);
      if v_token is not null then
         --pattern is found
         v_pos_next := regexp_instr(p_string, 
                                    p_pattern, 
                                    p_pos,
                                    1,
                                    1);

         v_ret := reg_split(p_string,
                           
p_pattern,
                           
v_pos_next);
         v_ret.extend(1);
         v_ret(v_ret.last) := v_token;
         return v_ret;
      else
         --pattern is not found
         return nt_tab_vc2();
      end if;
  end;

begin
   v_return := reg_split(
                    'Hello @world@! The weather is @great@ today.', 
                    '(\@)([a-z-]\w+)(\@)');
   if v_return.count > 0 then
      for i in v_return.first .. v_return.last
      loop
         dbms_output.put_line(lpad(i, 
                                   round(log(10,v_return.count))+1)||' '||v_return(i));
      end loop;
   end if;
end;

BEISPIEL-AUSGABE:
=================
1 @world@
2 @great@

Keine Kommentare:

Kommentar veröffentlichen