Microsoft SQL Server 2008でテーブル値関数を記述して、データベースのカンマ区切りの列を取得し、各値の個別の行を出力しました。
例:「one、two、three、four」は、次の値を含む列が1つだけの新しいテーブルを返します。
one
two
three
four
このコードは皆さんにエラーが発生しやすいように見えますか?私がそれをテストするとき
SELECT * FROM utvf_Split('one,two,three,four',',')
それは永遠に実行され、何も返しません。 MSSQLサーバーには組み込みの分割関数がないので(なぜ、なぜ、なぜ!?)、Webで見つけた同様の関数はすべて、完全にゴミ箱であるか、単に私がやろうとしていることとは無関係であるため、これは特に失望します。 。
これが関数です:
USE *myDBname*
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[utvf_SPlit] (@String VARCHAR(MAX), @delimiter CHAR)
RETURNS @SplitValues TABLE
(
Asset_ID VARCHAR(MAX) NOT NULL
)
AS
BEGIN
DECLARE @FoundIndex INT
DECLARE @ReturnValue VARCHAR(MAX)
SET @FoundIndex = CHARINDEX(@delimiter, @String)
WHILE (@FoundIndex <> 0)
BEGIN
DECLARE @NextFoundIndex INT
SET @NextFoundIndex = CHARINDEX(@delimiter, @String, @FoundIndex+1)
SET @ReturnValue = SUBSTRING(@String, @FoundIndex,@NextFoundIndex-@FoundIndex)
SET @FoundIndex = CHARINDEX(@delimiter, @String)
INSERT @SplitValues (Asset_ID) VALUES (@ReturnValue)
END
RETURN
END
少し手直ししました...
DECLARE @FoundIndex INT
DECLARE @ReturnValue VARCHAR(MAX)
SET @FoundIndex = CHARINDEX(@delimiter, @String)
WHILE (@FoundIndex <> 0)
BEGIN
SET @ReturnValue = SUBSTRING(@String, 0, @FoundIndex)
INSERT @SplitValues (Asset_ID) VALUES (@ReturnValue)
SET @String = SUBSTRING(@String, @FoundIndex + 1, len(@String) - @FoundIndex)
SET @FoundIndex = CHARINDEX(@delimiter, @String)
END
INSERT @SplitValues (Asset_ID) VALUES (@String)
RETURN
ループではこれを行いません。より良い代替案があります。あなたが分割するとき、CLRであり、 Adam Machanicのアプローチが私がテストした中で最速 である場合は、群を抜いて最高です。
CLRを実装できない場合の次善のアプローチは、数値表です。
SET NOCOUNT ON;
DECLARE @UpperLimit INT = 1000000;
WITH n AS
(
SELECT
x = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
CROSS JOIN sys.all_objects AS s3
)
SELECT Number = x
INTO dbo.Numbers
FROM n
WHERE x BETWEEN 1 AND @UpperLimit
OPTION (MAXDOP 1); -- protecting from Paul White's observation
GO
CREATE UNIQUE CLUSTERED INDEX n ON dbo.Numbers(Number)
--WITH (DATA_COMPRESSION = PAGE);
GO
...これはこの機能を可能にします:
CREATE FUNCTION dbo.SplitStrings_Numbers
(
@List NVARCHAR(MAX),
@Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT Item = SUBSTRING(@List, Number,
CHARINDEX(@Delimiter, @List + @Delimiter, Number) - Number)
FROM dbo.Numbers
WHERE Number <= CONVERT(INT, LEN(@List))
AND SUBSTRING(@Delimiter + @List, Number, 1) = @Delimiter
);
GO
これらはすべて、特に複数ステートメントではなくインラインなので、when機能させるよりもパフォーマンスが優れていると思います。あなたの機能が機能していない理由を調査していません。その機能を機能させることは価値がないと思います。
しかし、すべてが言った...
SQL Server 2008を使用しているので、最初に分割する必要がある理由はありますか?これにはTVPを使用したいと思います。
CREATE TYPE dbo.strings AS TABLE
(
string NVARCHAR(4000)
);
これをストアドプロシージャのパラメーターとして受け入れ、TVFを使用する場合と同じようにコンテンツを使用できます。
CREATE PROCEDURE dbo.foo
@strings dbo.strings READONLY
AS
BEGIN
SET NOCOUNT ON;
SELECT Asset_ID = string FROM @strings;
-- SELECT Asset_ID FROM dbo.utvf_split(@other_param, ',');
END
また、TVPをC#などから直接DataTableとして渡すことができます。特にアプリでコンマ区切りの文字列を作成して、ストアドプロシージャがTVPを呼び出して再び分割できるようにする場合は特に、これは上記のどのソリューションよりも確実に優れています。 TVPの詳細については、 Erland Sommarskogの素晴らしい記事 を参照してください。
最近、文字列の分割に関するシリーズを書きました:
SQL Server 2016以降(またはAzure SQL Database)を使用している場合は、 新しいSTRING_SPLIT
function 、私がここでブログに書いた:
SQL Server 2016は STRING_SPLIT() 関数を導入しました。これには2つのパラメータがあります-切り刻まれる文字列とセパレータです。出力は、返される値ごとに1行です。
与えられた例について
SELECT * FROM string_split('one,two,three,four', ',');
戻ります
value
------------------
one
two
three
four
ジェフ・モデムのストリング・スプリッターが登場して以来、ずっと使っていて、とても気に入っています。
Tally OH!改善されたSQL 8K「CSVスプリッター」機能
CREATE FUNCTION [dbo].[DelimitedSplit8K] --===== Define I/O parameters (@pString VARCHAR(8000), @pDelimiter CHAR(1)) --WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE! RETURNS TABLE WITH SCHEMABINDING AS RETURN --===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000... -- enough to cover VARCHAR(8000) WITH E1(N) AS ( SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 ), --10E+1 or 10 rows E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front -- for both a performance gain and prevention of accidental "overruns" SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4 ), cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter) SELECT 1 UNION ALL SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter ), cteLen(N1,L1) AS(--==== Return start and length (for use in substring) SELECT s.N1, ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000) FROM cteStart s ) --===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found. SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1), Item = SUBSTRING(@pString, l.N1, l.L1) FROM cteLen l ;