SQLに正規表現マッチングのパワーを活用

Knut StolzeIBM Germany

Knut StolzeがDB2に取り組み始めたのは、IBMシリコンバレー研究所で客員サイエンティストとしてIBMに加わって時で、それ以来、DB2 Image Extenderに従事する。次にDB2 Spacial Extender V8の担当となり、2年以上にわたってExtenderの操作性、パフォーマンス、および標準準拠を改善するいくつかの機能拡張を担当。

現在、ドイツのイェーナ大学にティーチング・アシスタントを務めると同時に、連合データベースの分野でIBMの仕事にも従事している。メール・アドレス: stolze@de.ibm.com



2003年 1月 23日

重要: この記事を読む前に、特記事項をお読みください。

はじめに

データベースでテキスト・データを探すことは、アプリケーションで非常によくあるシナリオです。IBM®DB2® Universal Database™では、DB2 Text Information ExtenderやDB2 Net Search Extenderなど、いくつかの全文検索ベースの製品が提供されています。しかし、一連のDB2 ExtendersRには、ストリング中のパターンの検索および置換に使用される正規表現マッチングを実施する機能は含まれません。この記事では、正規表現マッチングに一般利用可能なライブラリーをDB2と統合するユーザー定義関数 (UDF) の実装方法を解説します。例題を通して、pcreライブラリー (Perl互換の正規表現) が使用されますが、このライブラリーは他のライブラリーと置き換えても構いません。

この記事での解説は、DB2 Universal Database for Linux, UNIX®, andWindows®のバージョン7とバージョン8に適用されます。


背景

このセクションでは、正規表現とはどのようなもので、それが有用である理由を説明します。

正規表現とは

正義表現はストリング中のパターンの検索および置換に使用できます。文法を使用して定義され、正規表現エンジンがその文法をストリングと比較します。エンジンはストリングが文法と一致したかどうか、つまり、文法から導かれるサブストリングがストリングに含まれるかどうかの標識を返します。さらに、エンジンはマッチング・サブストリングも返します。「パターン」という用語は、文法を表すために使用されます。

非常に基本的なパターンは、1文字だけで構成されます。この文字を含むストリングは、このパターンと比較された場合、必ず「一致」します。たとえば、パターンが「a」の場合、ストリング「abcd」は一致ですが、ストリング「xyz」はそうではありません。正規表現のパワーは事前定義される演算子 (メタ文字ともいいます) から生まれます。この演算子は非常にわずかなスペースでパターンを表現できます。方言やサポート機能によっては、使用できるメタ文字が異なる場合もあります。一般的には、次の文字が使えます。

| - 論理和 (または)
[ ] - グループ
* - 複数のオカレンス (0個以上)
+ - 複数のオカレンス (1個以上)
? - オプションのオカレンス
\\ - 円記号

さまざまなシステムが、一般の正規表現に対する多様な拡張機能を実装しています。プログラミング言語Perlで使用される正規式は、さらに多くの略語をサポートします。この文書で使用するライブラリーは、そのような拡張機能を実装しています。Perlの正規表現言語では、中でも次のような略語を使用できます。

\s - 任意の空白文字
\w - 任意の英数字
\d - 任意の数字

もう1つの高度な例は、パターン「[A-Z]* = ([0-9]|0x00);」です。このパターンは、いくつかの大文字の後にスペース1つ、等号、もう1つのスペース、そして数字1桁またはストリング「0x00」が続くサブストリングを含むストリングと一致します。そのサブストリングの最後の文字はセミコロンでなければなりません。Perlを使用すると、このパターンは「\w* = (\d|0x00);」として表せます。「NM = 0x00;」や「X = 7;」というストリングはこのパターンに一致しますが、ストリング「Z = 123;」は、123が複数桁で構成されるため、一致しません。


DB2におけるストリング・マッチング

