文字列に数値が含まれていないかどうかを確認する関数を作成しようとしています(つまり、検索対象の数値が「6」で、文字列が「7 + 16 + 2」である場合)この文字列の「6」は数値「16」の一部であるため、falseを返す必要があります)
私は以下の関数を書きました(時間がかかりますが、リファクタリングする前に最初にテストしました)
テストの際に、見つかった番号の最初のインスタンスのみをロジックで実行するバグを発見しました。したがって、この関数を '6'で'16 + 7 + 9 + 6 'に対して実行すると、最初の' 6 'がより大きな数の一部であると判断され、処理が停止するため、falseが返されます。
私はこれを回避するために 'haystack'文字列を短くするループを実装する必要があると考えました(そのため、例'16 + 7 + 9 + 6 'を使用すると、関数は削除した後も' + 7 + 9 + 6 'をチェックし続けます最初の '6')ですが、時間をかけて複雑な関数をさらに複雑にする前に、同じ目標を達成する簡単な方法があるかどうかを確認したいと思いましたか?
drop function dbo.runners_contain_runner
go
create function dbo.runners_contain_runner(@runner varchar(max), @runners varchar(max))
returns int
as
begin
/*
eliminate the plus sign from @runners so that the
'isnumeric' function doesn't return false positives (it returns 1 for '+')
*/
set @runners = replace(@runners,'+','_' )
declare @ret int;
set @ret = 0;
-- if the runner is the only runner return 1
if @runners = @runner
set @ret = 1
else
begin
declare @charindex int;
set @charindex = charindex(@runner,@runners)
if @charindex > 0
begin
-- if it is at the beginning then check the char after it
if @charindex = 1
begin
if isnumeric(substring(@runners,@charindex + len(@runner),1)) = 0
set @ret = @charindex
end
-- if it is at the end then check the char before it
else if @charindex = len(@runners) - (len(@runner) - 1)
begin
if isnumeric(substring(@runners,@charindex - 1,1)) = 0
set @ret = @charindex
end
-- if it is in the middle check the chars either side of it
else
begin
if isnumeric(substring(@runners,@charindex - 1,1)) +
isnumeric(substring(@runners,@charindex + len(@runner),1)) = 0
set @ret = @charindex
end
end
end
return @ret
end
たぶん、あなたは数を求めることに集中しすぎて、これを過度に複雑にしています。少し前に戻ります。実際に必要なのは、両側に数字がない部分文字列です。数値がより大きい数値の一部になる唯一の方法は、その両側に少なくとも1桁あることです。したがって、数値のみを渡す限り、この定義では、どちらの側にも数字がない数値を生成する必要があります。
それを念頭に置いて、渡された値が左端、右端、または中央にある場合、3つのPATINDEX
述部が必要です。うまくいくと思われるので、以下を試してください:
GO
CREATE PROCEDURE #TestFindRunner
(
@Runner VARCHAR(10)
)
AS
SET NOCOUNT ON;
DECLARE @Data TABLE
(
[ID] INT NOT NULL PRIMARY KEY,
[Runners] VARCHAR(50) NULL
);
INSERT INTO @Data ([ID], [Runners]) VALUES (1, '16+7+9+6');
INSERT INTO @Data ([ID], [Runners]) VALUES (2, '16+7+9+5');
INSERT INTO @Data ([ID], [Runners]) VALUES (3, '26+77+9+5');
INSERT INTO @Data ([ID], [Runners]) VALUES (4, '6+3+45');
INSERT INTO @Data ([ID], [Runners]) VALUES (5, '63,808,111,92');
INSERT INTO @Data ([ID], [Runners]) VALUES (6, '1-7-9,6');
INSERT INTO @Data ([ID], [Runners]) VALUES (7, '1-6-9,7');
INSERT INTO @Data ([ID], [Runners]) VALUES (8, '1-7-9,63');
INSERT INTO @Data ([ID], [Runners]) VALUES (9, '1-63-9,7');
INSERT INTO @Data ([ID], [Runners]) VALUES (10, NULL);
INSERT INTO @Data ([ID], [Runners]) VALUES (11, '6');
SELECT tmp.*
FROM @Data tmp
WHERE @Runner COLLATE Latin1_General_100_BIN2 = tmp.[Runners]
OR PATINDEX('%[^0123456789]' + @Runner COLLATE Latin1_General_100_BIN2,
tmp.[Runners]) > 0
OR PATINDEX(@Runner + '[^0123456789]%' COLLATE Latin1_General_100_BIN2,
tmp.[Runners]) > 0
OR PATINDEX('%[^0123456789]' + @Runner + '[^0123456789]%'
COLLATE Latin1_General_100_BIN2, tmp.[Runners]) > 0
GO
そして、次でテストします:
EXEC #TestFindRunner 0;
EXEC #TestFindRunner 2;
EXEC #TestFindRunner 4;
EXEC #TestFindRunner 8;
EXEC #TestFindRunner 11;
-- 0 rows
EXEC #TestFindRunner 3; -- 4
EXEC #TestFindRunner 77; -- 3
EXEC #TestFindRunner 111; -- 5
-- 1 row
EXEC #TestFindRunner 5; -- 2 and 3
-- 2 rows
EXEC #TestFindRunner 1; -- 6, 7, 8, and 9
-- 4 rows
EXEC #TestFindRunner 6; -- 1, 4, 6, 7, and 11
-- 5 rows
EXEC #TestFindRunner 7; -- 1, 2, 6, 7, 8, and 9
-- 6 rows
EXEC #TestFindRunner 9; -- 1, 2, 3, 6, 7, 8, and 9
-- 7 rows
PATINDEX
の3つのバリエーションがある理由は、PATINDEX
の検索パターンがnot正規表現(RegeEx)であるためです。 LIKE
パターン)。 PATINDEX
およびLIKE
パターンには数量詞がないため、[^0123456789]
の単一文字の置換を「0以上」にするように指定することはできません。それは「唯一無二、それ以上でもそれ以下でもない」です。
バイナリ照合を強制すると(つまり、各COLLATE Latin1_General_100_BIN2
参照の後の@Runner
)、これらの10桁の10進数のみが処理され、同等と見なされる可能性のある他の文字は処理されません
上記のロジックをインラインテーブル値関数(TVF)に入れて、使いやすく(そして、同様に使いやすいスカラーUDFよりも効率的に)するには、次のことを試してください。
USE [tempdb];
GO
CREATE FUNCTION dbo.IsRunnerPresent
(
@Runner VARCHAR(10),
@Runners VARCHAR(8000)
)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN
SELECT CONVERT(BIT,
CASE WHEN @Runner COLLATE Latin1_General_100_BIN2 = @Runners
OR PATINDEX('%[^0123456789]' + @Runner
COLLATE Latin1_General_100_BIN2, @Runners) > 0
OR PATINDEX(@Runner + '[^0123456789]%'
COLLATE Latin1_General_100_BIN2, @Runners) > 0
OR PATINDEX('%[^0123456789]' + @Runner + '[^0123456789]%'
COLLATE Latin1_General_100_BIN2, @Runners) > 0
THEN 1
ELSE 0
END) AS [RunnerFound];
GO
そして、次でテストします:
DECLARE @Runner VARCHAR(10);
SET @Runner = '6';
DECLARE @Data TABLE
(
[ID] INT NOT NULL PRIMARY KEY,
[Runners] VARCHAR(50) NULL
);
INSERT INTO @Data ([ID], [Runners]) VALUES (1, '16+7+9+6');
INSERT INTO @Data ([ID], [Runners]) VALUES (2, '16+7+9+5');
INSERT INTO @Data ([ID], [Runners]) VALUES (3, '26+77+9+5');
INSERT INTO @Data ([ID], [Runners]) VALUES (4, '6+3+45');
INSERT INTO @Data ([ID], [Runners]) VALUES (5, '63,808,111,92');
INSERT INTO @Data ([ID], [Runners]) VALUES (6, '1-7-9,6');
INSERT INTO @Data ([ID], [Runners]) VALUES (7, '1-6-9,7');
INSERT INTO @Data ([ID], [Runners]) VALUES (8, '1-7-9,63');
INSERT INTO @Data ([ID], [Runners]) VALUES (9, '1-63-9,7');
INSERT INTO @Data ([ID], [Runners]) VALUES (10, NULL);
INSERT INTO @Data ([ID], [Runners]) VALUES (11, '6');
SELECT tmp.[ID],
tmp.[Runners],
fnd.[RunnerFound]
FROM @Data tmp
CROSS APPLY dbo.IsRunnerPresentTVF(@Runner, tmp.[Runners]) fnd;
どちらが戻ります:
ID Runners RunnerFound
1 16+7+9+6 1
2 16+7+9+5 0
3 26+77+9+5 0
4 6+3+45 1
5 63,808,111,92 0
6 1-7-9,6 1
7 1-6-9,7 1
8 1-7-9,63 0
9 1-63-9,7 0
10 NULL 0
11 6 1
以降、関数を次のように書き直しました。
編集:質問に対するSolomon Rutzkyの返信を見る前に、これを完了しました。彼の返事はうまくいき、私のものよりはましだ。しかし、私はこの答えを代替として残しておきます。
drop function dbo.runners_contain_runner
go
create function dbo.runners_contain_runner(@runner varchar(max), @runners varchar(max))
returns int
as
begin
declare @ret int
if @runner = @runners
set @ret = 1
else if charindex(@runner,@runners) = 0
set @ret = 0
else
begin
declare @foundindex int
declare @item varchar(max)
declare @tmp varchar(max)
declare @position int
set @tmp = @runners
set @position = 1
set @foundindex = 0
while @position > 0
begin
set @foundindex = @foundindex + @position
set @position = charindex('+',@tmp)
if @position > 0
begin
set @item = substring(@tmp,1,@position - 1)
set @tmp = substring(@tmp,@position + 1,len(@tmp) - @position)
end
else
set @item = @tmp
if @item = @runner
begin
set @ret = @foundindex
break
end
else
set @ret = 0
end
end
return @ret
end