フォルダ パスを解析してテーブルを返す関数を作成する方法は?
区切り文字はバックスラッシュになります\
。入力はフォルダー パスになります FolderA\FolderB
。
出力は、フォルダーが順番に並んだテーブルになります。
FolderName: FolderA FolderB
Level: 0 1
フォルダ パスを解析してテーブルを返す関数を作成する方法は?
区切り文字はバックスラッシュになります\
。入力はフォルダー パスになります FolderA\FolderB
。
出力は、フォルダーが順番に並んだテーブルになります。
FolderName: FolderA FolderB
Level: 0 1
この問題は、文字列分割の問題に一般化できます。T-SQL での文字列操作は可能ですが、複雑な式の構文が扱いにくいため、理解するのは困難です。
Itzik Ben-Gan は、 sqlservercodeブログに行ったインタビューで、この問題を解決するために必要なすべてのトリックを提供しています。
Itzik は、文字列を分割するためのインライン テーブル値関数を提供します。これをいくつか変更すると、問題が解決します。
CREATE FUNCTION dbo.fn_split(@arr AS VARCHAR(MAX)) RETURNS TABLE
AS
RETURN
SELECT
n - LEN(REPLACE(LEFT(@arr, n), ',', '')) + 1 AS pos,
SUBSTRING(@arr, n,
CHARINDEX(',', @arr + ',', n) - n) AS element
FROM dbo.Nums
WHERE n <= LEN(@arr) AND SUBSTRING(',' + @arr, n, 1) = ',';
この関数を使用すると、要素のコンマ区切り文字列を位置インデックス付きのテーブルに分割できます。次のようなクエリ:
SELECT * FROM dbo.fn_split('10248,10249,10250');
次のような結果セットが生成されます。
pos element
---- --------
1 10248
2 10249
3 10250
文字列分割関数は、補助数値テーブルに依存しています。あらゆる種類の問題を解決するのに役立つため、データベースにこれらのいずれかが既にある場合があります。
持っていない場合は、Itzik が効率性のために推奨する別のインライン テーブル値関数を適応させることができます。
CREATE FUNCTION dbo.fn_nums(@n AS BIGINT) RETURNS TABLE
AS
RETURN
WITH
L0 AS(SELECT 1 AS c UNION ALL SELECT 1),
L1 AS(SELECT 1 AS c FROM L0 AS A, L0 AS B),
L2 AS(SELECT 1 AS c FROM L1 AS A, L1 AS B),
L3 AS(SELECT 1 AS c FROM L2 AS A, L2 AS B),
L4 AS(SELECT 1 AS c FROM L3 AS A, L3 AS B),
L5 AS(SELECT 1 AS c FROM L4 AS A, L4 AS B),
Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY c) AS n FROM L5)
SELECT n FROM Nums
WHERE n <= @n;
この関数を使用すると、指定した行数のテーブルを生成できます。次のようなクエリ:
SELECT * FROM dbo.fn_nums(10);
次のような結果セットが生成されます。
n
---
1
2
3
4
5
6
7
8
9
10
文字列スプリッターと行ジェネレーターを組み合わせることで、関数を自己完結型にすることができます。つまり、データベース内の他のオブジェクトから独立して機能します。
区切り文字を指定する追加のパラメーターを追加することで、コンマだけでなく任意の文字で区切られた文字列を分割するため、文字列スプリッターを汎用にすることができます。
次のように、Itzik の文字列のスプリッターを変更したバージョンに置き換えることができます。
CREATE FUNCTION dbo.fn_split(@arr AS VARCHAR(MAX), @delim AS CHAR(1)) RETURNS TABLE
AS
RETURN
WITH
L0 AS(SELECT 1 AS c UNION ALL SELECT 1),
L1 AS(SELECT 1 AS c FROM L0 AS A, L0 AS B),
L2 AS(SELECT 1 AS c FROM L1 AS A, L1 AS B),
L3 AS(SELECT 1 AS c FROM L2 AS A, L2 AS B),
L4 AS(SELECT 1 AS c FROM L3 AS A, L3 AS B),
L5 AS(SELECT 1 AS c FROM L4 AS A, L4 AS B),
Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY c) AS n FROM L5)
SELECT
n - LEN(REPLACE(LEFT(@arr, n), @delim, '')) + 1 AS pos,
SUBSTRING(@arr, n,
CHARINDEX(@delim, @arr + @delim, n) - n) AS element
FROM Nums
WHERE n <= LEN(@arr) AND SUBSTRING(@delim + @arr, n, 1) = @delim;
以前と同じパラメーターを渡し、区切り文字としてコンマを渡すことで、元の関数の出力を複製できます。
SELECT * FROM dbo.fn_split('10248,10249,10250', ',');
次のような結果セットが生成されます。
POS ELEMENT
1 10248
2 10249
3 10250
区切られた要素の文字列のテーブルを生成できる関数ができたので、問題の解決策が得られました。
一般的な自己完結型の文字列スプリッターを配置すると、次のクエリを使用できます。
SELECT
Element AS FolderName,
Pos - 1 AS Level
FROM dbo.fn_split('FolderA\FolderB', '\');
次のような結果セットが生成されます。
FOLDERNAME LEVEL
FolderA 0
FolderB 1
SQL Server は、文字列操作と ROW_NUMBER 関数によって生成されるシーケンスなどに 1 から始まるインデックスを使用するため、一般的な文字列スプリッターがこの規則に従う必要があるのは理にかなっています。
パスの最初のフォルダーをレベル 0 にする必要があるため、クエリは要素の位置から 1 を引いて、0 から始まるインデックスを取得します。
SQL Fiddleでこのソリューションを試すことができます。