SQL Serverのストアドプロシージャに配列を渡す方法
たとえば、従業員のリストがあります。このリストをテーブルとして使い、それを別のテーブルと結合したいのです。しかし、従業員のリストはC#からパラメータとして渡されるべきです。
まず、データベースに次の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を使用している場合でも、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が表すべきコンテキストを記述し、型名は一般的であるべきです。
私の経験に基づいて、従業員IDから区切られた式を作成することによって、この問題に対するトリッキーでいい解決策があります。 ';123;434;365;'
のような文字列式のみを作成してください。ここで123
、434
、および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
ストアドプロシージャにテーブル値パラメーターを使用します。
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();
}
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
配列のsizeやcomplexityなど、コンテキストは常に重要です。小規模から中規模のリストの場合、いくつかの明確化を行う必要がありますが、ここに投稿されたいくつかの回答は問題ありません。
text()
XML関数を使用しているため、最高です。XMLを使用したリストの分割に関する詳細(パフォーマンス分析など)については、チェックアウト 「XMLを使用してSQL Serverのパラメーターとしてリストを渡す」 Phil Factor。DataTable
に格納するということは、元のコレクションからコピーされるときにデータをメモリに複製することを意味します。したがって、TVPを渡すDataTable
メソッドの使用は、より大きなデータセットに対してうまく機能しません(つまり、適切にスケーリングされません)。DataTable
TVPメソッドと同様に、XMLは、XMLドキュメントのオーバーヘッドをさらに考慮する必要があるため、メモリ内のデータサイズが2倍を超えるため、うまくスケーリングできません。以上のことをすべて踏まえると、使用しているデータが大きい場合、またはそれほど大きくはないが常に成長している場合は、IEnumerable
TVPメソッドがデータをSQL Serverにストリーミングするときに最適です(DataTable
method)、ただし、メモリ内のコレクションの複製は必要ありません(他のメソッドとは異なります)。この回答では、SQLおよびC#コードの例を投稿しました。
SQL Serverでは配列はサポートされていませんが、コレクションをストアドプロシージャに渡す方法はいくつかあります。
下のリンクはあなたを助けるかもしれません
これはあなたを助けるでしょう。 :)次の手順に従ってください、
コピー次のコードをそのまま貼り付けます。文字列を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
次のストアドプロシージャを作成します。
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
これを実行しますSP exec sp_DeleteId '1,2,3,12'
を使用して、これは削除したいIdの文字列です。
配列を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 ;
これにより複数の行が削除されます。
私は、この 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
楽しんでくれると良いです
これを理解するのに長い時間がかかったので、誰かがそれを必要とする場合に備えて...
これは、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)