web-dev-qa-db-ja.com

SQL Serverストアドプロシージャに配列を渡す方法

SQL Serverのストアドプロシージャに配列を渡す方法

たとえば、従業員のリストがあります。このリストをテーブルとして使い、それを別のテーブルと結合したいのです。しかし、従業員のリストはC#からパラメータとして渡されるべきです。

255
Sergey

SQL Server 2008(またはそれ以降)

まず、データベースに次の2つのオブジェクトを作成します。

CREATE TYPE dbo.IDList
AS TABLE
(
  ID INT
);
GO

CREATE PROCEDURE dbo.DoSomethingWithEmployees
  @List AS dbo.IDList READONLY
AS
BEGIN
  SET NOCOUNT ON;

  SELECT ID FROM @List; 
END
GO

今すぐあなたのC#コードで:

// Obtain your list of ids to send, this is just an example call to a helper utility function
int[] employeeIds = GetEmployeeIds();

DataTable tvp = new DataTable();
tvp.Columns.Add(new DataColumn("ID", typeof(int)));

// populate DataTable from your List here
foreach(var id in employeeIds)
    tvp.Rows.Add(id);

using (conn)
{
    SqlCommand cmd = new SqlCommand("dbo.DoSomethingWithEmployees", conn);
    cmd.CommandType = CommandType.StoredProcedure;
    SqlParameter tvparam = cmd.Parameters.AddWithValue("@List", tvp);
    // these next lines are important to map the C# DataTable object to the correct SQL User Defined Type
    tvparam.SqlDbType = SqlDbType.Structured;
    tvparam.TypeName = "dbo.IDList";
    // execute query, consume results, etc. here
}

SQL Server 2005

SQL Server 2005を使用している場合でも、XML上の分割機能をお勧めします。まず、関数を作成します。

CREATE FUNCTION dbo.SplitInts
(
   @List      VARCHAR(MAX),
   @Delimiter VARCHAR(255)
)
RETURNS TABLE
AS
  RETURN ( SELECT Item = CONVERT(INT, Item) FROM
      ( SELECT Item = x.i.value('(./text())[1]', 'varchar(max)')
        FROM ( SELECT [XML] = CONVERT(XML, '<i>'
        + REPLACE(@List, @Delimiter, '</i><i>') + '</i>').query('.')
          ) AS a CROSS APPLY [XML].nodes('i') AS x(i) ) AS y
      WHERE Item IS NOT NULL
  );
GO

今すぐあなたのストアドプロシージャはちょうどすることができます:

CREATE PROCEDURE dbo.DoSomethingWithEmployees
  @List VARCHAR(MAX)
AS
BEGIN
  SET NOCOUNT ON;

  SELECT EmployeeID = Item FROM dbo.SplitInts(@List, ','); 
END
GO

そしてあなたのC#コードではあなたは'1,2,3,12'...としてリストを渡す必要があります。


テーブル値パラメータを渡す方法は、それを使用するソリューションの保守性を単純化し、XMLや文字列分割を含む他の実装と比較してパフォーマンスが向上することが多いと思います。

入力は明確に定義され(区切り文字がコンマまたはセミコロンであるかどうかを推測する必要はありません)、ストアード・プロシージャーのコードを調べなければ明らかではない他の処理関数に依存しません。

UDTの代わりにユーザー定義のXMLスキーマを使用するソリューションと比較すると、これには同様のステップ数が含まれますが、私の経験では管理、保守、および読み取りがはるかに簡単なコードです。

多くのソリューションでは、多くのストアドプロシージャに再利用するこれらのUDT(ユーザー定義型)のうちの1つか少数しか必要としないかもしれません。この例のように、一般的な要件はIDポインタのリストを通過することです。関数名はそれらのIDが表すべきコンテキストを記述し、型名は一般的であるべきです。

395
Aaron Bertrand

私の経験に基づいて、従業員IDから区切られた式を作成することによって、この問題に対するトリッキーでいい解決策があります。 ';123;434;365;'のような文字列式のみを作成してください。ここで123434、および365は、一部の従業員IDです。下記の手続きを呼び出してこの式を渡すことで、あなたの望むレコードを取得することができます。簡単にあなたはこのクエリに "別のテーブル"を結合することができます。このソリューションは、すべてのバージョンのSQL Serverに適しています。また、テーブル変数または一時テーブルを使用する場合と比較して、非常に高速で最適化されたソリューションです。

CREATE PROCEDURE dbo.DoSomethingOnSomeEmployees  @List AS varchar(max)
AS
BEGIN
  SELECT EmployeeID 
  FROM EmployeesTable
  -- inner join AnotherTable on ...
  where @List like '%;'+cast(employeeID as varchar(20))+';%'