Extendersとは別に、DB2ではテキスト比較用にいくつかの関数および演算子を使えます。しかしそのような関数および演算子は、パターン・マッチングには機能的に制約があったり、あるいはそれらを使用する照会を複雑化しかねません。使用できる関数をいくつか次に示します。

= または 述部。等しいまたは等しくないかについて2つのストリングを (1文字ずつ) 比較します。
LIKE述部。ワイルドカードを使用した基本的なパターン・マッチングです。
LOCATE関数。ストリング中のサブストリングを見つけます。

SQL演算子を使用してパターン「[A-Z]* = ([0-9]|0x00);」を表現することはできますが、面倒なものになります。たとえば、次のSELECTステートメントのWHERE文節で使われる述部は、リスト1のストリング「str」内の等号の後の部分に一致します。

リスト1. LIKEを使用したパターン・マッチング

SELECT str
FROM   strTable
WHERE ( str LIKE '% = 0;%' OR str LIKE '% = 1;%' OR str LIKE '% = 2;%' 
	OR str LIKE '% = 3;%' OR str LIKE '% = 4;%' OR str LIKE '% = 5;%' 
	OR str LIKE '% = 7;%' OR str LIKE '% = 7;%' OR str LIKE '% = 8;%' 
	OR str LIKE '% = 9;%' OR str LIKE '% = 0x00;%' )

それに加えて、「[A-Z]*」サブパターンに該当する述部は複雑で、ストリング1文字ずつ反復的に比較する関数を用いて実現できます。しかし、これを通して組み込み機能の使用は冗長で複雑であることがわかります。


サンプル・シナリオ

次のようなリスト (リスト2) を定義して、何行かを挿入してみましょう。

リスト2. サンプル表の作成

CREATE TABLE strTable ( c1 INTEGER, str VARCHAR(500) );
INSERT INTO strTable VALUES ( 1, 'some text;' ),
                            ( 2, 'variable = 1234;' ),
                            ( 3, 'var2 = ''string variable'';' ),
                            ( 4, 'xyz = ' ),
                            ( 5, 'myVar = 0x00;' ),
                            ( 6, '# comment' ),
                            ( 7, 'abc = def' );

以下のすべての例について、このリストとそのデータが使用されます。

SELECT * FROM strTable;

C1          STR
----------- ------------------------------
          1 some text;
          2 variable = 1234;
          3 var2 = 'string variable';
          4 xyz = 
          5 myVar = 0x00;
          6 # comment
          7 abc = def

  7 record(s) selected.

パターン・マッチングの実装

DB2の拡張方式を用いて、SQLステートメントでUDFを使用し、状況を劇的に改善できます。パターンとストリングを入力パラメーターとしてとるregex1というUDFを定義することにより、リスト1のWHERE文節はリスト3のように記述できます。

リスト3. regex UDFを使用してパターン・マッチングを簡素化する

SELECT str
FROM   strTable
WHERE regex1('\w* = (\d|0x00);', str) = 1

この例では、リスト1で対応するLIKE述部が指定された部分パターンだけでなく、Perlの拡張機能を使った正規表現を用いて、完全なパターン・マッチングが行われます。見てのとおり、LIKE述部を使用して同じ意味を表現するより、関数を使用してこのパターンに対する述部を記述するほうが、はるかに簡単です。

UDFの実装

サンプルの実装では、筆者はPCRE (Perl-Compatible Regular Expressions) と呼ばれる既存のパターン・マッチング・エンジンを選択しました。このエンジンは、パターン処理とマッチングの実施用にC APIを提供します。そのエンジンと照会で使用されるSQL言語を合わせても欠けているものは、UDFです。UDFは次の2つの部分で構成されます。

  • CREATE FUNCTIONステートメント。データベース内で関数を作成 (または登録) します。
  • 正規表現マッチング・エンジンに対するC API呼び出しを包むようなラッパーを実装する関数の本体です。

リスト4は、関数を作成するSQLステートメントを示しています。

リスト4. regex1関数を登録する

