將正則表達式匹配的強大功能帶給 SQL

簡介

在數據庫中查找文本數據是應用程序中很常見的情形。有幾種基於全文本的產品可用於 IBM® DB2® Universal Database™,包括 DB2 Text Information Extender 和 DB2 Net Search Extender。但是,DB2 Extenders® 不包括執行 正則表達式匹配的功能,該功能用來查找和替換字符串中的模式。本文描述瞭如何實現一個用戶定義函數(UDF),該函數將普遍可用的正則表達式匹配庫與 DB2 集成在一起。我們通過示例使用了 pcre 庫(Perl 兼容的正則表達式),但也可以用任何其它庫替換它。

本文中所提供的描述適用於 V7 和 V8 版本的 DB2 通用數據庫的 Linux 版、UNIX® 版和 Windows® 版。

背景知識

本節描述了正則表達式是什麼以及它們爲什麼有用。

正則表達式是什麼?

正則表達式用於查找和替換字符串中的模式。正則表達式是用某種語法定義的,正則表達式引擎採用這種語法並將它與字符串進行比較。引擎返回字符串是否與語法匹配的指示;也即,該字符串是否包含能夠從該語法派生的子串。此外,引擎還能夠返回匹配的子串。術語“模式(pattern)”用來表示語法。

最基本的模式僅由單個字母組成。當與該模式進行比較時,包含這個字母的字符串就是一個“匹配”。例如,如果模式是“a”,則字符串“abcd”就是一個匹配,而字符串“xyz”則不是。正則表達式的強大功能來自於預定義的運算符(也稱爲元字符),它們可以用很小的空間來表示模式。根據“方言”和受支持的功能,可以使用不同的元字符。通常,其中的一些可用字符如下:

1

2

3

4

5

6

| — 二中擇一

[ ] — 分組

* — 多次出現(也匹配零次出現)

+ — 多次出現(至少一次)

? — 隨意的出現次數

\\\\ — 反斜槓

不同的系統實現了常用正則表達式的各種擴展。編程語言 Perl 中使用的正則表達式支持進一步的縮寫。本文中所用的庫實現了這些擴展。下面摘錄了其中部分可以在 Perl 正則表達式語言中使用的縮寫:

1

2

3

\\s — 任意空白字符

\\w — 任意字母數字字符

\\d — 任意數字字符

另一個更高級的示例是模式“[A-Z]* = ([0-9]|0x00);”。與這個模式相匹配的字符串包含這樣的子串:它由幾個大寫字母、後面跟上一個空格、一個等號、另一個空格,然後是一個數字或字符串“0x00”組成。該子串的最後一個字符必須是分號。使用 Perl,這個模式可以表示爲“\\w* = (\\d|0x00);”。“NM = 0x00;”和“X = 7;”是兩個可以與該模式匹配的字符串。但字符串“Z = 123;”不能匹配,因爲 123 是由三個數字所組成的。

DB2 中的字符串匹配

除了 Extender 以外,DB2 還允許幾種用於文本比較的函數和運算符。但那些函數和運算符要麼在用於模式匹配的功能方面有限制,要麼就是會給可能使用它們的查詢帶來複雜性。這裏簡要地摘錄幾個可用的功能:

= 或 <> 謂詞:逐字符地比較兩個字符串是否相等。 
LIKE 謂詞:使用通配符的基本模式匹配。 
LOCATE 函數:在字符串中查找子串。

儘管也可以用 SQL 運算符表示模式“[A-Z]* = ([0-9]|0x00);”,但那樣會很麻煩。例如,下列 SELECT 語句的 WHERE 子句中所使用的謂詞會匹配字符串“str”中等號之後的部分,如 清單 1所示:

清單 1. 使用 LIKE 匹配模式

1

2

3

4

5

6

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]*”子模式的謂詞的複雜度,這可以使用對整個字符串進行迭代並進行逐字符比較的函數來完成,但您會發現使用內置功能既冗長又複雜。

示例方案

讓我們定義下列清單( 清單 2)並插入幾行:

清單 2. 創建我們的樣本表

1

2

3

4

5

6

7

8

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' );

這個 清單及其數據被用於下面的所有示例。

1

2

3

4

5

6

7

8

9

10

11

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 來簡化模式匹配

1

2

3

SELECT str

FROM   strTable

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

在本示例中,使用帶有 Perl 擴展的正則表達式來匹配完整的模式,而不僅僅是 清單 1中給出的 LIKE 謂詞所對應的部分模式。正如您所看到的,使用函數來爲該模式編寫謂詞比用 LIKE 謂詞表示同樣的語義要容易得多。

實現 UDF

在我的示例實現中,我選擇了現有的名爲 PCRE(Perl 兼容的正則表達式,Perl-compatible regular expression)的模式匹配引擎。該引擎提供了用來處理模式和執行匹配的 C API。該引擎和查詢中所用的 SQL 語言之間“缺失的部分”是 UDF。該 UDF 由兩部分組成:

  • 在數據庫中創建(或註冊)該函數的 CREATE FUNCTION 語句。
  • 該函數的主體,它實現了用於正則表達式匹配引擎的 C API 調用的封裝器

清單 4顯示了用於創建該函數的 SQL 語句。

清單 4. 註冊 regex1 函數

1

2

3

4

5

6

7

8

9

10

11

12

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 Reference以獲取所有子句的詳細含義。可以修改參數的長度以適應您的需求。我在此處展示某些值並沒有任何推薦使用它們的用意。

第二部分由一小段 C 代碼組成,它實現了 UDF 入口點。在查詢執行期間,DB2 爲每個要與模式匹配的行調用這個入口點。 清單 5 中的示例列出了該代碼的清單。有關 pcre_* 函數和宏的描述,請參考 PCRE 庫的文檔。有關 C 代碼的編譯和共享庫的構建,請參考 DB2 Application Development Guide

清單 5. 實現 rege1x UDF 入口點的 C 代碼

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

#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 中找出包含註釋文本的所有字符串。註釋以“#”開頭,所以模式是“#”後跟非空文本。

1

2

3

SELECT c1, str

FROM   strTable

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

結果只包含 c1 = 6 的行。

1

2

3

4

C1          STR

----------- -------------------------

          6 # comment;

  1 record(s) selected.

在第二個示例中,我們試圖找到這種賦值形式的字符串;即“text = text”。爲了進一步縮小範圍,我們只查找那些右端爲數值的賦值。將十六進制表示法作爲有效數值對待。

1

2

3

SELECT c1, str

FROM   strTable

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

除了 c1 爲 2 或 5 的兩行以外,其它行都不包含數值的賦值,因此不會出現在結果中:

1

2

3

4

5

C1          STR

----------- -------------------------

          2 variable = 1234;

          5 myVar = 0x00;

  2 record(s) selected.

改進性能

儘管上面的函數按照預期的方式工作,但還可以改進它以獲得更佳的性能。注:函數內部的執行完成得越快,DB2 處理整個 SQL 語句的速度也就越快。

SQL 旨在處理多組行,這意味着通常會針對一個模式匹配多個行。在大多數情況下,模式本身對於整個 SQL 語句都是不變的;即,它不會隨行的更改而更改。 清單 5 中的 C 代碼展示了對每一行都調用函數pcre_compile() ,該函數將給定模式轉換成內部表示法。

DB2 通過使用所謂的“高速暫存(scratchpad)”提供了在 UDF 調用之間傳遞信息的機制。此外,您可以標識特定調用“類型”;即它是對該 UDF 的第一次調用、普通調用還是最後一次(最終)調用。使用高速暫存和調用類型,有可能只對模式編譯一次,然後將該已編譯模式的內部表示法重用於對該 UDF 的所有後續調用。在最後一次調用時,釋放在處理期間分配的資源。

如 清單 6所示,對 CREATE FUNCTION 語句進行修改,告訴 DB2 向外部 C 代碼提供高速暫存和調用類型:

清單 6. 將高速暫存和調用類型添加到 CREATE FUNCTION 語句

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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 入口點看起來很不一樣,因爲必須改寫函數內部的邏輯。參數方面唯一的更改是使用SQLUDF_TRAIL_ARGS_ALL 代替了 SQLUDF_TRAIL_ARGS ,如 清單 7所示。

清單 7. regex2 的 C UDF 入口點

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

#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 中使用這種能力,則必須使用不同的方法來實現匹配函數。給定的字符串可能包含不止一個匹配的子串。例如,當解析類似“abc = 123;”或“def = 'some text';”這樣的字符串時,用戶可能會希望檢索由等號分隔的兩個子串。您可以使用模式“\\w+\\s*=\\s*(\\d+|'[\\w\\s]*');”來表示適用於該字符串的語法規則。Perl 兼容的正則表達式允許您捕獲等號兩邊的子串。最後,必須將要捕獲的子串用括號括起來。我已經用該方式編寫了第二個子串,但第一個子串不是這樣編寫的。用於該用途的最終模式是這樣的:

1

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

當把這個模式應用於字符串“abc= 123;”或“def = 'some text';”時,“abc”或“def”分別與“(\\w+)”匹配,空格和等號是通過“\\s*=\\s*”查找的,並用另外的“(\\d+|'[\\w\\s*]')”涵蓋了餘下的子串。在“(\\d+|'[\\w\\s*]')”中,第一個選項與任何至少由一個數字“\\d+”組成的數匹配,而第二個選項解析任何由字母和空格組成的由單引號括起的字符串“'[\\w\\s]*'”。

在 DB2 中做到這一點的需求可以描述成:爲一次 UDF 調用返回多個結果。換句話說,就是返回針對模式進行匹配的單個字符串的多個子串。DB2 的表函數是完成這一任務的完美工具。

實現表 UDF

和以前一樣,必須在數據庫中創建該函數。 清單 8中的下列語句正是用於這一任務的:

清單 8. 註冊名爲 regex3 的表 UDF

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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 函數

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

#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 語句中使用:

1

2

3

SELECT c1, str, num, substr

FROM   strTable,

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

結果只包括擁有匹配模式的字符串。對於每個字符串,都在單獨的行中顯示所捕獲的第一個和第二個子串。

1

2

3

4

5

6

7

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

2

3

4

5

6

7

8

9

10

11

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

這裏使用了與前面查詢中相同的模式。因此,可以從上面的表中派生出結果,但這一次,根據請求,這些 variable-value 對每個都位於單獨的行中。

1

2

3

4

5

C1          VARIABLE                       VALUE

----------- ------------------------------ --------------------

          2 variable                       1234

          3 var2                           'string variable'

  2 record(s) selected.

結束語

在本文中,我相當簡略地介紹了正則表達式以及 DB2 中可用的字符串比較和匹配功能。我還描述了爲什麼正則表達式的強大功能是如此有用。UDF 可以用來以兩種形式在 DB2 中提供正則表達式。在第一種形式中,通過將字符串與給定模式進行比較來完成基本匹配。第二種形式是實現表函數,它從正則表達式抽取已捕獲的子串,並將這些子串返回給 DB2,以便用在 SQL 語句的進一步處理中。我還給出了一些關於如何改進性能的提示。

參考資料

免責聲明

本文包含了樣本代碼。IBM 授予您(“被許可方”)使用這個樣本代碼的非專有的、版權免費的許可證。然而,該樣本代碼是以“按現狀”的基礎提供的,沒有任何形式的(不論是明示的,還是默示的)保證,包括對適銷性、適用於特定用途或非侵權性的默示保證。IBM 及其許可方不對被許可方由於使用該軟件所導致的任何損失負責。任何情況下,無論損失是如何發生的,也不管責任條款怎樣,IBM 或其許可方都不對由使用該軟件或不能使用該軟件所引起的收入的減少、利潤的損失或數據的丟失,或者直接的、間接的、特殊的、由此產生的、附帶的損失或懲罰性的損失賠償負責,即使 IBM 已經被明確告知此類損害的可能性,也是如此。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章