2010年4月22日木曜日

SQL-Server 2008 Expressで、正規表現を使ってマッチした文字列を抜き出す関数を作ってみる




今回の記事も、SQL-Server 2008 Expressの正規表現について書かせていただきたいと思います。
前回までは、正規表現パターンで条件を検索する関数SQLCLR_Regex()、正規表現パターンで文字列をリプレース(置換)するSQLCLR_RegexReplace()を作りました。

今回は、正規表現パターンで一体どの文字列がマッチしたのか?を実際に抜き出す関数を作ります。
これは、SQLCLR_RegexSelect()という名前にしてみます。

元々SQL-Serverに備わっているlikeもそうなんですが、条件がマッチしたのは良いけれど、実際にマッチした文字列をそのまま抜き出して利用したい・・・というところまでは出来ません。
今回の関数を使えば、それが可能になります

ただ、正規表現の書き方によっては、もちろん複数の文字列がマッチすることもあります。
どんな形で結果を得られるようにすれば良いか悩んだのですが、CSV形式の文字列で結果が得られるようにしてみました。

SQLCLRの登録などは過去の記事を参考になさって下さい。

■使用例■

--ゲーム名テーブル#T_BASEにおいて、
--ゲーム名に「冒険」か「伝説」か「物語」がつくものを抽出
--
--※条件はSQL_Regex()を使っています。
SELECT
GAME_NAME AS [元のゲーム名],
dbo.SQLCLR_RegexSelect(GAME_NAME,'.*(冒険|伝説|物語)') AS [マッチした文字列]
FROM #T_GAME
WHERE dbo.SQLCLR_Regex(GAME_NAME,'.*(冒険|伝説|物語)') = 1;


--ゲーム名に、アルファベットが付くゲームのアルファベットを抽出
SELECT
GAME_NAME AS [元のゲーム名],
dbo.SQLCLR_RegexSelect(GAME_NAME,'[a-zA-Z]') AS [マッチした文字列]
FROM #T_GAME
WHERE dbo.SQLCLR_Regex(GAME_NAME,'[a-zA-Z]') = 1;


■コード■
(インデントが消えています)
(無償版のVisual C# 2008でプログラムを書けます)
(不等号記号は全角で記述していますが実際は半角です)

//*******************************************************************************
// * ローカル関数
// * SQLCLR_RegexReplaceと共通で使います。
//*******************************************************************************
//------------------------------------
//正規表現で取得したパターンをList<>で返却
//p_value:検査したい文字列
//p_pat :正規表現パターン
//------------------------------------
private static List<string> GetMatchList(
string p_value,
string p_pat
)
{
//結果格納エリア
List<string> list_str_result = new List<string>();
//正規表現コア
Regex obj_rx;
//正規表現マッチコレクション
MatchCollection col_match;
//正規表現マッチ結果格納配列
string[] strArr_match;
//比較エリア
string str_comp_match = "";
//インデックス
Int32 i;

try
{
obj_rx = new Regex(p_pat);

col_match = obj_rx.Matches(p_value);

if (col_match.Count > 0)
{
//配列要素数決定
strArr_match = new string[col_match.Count];
//取得したマッチ文字列を配列に格納
//@@@@@@@@@@[LOOP-START]@@@@@@@@@@
for (i = 0; i < col_match.Count; i++)
{
strArr_match[i] = col_match[i].Value;
}
//@@@@@@@@@@[LOOP-END ]@@@@@@@@@@

//配列をソート
Array.Sort(strArr_match);

//@@@@@@@@@@[LOOP-START]@@@@@@@@@@
for (i = 0; i < strArr_match.Length; i++)
{
//マッチ文字列のうち、重複していない文字列だけを結果にセット
if (!(str_comp_match.Equals(strArr_match[i])))
{
list_str_result.Add(strArr_match[i]);
str_comp_match = strArr_match[i];
}
}
//@@@@@@@@@@[LOOP-END ]@@@@@@@@@@
}

}
catch (System.Exception obj_err)
{
throw obj_err;
}

return list_str_result;
}


//*******************************************************************************
//* SQL関数
//*******************************************************************************
//------------------------------------
//正規表現による文字列抜き出し
//------------------------------------
//using System.Text.RegularExpressions;を記述して下さい。
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlString SQLCLR_RegexSelect(
SqlString p_value,
SqlString p_pat
)
{
//書式チェック用
Regex obj_rx = new Regex(@"^.*"".*$");
//マッチ結果取得エリア
List<string> list_str_value = new List<string>();
//結果格納エリア
string str_result = "";
//インデックス
Int32 i;

try
{
//SQL-ServerからNULLが来ない時に処理する。
if (!(p_value.Equals(SqlString.Null)))
{
//書式チェック
if(obj_rx.IsMatch((string)p_pat))
{
throw new System.Exception("ダブルクォーテーションが含まれた文字列は処理できません。");
}

//自作関数GetMatchListで正規表現にマッチした文字列をList<>で取得
list_str_value = GetMatchList((string)p_value, (string)p_pat);

//マッチした文字列分をCSV形式で組み立てる
//@@@@@@@@@@[LOOP-START]@@@@@@@@@@
for (i = 0; i < list_str_value.Count; i++)
{
str_result += "\"" + list_str_value[i] + "\"" + ",";
}
//@@@@@@@@@@[LOOP-END ]@@@@@@@@@@
str_result = str_result.Substring(0, str_result.Length - 1);
}
}
catch (System.Exception obj_err)
{ //例外が発生したらSQL-Serverに投げつけて強制終了させる。
throw obj_err;
}

return (SqlString)str_result;
}


■SQL-Server関数インターフェース■
(ご自分の環境に読み替えお願いいたします。)

-- ----------------------------------------------
-- 正規表現による文字列抜き出し
-- (パラメータ説明)
-- @p_value:判定したい文字列
-- @p_pat:正規表現パターン
-- ----------------------------------------------
create function SQLCLR_RegexSelect(
@p_value nvarchar(max),
@p_pat nvarchar(max)
)
RETURNS nvarchar(max)
AS EXTERNAL NAME ASSEMBLY_SQLCLR.CLS_SQLCLR.SQLCLR_RegexSelect;
GO


それにつけても、C#言語の応用範囲は本当に広いですよね・・・。