CREATE FUNCTION regex1(pattern VARCHAR(2048), string CLOB(10M))
    RETURNS INTEGER
    SPECIFIC regexSimple
    EXTERNAL NAME 'regexUdf!regexpSimple'
    LANGUAGE C
    PARAMETER STYLE DB2SQL
    DETERMINISTIC
    NOT FENCED
    RETURNS NULL ON NULL INPUT
    NO SQL
    NO EXTERNAL ACTION
    ALLOW PARALLEL;

注意:

文節の詳細な意味については、『DB2 SQLリファレンス』を参照してください。パラメーターの長さは、ユーザーのニーズに合わせて調整できます。ここで示す値は、推奨値を意味するものではありません。

2番目の部分は小さなCコードで構成され、UDFエントリー・ポイントを実装します。このエントリー・ポイントは照会実行中にDB2によって呼び出され、各行がパターンとマッチングされます。リスト5は、このコードの例です。Pcre_* 関数およびマクロの説明については、PCREライブラリーのマニュアルを参照してください。Cコードのコンパイルと共用ライブラリーの構築については、『DB2アプリケーション開発ガイド』を参照してください。

リスト5. rege1x UDFエントリー・ポイントを実装するCコード

#include <pcre.h>
#include <sqludf.h>

void regexpSimple(
    // input parameters
    SQLUDF_VARCHAR *pattern,      SQLUDF_CLOB *str,
    // output
    SQLUDF_INTEGER *match,
    // null indicators
    SQLUDF_NULLIND *pattern_ind,  SQLUDF_NULLIND *str_ind,
    SQLUDF_NULLIND *match_ind,
    SQLUDF_TRAIL_ARGS)
{
    pcre *re = NULL;
    const char *error = NULL;
    int errOffset = 0;
    int rc = 0;

    // we assume successful return
    *match_ind = 0;

    // compile the pattern to its internal representation
    re = pcre_compile(pattern, 0 /* default options */, &error,
        &errOffset, NULL);
    if (re == NULL) {
        snprintf(SQLUDF_MSGTX, 70, "Regexp compilation failed at "
            "offset %d: %s\n", errOffset, error);
        strcpy(SQLUDF_STATE, "38900");
        (*pcre_free)(re);
        return;
    }

    // match the string againts the pattern
    rc = pcre_exec(re, NULL, str->data, str->length, 0,
            0 /* default options */, NULL, 0);
    switch (rc) {
      case PCRE_ERROR_NOMATCH:
        *match = 0;
        break;
      case PCRE_ERROR_BADOPTION:
        snprintf(SQLUDF_MSGTX, 70, "An unrecognized bit was set in the "
            "options argument");
        strcpy(SQLUDF_STATE, "38901");
        break;
      case PCRE_ERROR_NOMEMORY:
        snprintf(SQLUDF_MSGTX, 70, "Not enough memory available.");
        strcpy(SQLUDF_STATE, "38902");
        break;
      default:
        if (rc < 0) {
            snprintf(SQLUDF_MSGTX, 70, "A regexp match error "
                "occured: %d", rc);
            strcpy(SQLUDF_STATE, "38903");
        }
        else {
            *match = 1;
        }
        break;
    }

    // cleanup
    (*pcre_free)(re);
    return;
}

使用例

次の照会は、コメント・テキストを含む表strTableからすべてのストリングの検出を試みます。コメントは「#」で始まるので、パターンは「#」記号の後に空ではないテキストが続きます。

SELECT c1, str
FROM   strTable
WHERE  regex1('#\s*\w+', str) = 1;

結果には、c1 = 6を満たす行だけが含まれます。

C1          STR
----------- -------------------------
          6 # comment;

  1 record(s) selected.

2番目の例では、代入形式、つまり「text = text」のストリングを検出します。さらに絞り込むために、右辺に数値を持つ代入だけを検出します。16進表記は、有効な数値として扱われます。

