ストアドプロシージャのパラメーターで送信されたコンマ区切りの値に基づいて、テーブルからデータを取得する必要があります。現在のところ、1つの値に対してコードを機能させていますが、複数の値に対して機能させる方法はわかりません。
サンプルテーブル:
CREATE TABLE [dbo].FinalStatus
(
[ID] [int] Primary key IDENTITY(1,1) NOT NULL,
[Col1] [varchar](15) NULL,
[Col2] [varchar](15) NULL,
[Col3] [varchar](100) NOT NULL,
[LastUpdatedDate] [datetime] NOT NULL DEFAULT (getdate())
)
テストデータ:
Insert into FinalStatus (Col1, Col2, Col3) values ('10','ABC21','Msg1')
Insert into FinalStatus (Col1, Col2, Col3) values ('10','ABC21','Msg2')
Insert into FinalStatus (Col1, Col2, Col3) values ('11','C21','Some Msg1')
Insert into FinalStatus (Col1, Col2, Col3) values ('12','BC21','Some Msg2')
ストアドプロシージャ:
CREATE PROCEDURE [dbo].[FindResult]
(@col1 VARCHAR(15) = NULL,
@col2 VARCHAR(15) = NULL)
AS
SET NOCOUNT ON
BEGIN
DECLARE @c1 VARCHAR(15)
DECLARE @c2 VARCHAR(15)
SET @c1 = @col1
SET @c2 = @col2
SELECT
Col2, Col1,
LastUpdatedDate, Col3
FROM
dbo.FinalStatus
WHERE
(Col1 = @c1 OR @c1 IS NULL)
AND (Col2 = @c2 OR @c2 IS NULL)
ORDER BY
LastUpdatedDate DESC
END
単一値の実行スクリプト(ここまで機能します):
--To get all data
EXEC [dbo].[FindResult]
--passing first parameter alone
EXEC [dbo].[FindResult] @col1 = '10', @col2 = NULL
--passing second parameter alone
EXEC [dbo].[FindResult] @col1 = null , @col2 = 'c21'
パラメータに複数の値を渡した場合でも、適切な結果を返すにはどうすればよいですか?
このようなもの:
EXEC [dbo].[FindResult] @col1 = '10,12', @col2 = NULL
EXEC [dbo].[FindResult] @col1 = null , @col2 = 'ABC21, c21'
基になるテーブルには、特定の時点で最低100000レコードが含まれます。
Erland Sommarskogによる "SQL Server 2008の配列とリスト" を読んでみましたが、私の頭の上にありました。したがって、このストアドプロシージャを変更するためのヘルプを探しています。
それを行うにはいくつかの方法があります。データモデルを変更することもできます。
カンマ区切りの文字列を使い続けたい場合は、CLR、CTE、XMLなどを使用して、オンラインで多くの文字列分割関数を見つけることができます。ここではそれらをすべて取り上げるわけではないので、ベンチマークは行いません。しかし、あなたは彼ら全員が彼ら自身の問題を持っていることを知らなければなりません。詳細については、この投稿を Aaron Bertrand から確認できます。 文字列を正しい方法で分割する–または次善の方法
DECLARE @c1 nvarchar(100) = N'10,15,13,14';
DECLARE @delimiter nvarchar(1) = N',';
SELECT v1 = LTRIM(RTRIM(vals.node.value('(./text())[1]', 'nvarchar(4000)')))
FROM (
SELECT x = CAST('<root><data>' + REPLACE(@c1, @delimiter, '</data><data>') + '</data></root>' AS XML).query('.')
) v
CROSS APPLY x.nodes('/root/data') vals(node);
v1
10
15
13
14
アイデアは、それをテーブルに変換することです。その後、相関サブクエリで使用できます。
DECLARE @c1 nvarchar(100) = N'10,15,13,14';
DECLARE @c2 nvarchar(100) = N'C21, B21';
DECLARE @delimiter nvarchar(1) = N',';
SELECT *
FROM FinalStatus f
WHERE EXISTS (
SELECT 1
FROM (
SELECT CAST('<root><data>' + REPLACE(@c1, @delimiter, '</data><data>') + '</data></root>' AS XML) AS x
)t
CROSS APPLY x.nodes('/root/data') vals(node)
WHERE LTRIM(RTRIM(vals.node.value('.[1]', 'nvarchar(4000)'))) = f.Col1
)
OR EXISTS (
SELECT 1
FROM (
SELECT CAST('<root><data>' + REPLACE(@c2, @delimiter, '</data><data>') + '</data></root>' AS XML) AS x
)t
CROSS APPLY x.nodes('/root/data') vals(node)
WHERE LTRIM(RTRIM(vals.node.value('.[1]', 'nvarchar(4000)'))) = f.Col2
);
プロシージャで更新する必要があるのは、WHERE
句だけです。パラメータは、カンマ区切りの値を持つvarcharタイプのままです。
ID Col1 Col2 Col3 LastUpdatedDate
1 10 ABC21 Msg1 2016-07-20 09:06:19.380 => match c1
2 10 ABC21 Msg2 2016-07-20 09:06:19.390 => match c1
3 11 C21 Some Msg1 2016-07-20 09:06:19.390 => match c2
パラメータのタイプを変更できる場合は、XMLデータタイプを使用できます。プロシージャとクエリによって簡単に逆シリアル化できます。
このクエリは、前のクエリとよく似ています。ただし、変換や検索および置換がないため、無効な値や特殊文字をより適切に制御できます。
DECLARE @c xml = N'
<root>
<c1>10</c1><c1>15</c1><c1>13</c1><c1>14</c1>
<c2>C21</c2><c2>B21</c2>
</root>';
DECLARE @delimiter nvarchar(1) = N',';
SELECT *
FROM FinalStatus f
WHERE EXISTS (
SELECT 1
FROM (
SELECT x = @c.query('.')
) v
CROSS APPLY x.nodes('/root/c1') val1(node)
WHERE LTRIM(RTRIM(val1.node.value('.[1]', 'nvarchar(4000)'))) = f.Col1
)
OR EXISTS (
SELECT 1
FROM (
SELECT x = @c.query('.')
) v
CROSS APPLY x.nodes('/root/c2') val1(node)
WHERE LTRIM(RTRIM(val1.node.value('.[1]', 'nvarchar(4000)'))) = f.Col2
);
パラメータのタイプをxmlに変更する必要があります。 c1
およびc2
ノードで1つの変数のみを使用しましたが、各ノード(c1、c2、...)ごとに1つの変数を使用し、異なるノード名も使用できます。
DECLARE @c1 xml = N'<root><data>10</data><data>15</data><data>13</data><data>14</data></root>';
DECLARE @c2 xml = N'<root><data>C21</data><data>B21</data></root>';
DECLARE @c3 xml = N'...';
CROSS APPLY
のnodes
部分で正しいパスと変数を更新する必要があります。
.Net、Powershell、またはその他の言語を使用して、配列またはその他のタイプを簡単にxmlに変換し、プロシージャのパラメーターとして使用できます。
...
別のオプションは、ストアドプロシージャパラメータで使用できるテーブル値タイプを作成することです。
CREATE TYPE [dbo].[TableTypeCols] AS TABLE
(
[col] varchar(15)
);
次に、この新しく作成されたタイプを使用してストアドプロシージャを更新します。
CREATE OR ALTER PROCEDURE [dbo].[FindResult]
@c1 [dbo].[TableTypeCols] READONLY
, @c2 [dbo].[TableTypeCols] READONLY
AS
SELECT * -- fs.[...], fs.[...], ...
FROM [dbo].[FinalStatus] fs
WHERE
EXISTS (
SELECT 1 FROM @c1 c1 WHERE c1.col = fs.Col1
)
OR EXISTS (
SELECT 1 FROM @c2 c2 WHERE c2.col = fs.Col2
);
GO;
READONLY
はパラメーター宣言では必須です。
SQLでは、テーブル変数を使用して同じ方法で呼び出すことができます。
DECLARE @tc1 [dbo].[TableTypeCols];
DECLARE @tc2 [dbo].[TableTypeCols];
INSERT INTO @tc1(col) VALUES('10'), ('15'), ('13'), ('14');
INSERT INTO @tc2(col) VALUES('C21'), ('B21');
EXEC dbo.FindResult @c1 = @tc1, @c2 = @tc2;
ID Col1 Col2 Col3 LastUpdatedDate
1 10 ABC21 Msg1 2016-07-20 09:06:19.380 => match c1
2 10 ABC21 Msg2 2016-07-20 09:06:19.390 => match c1
3 11 C21 Some Msg1 2016-07-20 09:06:19.390 => match c2
各パラメーターに2つ以上の行を使用する場合は、実際のデータを使用してパフォーマンステストを実行し、システムで正しく機能することを確認してください。
手順は、.Netコードから呼び出すことができます
// Create & Fill Parameter 1
DataTable dt1 = new DataTable();
dt1.Columns.Add("col", typeof (string));
DataRow row = dt1.NewRow();
row["col"] = ("10");
dt1.Rows.Add(row);
/*
... add more row ...
*/
// Create & Fill Parameter 2
DataTable dt2 = new DataTable();
dt2.Columns.Add("col", typeof (string));
DataRow row = dt2.NewRow();
row["col"] = ("C21");
dt2.Rows.Add(row);
/*
... add more row ...
*/
// Output dataset
DataSet ds = new DataSet("SQLDatabase");
using (SqlConnection conn = new SqlConnection(...))
{
// Stored Procedure with table parameters
SqlCommand sqlComm = new SqlCommand("dbo.FindResult", conn);
sqlComm.CommandType = CommandType.StoredProcedure;
// Parameter 1
SqlParameter param = new SqlParameter("@c1", SqlDbType.Structured)
{
TypeName = "dbo.TableTypeCols",
Value = dt1
};
sqlComm.Parameters.Add(param);
// Parameter 2
SqlParameter param = new SqlParameter("@c2", SqlDbType.Structured)
{
TypeName = "dbo.TableTypeCols",
Value = dt2
};
sqlComm.Parameters.Add(param);
// Call Stored Procedure
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = sqlComm;
da.Fill(ds);
}
SqlDbType.Structured
は必須です。
私は.Netの専門家ではないため、このコードを記述して使用する方法は他にもあり、より優れていることに注意してください。
最小限の変更で、jvar文字列を使用してNVARCHAR(MAX)パラメーターを宣言できます。
DECLARE @C1 NVARCHAR(MAX);
SET @jsonC1 =
N'[
{ "col" : "10"}, { "col" : "15"}, { "col" : "13"}, { "col" : "14"}
]';
DECLARE @C2 NVARCHAR(MAX);
SET @jsonC2 =
N'[
{ "col" : "C21"}, { "col" : "B21"}
]';
SELECT *
FROM dbo.FinalStatus f
WHERE
EXISTS (
SELECT 1 FROM OPENJSON(@C1) WITH(col nvarchar(15)) AS c1 WHERE c1.col = f.Col1
)
OR
EXISTS (
SELECT 1 FROM OPENJSON(@C2) WITH(col nvarchar(15)) AS c2 WHERE c2.col = f.Col2
);
[〜#〜] openjson [〜#〜] にはSQL Server 2016が必要です。
ある時点で、その文字列を分割する必要があります。 @MguerraTorresによって提案されているようにプロシージャを呼び出す前、またはプロシージャ内のいずれか。次に、文字列分割関数の良い例を示します。 http://www.codeproject.com/Articles/7938/SQL-User-Defined-Function-to-Parse-a-Delimited-Str
その関数はテーブルのデータ型を返します。そのテーブルを使用して、分割テーブル内の行をクエリできます。
select what_i_want from my_table where value in (select value from split_table )
SQL Server 2016には、STRING_SPLIT関数が組み込まれています。
ここでの簡単な解決策は、(例として)where句を「=」から「in」に変更することです。
where col1 = @c1 --> where col1 in (@c1)
@ c1を一重引用符の付いた値リストに変更します。たとえば、
@c1 = '1,2,3' --> '1','2','3'
変更された手順スクリプトは次のとおりです
CREATE PROCEDURE [dbo].[FindResult]
(@col1 VARCHAR(15) = NULL,
@col2 VARCHAR(15) = NULL)
AS
SET NOCOUNT ON
BEGIN
DECLARE @c1 VARCHAR(15)
DECLARE @c2 VARCHAR(15)
declare @qry varchar(3000);
SET @c1 = ''''+replace(@col1,',', ''',''')+'''' -- make @col1 to be a string in signle quote
SET @c2 =''''+replace(@col2,',', ''',''')+'''' -- make @col1 to be a string in signle quote
select @c1=iif(@c1 is null, 'col1', @c1), @c2=iif(@c2 is null, 'col2', @c2);
set @qry='
SELECT
Col2, Col1,
LastUpdatedDate, Col3
FROM
dbo.FinalStatus
WHERE
(Col1 in ('+@c1+') OR 1=1)
AND (Col2 in ('+@c2+') OR 2=2)
ORDER BY
LastUpdatedDate DESC';
if @c1 <> 'col1' -- i.e. @col1 parameter is not null, remove OR 1=1
set @qry = replace(@qry, 'OR 1=1', '');
IF @C2 <> 'col2' -- i.e. @col2 parameter is not null, remove OR 2=2
set @qry = replace(@qry, 'OR 2=2', '');
print @qry -- you can comment out this line, it is for debugging
exec (@qry)
END
これで、パラメーターで複数の値を使用できます
EXEC [dbo].[FindResult] @col1 = '10,12', @col2 = NULL;
EXEC [dbo].[FindResult] @col1 = null , @col2 = 'ABC21,c21'; --note NO space before or after comma.
呼び出しコードの変更を最小限に抑える1つのオプションは、単純なXMLを渡すことです。これは、@ JulienVavasseurの answer のオプション2と非常に似ていますが、2つの入力パラメーターを別々に保つ点が異なります。ここでの考えは、既存のString.Join(',', _SomeIntArray)
を'<v>' + String.Join('</v><v>', _SomeIntArray) + '</v>'
のように変更するように開発者を説得するのに十分簡単なはずであるということです。次に、次のようなクエリでそれらを使用できます。これは実行可能な例なので、そのままにしておきます。
DECLARE @Input1 XML,
@Input2 XML;
SET @Input1 = '<v>231</v><v>175</v>';
SET @Input2 = '<v>256</v><v>4</v>';
-- Both commented out = 800
-- Only @Input2 commented out = 119
-- Only @Input1 commented out = 390
-- Neither commented out = 63
SELECT so.*, in1.col.value('./text()[1]', 'INT')
FROM master.sys.columns so
LEFT JOIN @Input1.nodes('v') in1(col)
ON in1.col.value('./text()[1]', 'INT') = so.[system_type_id]
LEFT JOIN @Input2.nodes('v') in2(col)
ON in2.col.value('./text()[1]', 'INT') = so.[max_length]
WHERE (@Input1 IS NULL OR in1.col.value('./text()[1]', 'INT') IS NOT NULL)
AND (@Input2 IS NULL OR in2.col.value('./text()[1]', 'INT') IS NOT NULL);
1つの単一の入力に対してストアドプロシージャを2回実行することはできません。
テーブルの目的の行ごとにストアドプロシージャを1回実行するカーソルまたはループをお勧めします。
各ループの反復は、次のようになります。1)2つの変数をCol1およびCol2と等しくなるように設定します。2)入力パラメーターとして新しい変数を使用してプロシージャを実行します。
--Loop Code Here
--For each record returned by your FinalStatus table:
SET @Value1 =Col1;
SET @Value2 =Col2;
--Then execute the stored proc with those values
EXEC [dbo].[FindResult] @Value1,@value2
--End Loop Code