END
GO
36

ストアドプロシージャにテーブル値パラメーターを使用します。

C#から渡すと、SqlDb.Structuredのデータ型を持つパラメータを追加します。

ここを参照してください: http://msdn.Microsoft.com/en-us/library/bb675163.aspx

例:

// Assumes connection is an open SqlConnection object.
using (connection)
{
// Create a DataTable with the modified rows.
DataTable addedCategories =
  CategoriesDataTable.GetChanges(DataRowState.Added);

// Configure the SqlCommand and SqlParameter.
SqlCommand insertCommand = new SqlCommand(
    "usp_InsertCategories", connection);
insertCommand.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue(
    "@tvpNewCategories", addedCategories);
tvpParam.SqlDbType = SqlDbType.Structured;

// Execute the command.
insertCommand.ExecuteNonQuery();
}
24
Levi W

XMLパラメータとして渡す必要があります。

編集: 私のプロジェクトからの簡単なコードであなたにアイディアを与えましょう:

CREATE PROCEDURE [dbo].[GetArrivalsReport]
    @DateTimeFrom AS DATETIME,
    @DateTimeTo AS DATETIME,
    @HostIds AS XML(xsdArrayOfULong)
AS
BEGIN
    DECLARE @hosts TABLE (HostId BIGINT)

    INSERT INTO @hosts
        SELECT arrayOfUlong.HostId.value('.','bigint') data
        FROM @HostIds.nodes('/arrayOfUlong/u') as arrayOfUlong(HostId)

その後、一時テーブルを使用して自分のテーブルと結合できます。データの完全性を維持するためにarrayOfUlongを組み込みのXMLスキーマとして定義しましたが、そうする必要はありません。私はそれを使用することをお勧めしますので、ここであなたが常に長いXMLを取得することを確認するための簡単なコードです。

IF NOT EXISTS (SELECT * FROM sys.xml_schema_collections WHERE name = 'xsdArrayOfULong')
BEGIN
    CREATE XML SCHEMA COLLECTION [dbo].[xsdArrayOfULong]
    AS N'<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="arrayOfUlong">
        <xs:complexType>
            <xs:sequence>
                <xs:element maxOccurs="unbounded"
                            name="u"
                            type="xs:unsignedLong" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>';
END
GO
15
Fedor Hajdu