SELECT c1, str
FROM   strTable
WHERE  regex1('\w+\s*=\s*(\d+|0x\d\d)', str) = 1;

c1が2または5の2行を除き、数値の代入を含む行は他にないため、結果には現れません。

C1          STR
----------- -------------------------
          2 variable = 1234;
          5 myVar = 0x00;

  2 record(s) selected.

パフォーマンスの改善

上記の関数は予想どおりに機能しますが、さらにパフォーマンスを向上させるために改善できます。関数内部での実行の完了が速いほど、DB2はSQLステートメント全体を速く処理できることに注意してください。

SQLは行の集合を処理するように設計されます。つまり、パターンに一致する行は通常は複数行存在することを意味します。パターン自体、ほとんどの場合ではSQLステートメント全体に一定しており、行ごとに変わることはありません。リスト5のCコードは、指定のパターンを内部表記に変換する関数pcre_compile()への呼び出しを、すべての行について1行ずつ実施されることを示しています。

DB2は、いわゆる「スクラッチパッド」を使用することにより、UDF呼び出しからUDF呼び出しへ情報を渡す方式を備えています。さらに、特定の呼び出し「タイプ」、すなわち、UDFを構成する最初の呼び出しか通常の呼び出し、あるいは最後の呼び出しを識別できます。スクラッチパッドと呼び出しタイプを使うと、パターンを1回コンパイルすれば、そのコンパイル済みパターンの内部表記を、UDFに対して行われる後続のすべての呼び出しに再利用できます。処理中に割り振られたリソースは、最後の呼び出しで解放できます。

CREATE FUNCTIONステートメントをリスト6で示すように変更し、DB2に対して 、スクラッチパッドと呼び出しタイプを外部Cコードに提供するように指示します。

リスト6. CREATE FUNCTIONステートメントにスクラッチパッドと呼び出しタイプを追加する

CREATE FUNCTION regex2(pattern VARCHAR(2048), string CLOB(10M))
    RETURNS INTEGER
    SPECIFIC regexPerf
    EXTERNAL NAME 'regexUdf!regexpPerf'
    LANGUAGE C
    PARAMETER STYLE DB2SQL
    DETERMINISTIC
    NOT FENCED
    RETURNS NULL ON NULL INPUT
    NO SQL
    NO EXTERNAL ACTION
    SCRATCHPAD 50
    FINAL CALL
    ALLOW PARALLEL;

関数内部のロジックの調整が必要であるため、UDFエントリー・ポイントはまったく違うように見えます。パラメーターに対する唯一の変更は、リスト7に示すように、SQLUDF_TRAIL_ARGSの代わりにSQLUDF_TRAIL_ARGS_ALLを使用することです。

リスト7. regex2のC UDFエントリー・ポイント

#include <pcre.h>
#include <sqludf.h>

// data structure mapped on the scratchpad for easier use and access
// to the objects
// the size of the scratchpad defined in the CREATE FUNCTION statement
// must be at least as large as sizeof(scratchPadMapping)
struct scratchPadMapping {
    pcre *re;
    pcre_extra *extra;
    const char *error;
    int errOffset;
};

