web-dev-qa-db-ja.com

ストアドプロシージャのパラメータにカンマ区切りの値を渡す

ストアドプロシージャのパラメーターで送信されたコンマ区切りの値に基づいて、テーブルからデータを取得する必要があります。現在のところ、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の配列とリスト" を読んでみましたが、私の頭の上にありました。したがって、このストアドプロシージャを変更するためのヘルプを探しています。

3
prasanth

それを行うにはいくつかの方法があります。データモデルを変更することもできます。

1.カンマ区切りのパラメーター

カンマ区切りの文字列を使い続けたい場合は、CLR、CTE、XMLなどを使用して、オンラインで多くの文字列分割関数を見つけることができます。ここではそれらをすべて取り上げるわけではないので、ベンチマークは行いません。しかし、あなたは彼ら全員が彼ら自身の問題を持っていることを知らなければなりません。詳細については、この投稿を Aaron Bertrand から確認できます。 文字列を正しい方法で分割する–または次善の方法

サンプルクエリ(XML変換を使用):

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

2. XMLパラメータ

パラメータのタイプを変更できる場合は、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 APPLYnodes部分で正しいパスと変数を更新する必要があります。

.Net、Powershell、またはその他の言語を使用して、配列またはその他のタイプを簡単にxmlに変換し、プロシージャのパラメーターとして使用できます。

...

3.ユーザー定義のテーブルタイプ

別のオプションは、ストアドプロシージャパラメータで使用できるテーブル値タイプを作成することです。

テーブルタイプ

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 C#呼び出し

手順は、.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の専門家ではないため、このコードを記述して使用する方法は他にもあり、より優れていることに注意してください。

4. Json型パラメーター(SQL Server 2016以降)

最小限の変更で、jvar文字列を使用してNVARCHAR(MAX)パラメーターを宣言できます。

jsonパラメータ

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が必要です。

7

ある時点で、その文字列を分割する必要があります。 @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関数が組み込まれています。

3
R Evans

ここでの簡単な解決策は、(例として)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.
2
jyao

呼び出しコードの変更を最小限に抑える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
Solomon Rutzky

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
0
MguerraTorres