配列のsizecomplexityなど、コンテキストは常に重要です。小規模から中規模のリストの場合、いくつかの明確化を行う必要がありますが、ここに投稿されたいくつかの回答は問題ありません。

  • 区切りリストを分割するには、SQLCLRベースのスプリッターが最速です。独自のコードを作成する場合は、多数の例があります。または、CLR関数の無料の SQL# ライブラリ(私が作成したString_Split関数、および多くのその他は完全に無料です)。
  • XMLベースの配列の分割は高速ですが、要素ベースのXMLではなく属性ベースのXMLを使用する必要があります(これは、ここでの答えは、@ AaronBertrandのXMLの例は、コードがtext() XML関数を使用しているため、最高です。XMLを使用したリストの分割に関する詳細(パフォーマンス分析など)については、チェックアウト 「XMLを使用してSQL Serverのパラメーターとしてリストを渡す」 Phil Factor。
  • TVPを使用すると、データがプロシージャにストリーミングされ、事前に解析され、テーブル変数として厳密に型指定されて表示されるので(少なくともSQL Server 2008以降を使用している場合)優れています。ただし、ほとんどの場合、すべてのデータをDataTableに格納するということは、元のコレクションからコピーされるときにデータをメモリに複製することを意味します。したがって、TVPを渡すDataTableメソッドの使用は、より大きなデータセットに対してうまく機能しません(つまり、適切にスケーリングされません)。
  • XMLは、IntまたはStringの単純な区切りリストとは異なり、TVPと同様に1次元以上の配列を処理できます。しかし、DataTable TVPメソッドと同様に、XMLは、XMLドキュメントのオーバーヘッドをさらに考慮する必要があるため、メモリ内のデータサイズが2倍を超えるため、うまくスケーリングできません。

以上のことをすべて踏まえると、使用しているデータが大きい場合、またはそれほど大きくはないが常に成長している場合は、IEnumerable TVPメソッドがデータをSQL Serverにストリーミングするときに最適です(DataTable method)、ただし、メモリ内のコレクションの複製は必要ありません(他のメソッドとは異なります)。この回答では、SQLおよびC#コードの例を投稿しました。

ストアドプロシージャT-SQLに辞書を渡す

13
Solomon Rutzky

SQL Serverでは配列はサポートされていませんが、コレクションをストアドプロシージャに渡す方法はいくつかあります。

  1. Datatableを使って
  2. XMLを使用する。コレクションをxml形式に変換してから、ストアドプロシージャへの入力として渡す

下のリンクはあなたを助けるかもしれません

コレクションをストアドプロシージャに渡す

6
praveen

これはあなたを助けるでしょう。 :)次の手順に従ってください、

  1. クエリデザイナを開く
  2. コピー次のコードをそのまま貼り付けます。文字列をIntに変換する関数が作成されます。

    CREATE FUNCTION dbo.SplitInts
    (
       @List      VARCHAR(MAX),
       @Delimiter VARCHAR(255)
    )
    RETURNS TABLE
    AS
      RETURN ( SELECT Item = CONVERT(INT, Item) FROM
          ( SELECT Item = x.i.value('(./text())[1]', 'varchar(max)')
            FROM ( SELECT [XML] = CONVERT(XML, '<i>'
            + REPLACE(@List, @Delimiter, '</i><i>') + '</i>').query('.')
              ) AS a CROSS APPLY [XML].nodes('i') AS x(i) ) AS y
          WHERE Item IS NOT NULL
      );
    GO
    
  3. 次のストアドプロシージャを作成します。

     CREATE PROCEDURE dbo.sp_DeleteMultipleId
     @List VARCHAR(MAX)
     AS
     BEGIN
          SET NOCOUNT ON;
          DELETE FROM TableName WHERE Id IN( SELECT Id = Item FROM dbo.SplitInts(@List, ',')); 
     END
     GO
    
  4. これを実行しますSP exec sp_DeleteId '1,2,3,12'を使用して、これは削除したいIdの文字列です。

  5. 配列をC#の文字列に変換し、それをストアドプロシージャのパラメータとして渡します。

    int[] intarray = { 1, 2, 3, 4, 5 };  
    string[] result = intarray.Select(x=>x.ToString()).ToArray();
    

    SqlCommand command = new SqlCommand();
    command.Connection = connection;
    command.CommandText = "sp_DeleteMultipleId";
    command.CommandType = CommandType.StoredProcedure;
    command.Parameters.Add("@Id",SqlDbType.VARCHAR).Value=result ;
    

これにより複数の行が削除されます。

5
Charan Ghate

私は、この linK が見つかるまで、新しいTable型を作成する手間をかけずに、SQLサーバーに配列を渡す方法のすべての例と答えを調べてきました。 :

- 次のコードは、パラメータとして配列を取得し、その値を別のテーブルに挿入します。

Create Procedure Proc1 


@UserId int, //just an Id param
@s nvarchar(max)  //this is the array your going to pass from C# code to your Sproc

AS

    declare @xml xml

    set @xml = N'<root><r>' + replace(@s,',','</r><r>') + '</r></root>'

    Insert into UserRole (UserID,RoleID)
    select 
       @UserId [UserId], t.value('.','varchar(max)') as [RoleId]


    from @xml.nodes('//root/r') as a(t)
END 

楽しんでくれると良いです

5
Adam

これを理解するのに長い時間がかかったので、誰かがそれを必要とする場合に備えて...

これは、Aaronの回答にあるSQL 2005の方法に基づいており、彼のSplitInts関数を使用しています(私は常にコンマを使用するので、delimパラメータを削除しました)。 SQL 2008を使用していますが、型付きデータセット(XSD、TableAdapters)で機能するものが必要でした。文字列パラメータがそれらを使用することはわかっています。

私は彼の機能を "where in(1,2,3)"型の節で動かそうとしていましたが、運が悪くないのです。そこで、まず一時テーブルを作成し、次に「where in」ではなく内部結合を実行しました。これが私の使用例です。私の場合は、特定の材料が入っていないレシピのリストを入手したいと思いました。

CREATE PROCEDURE dbo.SOExample1
    (
    @excludeIngredientsString varchar(MAX) = ''
    )
AS
    /* Convert string to table of ints */
    DECLARE @excludeIngredients TABLE (ID int)
    insert into @excludeIngredients
    select ID = Item from dbo.SplitInts(@excludeIngredientsString)

    /* Select recipies that don't contain any ingredients in our excluded table */
   SELECT        r.Name, r.Slug
FROM            Recipes AS r LEFT OUTER JOIN
                         RecipeIngredients as ri inner join
                         @excludeIngredients as ei on ri.IngredientID = ei.ID
                         ON r.ID = ri.RecipeID
WHERE        (ri.RecipeID IS NULL)
2
eselk