void regexpPerf(
    // input parameters
    SQLUDF_VARCHAR *pattern,      SQLUDF_CLOB *str,
    // output
    SQLUDF_INTEGER *match,
    // null indicators
    SQLUDF_NULLIND *pattern_ind,  SQLUDF_NULLIND *str_ind,
    SQLUDF_NULLIND *match_ind,
    SQLUDF_TRAIL_ARGS_ALL) // SQLUDF_SCRAT & SQLUDF_CALLT
{
    int rc = 0;
    struct scratchPadMapping *scratch = NULL;

    // map the buffer of the scratchpad and assume successful return
    scratch = (struct scratchPadMapping *)SQLUDF_SCRAT->data;
    *match_ind = 0;

    switch (SQLUDF_CALLT) {
      case SQLUDF_FIRST_CALL:
        // initialize data on the scratchpad
        scratch->re = NULL;
        scratch->extra = NULL;
        scratch->error = NULL;
        scratch->errOffset = 0;

        // compile the pattern (only in the FIRST call
        scratch->re = pcre_compile(pattern, 0 /* default options */,
            &scratch->error, &scratch->errOffset, NULL);
        if (scratch->re == NULL) {
            snprintf(SQLUDF_MSGTX, 70, "Regexp compilation failed at "
                "offset %d: %s\n", scratch->errOffset, scratch->error);
            strcpy(SQLUDF_STATE, "38900");
            rc = -1;
            break;
        }

        // further analyze the pattern (might return NULL)
        scratch->extra = pcre_study(scratch->re,
            0 /* default options */, &scratch->error);

        /* fall through to NORMAL call because DB2 expects a result
           already in the FIRST call */

      case SQLUDF_NORMAL_CALL:
        // match the current string
        rc = pcre_exec(scratch->re, scratch->extra, str->data,
              str->length, 0, 0 /* default options */, NULL, 0);
        switch (rc) {
          case PCRE_ERROR_NOMATCH:
            *match = 0;
            rc = 0;
            break;
          case PCRE_ERROR_BADOPTION:
            snprintf(SQLUDF_MSGTX, 70, "An unrecognized bit was set "
                "in the options argument");
            strcpy(SQLUDF_STATE, "38901");
            rc = -1;
            break;
          case PCRE_ERROR_NOMEMORY:
            snprintf(SQLUDF_MSGTX, 70, "Not enough memory available.");
            strcpy(SQLUDF_STATE, "38902");
            rc = -1;
            break;
          default:
            if (rc < 0) {
                snprintf(SQLUDF_MSGTX, 70, "A regexp match error "
                    "occured: %d", rc);
                strcpy(SQLUDF_STATE, "38903");
                rc = -1;
            }
            else {
                *match = 1;
                rc = 0;
            }
            break;
        }
        break;
      }

      // cleanup in FINAL call, or if we encountered an error in
      // the FIRST call (DB2 will make a FINAL call if we encounter
      // an error in any NORMAL call)
      if (SQLUDF_CALLT == SQLUDF_FINAL_CALL ||
          (SQLUDF_CALLT == SQLUDF_FIRST_CALL && rc < 0)) {
          (*pcre_free)(scratch->re);
          (*pcre_free)(scratch->extra);
      }
      return;
}

関数のパフォーマンスをさらに改善するために、パターン・マッチング・エンジンで提供される関数pcre_study() の呼び出しを追加しました。この関数はパターンをさらに分析し、追加情報を別の構造に格納します。この追加情報は、処理をスピードアップするために実際のマッチング中に使用されます。非常に簡単なパターンと約4000行の表を使用した場合、実行時間は5%改善されました。もちろん、パターンが複雑になるほど、違いは著しくなります。

実装では行ごとの処理の最中にパターンは変わらないと想定すると述べました。もちろん、パターンが変わる場合は、若干調整して再びパターンをコンパイルすることもできます。それには、現在の (コンパイル済み) パターンを追跡し、各呼び出しでそのパターンと提供されたパターンを比較する必要があります。現在のパターンは、スクラッチパッド上でも保持できます。しかし、別のバッファーにコピーされる必要があり、ポインター・パターンで直接リフレッシュすることはできません。それは、このポインターまたはポインターが指すデータは変化したり無効になることがあるためです。それぞれのコード変更は、読者の練習課題として残しておくことにします。


マッチング・サブストリングの戻し

ほとんどのパターン・マッチング・エンジンは、指定パターンまたはその一部と一致したサブストリングを返す手段を備えています。SQLでこの機能を使用したい場合、マッチング関数の実装に別の方式を使用する必要があります。1つのストリングに複数のマッチング・サブストリングが含まれることもあります。たとえば、「abc = 123;」や「def = 'some text';」などのストリングを解析する場合、ユーザーは等号で分けられた2つのサブストリングを取り出したいかもしれません。その場合、「\w+\s*=\s*(\d+|'[\w\s]*');」というパターンを使用して、ストリングを問い合わせる構文ルールを表現できます。Perl互換の正規表現を用いると、等号の前後のサブストリングをキャプチャーできます。そのためには、キャプチャーされるサブストリングを括弧 ( ) で囲む必要があります。2番目のサブストリングはすでにそのように記述されていますが、最初のサブストリングはそうではありません。このために最終的に使用するパターンは、次のようになります。

(\w+)\s*=\s*(\d+|'[\w\s]*');

このパターンがストリング「abc = 123;」や「def = 'some text';」に適用されると、「abc」または「def」はそれぞれ「(\w+)」と一致し、スペースと等号が「\s*=\s*」で検出され、残りのサブストリングは論理和のパターン「(\d+|'[\w\s*]')」で網羅されます。この論理和パターンでは、最初のオプションは最低でも1桁以上で構成される任意の数「\d+」と一致し、2番目のオプションは、文字とスペースで構成され、単一引用符で囲まれた任意のストリング「'[\w\s]*'」を解析します。

DB2上でこれを実施する際の要件は、UDFに対する1つの呼び出しについて、複数の結果を返すことであると説明できます。言い換えると、1つのストリングに関して、パターンに一致する複数のサブストリングを返すということです。DB2の表関数はこれにうってつけの機能です。

表UDFの実装

前述のとおり、データベースで関数を作成する必要があります。それには、リスト8のステートメントが使用されます。

リスト8. regex3という表UDFの登録

CREATE FUNCTION regex3(pattern VARCHAR(2048), string CLOB(10M))
    RETURNS TABLE ( position INTEGER, substring VARCHAR(2048) )
    SPECIFIC regexSubstr
    EXTERNAL NAME 'regexUdf!regexpSubstr'
    LANGUAGE C
    PARAMETER STYLE DB2SQL
    DETERMINISTIC
    NOT FENCED
    RETURNS NULL ON NULL INPUT
    NO SQL
    NO EXTERNAL ACTION
    SCRATCHPAD 50
    NO FINAL CALL
    DISALLOW PARALLEL;

関数の実際のロジックを実装するCコードは、リスト7のコードと非常によく似ていますが、リスト9に示すように、表関数のために従うべき特殊な要件に合わせて調整されています。

リスト9. 表関数で使用されるregex3関数の実装

#include <pcre.h>
#include <sqludf.h>
#include <sqlstate.h>
#include <string.h>

struct scratchPadMapping {
    pcre *re;
    pcre_extra *extra;
    const char *error;
    int errOffset;
    int numSubstr;
    int *substr;
    int currentSubstr;
};

void regexpSubstr(
    // input parameters
    SQLUDF_VARCHAR *pattern,      SQLUDF_CLOB *str,
    // output
    SQLUDF_INTEGER *pos,          SQLUDF_VARCHAR *substr,
    // null indicators
    SQLUDF_NULLIND *pattern_ind,  SQLUDF_NULLIND *str_ind,
    SQLUDF_NULLIND *pos_ind,      SQLUDF_NULLIND *substr_ind,
    SQLUDF_TRAIL_ARGS_ALL) // SQLUDF_SCRAT & SQLUDF_CALLT
{
    int rc = 0;
    size_t length = 0;
    struct scratchPadMapping *scratch = NULL;

    // map the buffer of the scratchpad and assume NULL return
    scratch = (struct scratchPadMapping *)SQLUDF_SCRAT->data;
    *pos_ind = 0;
    *substr_ind = 0;

    switch (SQLUDF_CALLT) {
      case SQLUDF_TF_OPEN:
        // initialize data on the scratchpad
        scratch->re = NULL;
        scratch->extra = NULL;
        scratch->error = NULL;
        scratch->errOffset = 0;
        scratch->numSubstr = 0;
        scratch->substr = NULL;
        scratch->currentSubstr = 1; // skip the complete match

        // compile the pattern (only in the FIRST call
        scratch->re = pcre_compile(pattern, 0 /* default options */,
            &scratch->error, &scratch->errOffset, NULL);
        if (scratch->re == NULL) {
            snprintf(SQLUDF_MSGTX, 70, "Regexp compilation failed at "
                "offset %d: %s\n", scratch->errOffset, scratch->error);
            strcpy(SQLUDF_STATE, "38900");
            rc = -1;
            break;
        }
        // further analyze the pattern (might return NULL)
        scratch->extra = pcre_study(scratch->re,
            0 /* default options */, &scratch->error);
        // determine the number of capturing subpatterns
        rc = pcre_fullinfo(scratch->re, scratch->extra,
            PCRE_INFO_CAPTURECOUNT, &scratch->numSubstr);
        if (rc) {
            snprintf(SQLUDF_MSGTX, 70, "Could not retrieve info "
                "on pattern. (rc = %d)", rc);
            strcpy(SQLUDF_STATE, "38901");
            rc = -1;
            break;
        }
        // allocate memory for the substring indices
        {
            int size = (scratch->numSubstr+1)*3;
            scratch->substr = (int *)malloc(size * sizeof(int));
            if (!scratch->substr) {
                snprintf(SQLUDF_MSGTX, 70, "Could allocate memory for "
                    "substring indices.");
                strcpy(SQLUDF_STATE, "38902");
                rc = -1;
                break;
            }
            memset(scratch->substr, 0, size * sizeof(int));
            // match the current string
            rc = pcre_exec(scratch->re, scratch->extra, str->data,
            str->length, 0, 0 /* default options */,
                scratch->substr, size);
        }
        switch (rc) {
          case PCRE_ERROR_BADOPTION:
            snprintf(SQLUDF_MSGTX, 70, "An unrecognized bit was set "
                "in the options argument");
            strcpy(SQLUDF_STATE, "38903");
            rc = -1;
            break;
          case PCRE_ERROR_NOMEMORY:
            snprintf(SQLUDF_MSGTX, 70, "Not enough memory available.");
            strcpy(SQLUDF_STATE, "38904");
            rc = -1;
            break;
          case PCRE_ERROR_NOMATCH:
            scratch->currentSubstr = scratch->numSubstr + 1;
            rc = 0;
            break;
          default:
            if (rc < 0) {
                snprintf(SQLUDF_MSGTX, 70, "A regexp match error "
                    "occured: %d", rc);
                strcpy(SQLUDF_STATE, "38905");
                rc = -1;
                break;
            }
        }
        break;

      case SQLUDF_TF_FETCH:
        // skip capturing substrings without a match
        while (scratch->currentSubstr <= scratch->numSubstr &&
            (scratch->substr[2*scratch->currentSubstr] < 0 ||
                scratch->substr[2*scratch->currentSubstr+1] < 0)) {
            scratch->currentSubstr++;
        }
        // no more data to be returned
        if (scratch->currentSubstr > scratch->numSubstr) {
            strcpy(SQLUDF_STATE, SQL_NODATA_EXCEPTION);
            rc = 0;
            break;
        }
        // get the current substring
        *pos = scratch->currentSubstr;
        length = scratch->substr[2*scratch->currentSubstr+1] -
            scratch->substr[2*scratch->currentSubstr];
        strncpy(substr, str->data + scratch->substr[2*scratch->currentSubstr],
            length);
        substr[length] = '\0';
        scratch->currentSubstr++;
    }

    // cleanup in CLOSE call, or if we encountered an error in
    // the OPEN call (DB2 will make a CLOSE call if we encounter
    // an error in any FETCH call)
    if (SQLUDF_CALLT == SQLUDF_TF_CLOSE ||
        (SQLUDF_CALLT == SQLUDF_TF_OPEN && rc < 0)) {
        (*pcre_free)(scratch->re);
        (*pcre_free)(scratch->extra);
        free(scratch->substr);
    }
    return;
}

表関数をFINAL CALLで定義することにより、基本マッチング関数に対して実施したのと同様に、パフォーマンスを最適化することもできます。Cコードは、SQLUDF_TF_FIRSTおよびSQLUDF_TF_FINAL呼び出しを処理するように変更される必要があります。

使用例

表関数は、次のようにSELECTステートメントで使用できます。

SELECT c1, str, num, substr
FROM   strTable,
       TABLE ( regex3('(\w+)\s*=\s*(\d+|''[\w\s]*'');', str) ) AS sub(num, substr)

結果には、一致したパターンのあるストリングのみが含まれます。各ストリングについて、最初にキャプチャーされたサブストリングと2番目にキャプチャーされたサブストリングが別々の行に表示されます。

C1          2                              NUM         4
----------- ------------------------------ ----------- -----------------------
          2 variable = 1234;                         1 variable
          2 variable = 1234;                         2 1234
          3 var2 = 'string variable';                1 var2
          3 var2 = 'string variable';                2 'string variable'

  4 record(s) selected.

次の照会は、結果セットで、別々の行を使用する代わりに、別々の列に両サブストリングのペアを返します。したがって、SQLステートメントでのストリングとそのサブストリングのその後の処理が簡単になります。照会は (WITHキーワードで示される) 共通表式を使用して、各ストリングの評価が、中間表s1およびs2に必要な副選択ごとに実施されるのではなく、全体の照会で1回だけ実施されることを保証します。

WITH substrings(c, num, substr) AS
   ( SELECT c1, num, substr
     FROM   strTable,
            TABLE ( regex3('(\w+)\s*=\s*(\d+|''[\w\s]*'');', str) )
               AS sub(num, substr) )
SELECT t.c1, s1.substr AS variable, s2.substr AS value
FROM   strTable AS t JOIN substrings AS s1 ON
          ( t.c1 = s1.c ) JOIN
       substrings AS s2 ON
          ( t.c1 = s2.c )
WHERE  s1.num = 1 AND s2.num = 2

前述の照会にあるのと同じパターンが使用されました。したがって、結果は上の表から導けますが、今回は要求されたおり、変数と値のペアがそれぞれ1行に表示されます。

C1          VARIABLE                       VALUE
----------- ------------------------------ --------------------
          2 variable                       1234
          3 var2                           'string variable'

  2 record(s) selected.

まとめ

この記事では、正規表現と、DB2で提供されるストリング比較およびマッチング機能を簡単に紹介しました。また、正規表現のパワーが有用である理由についても説明しました。DB2では、正規表現を実装する形式には2通りあり、いずれもUDFを使用します。最初の方式では、基本マッチングは指定パターンとストリングを比較することにより実施されます。2番目の方式では、SQLステートメントでさらに処理できるように、キャプチャーされたサブストリングを正規表現から抽出してそのサブストリングをDB2へ返す表関数を実装します。また、パフォーマンス改善のヒントも示しました。


特記事項

この文書には、サンプル・コードが含まれます。IBMは読者のみなさま (「ライセンス所有者」) に対して、このサンプル・コードを使用するための使用料無料の非独占的な使用権を許諾します。サンプル・コードは現存するままで提供され、保証およびプログラム・サービスは提供されません。IBMはこのサンプル・コードについて、商品性の保証、特定目的適合性の保証および法律上の瑕疵担保責任を含むすべての明示もしくは黙示の保証責任を負わないものとします。また、IBMは逸失利益を含むいかなる直接または間接の損害についても責任を負わないものとします。

参考文献

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Information Management
ArticleID=326365
ArticleTitle=SQLに正規表現マッチングのパワーを活用
publish-date=01232003