配列をSQL Serverストアドプロシージャに渡す方法は?
たとえば、従業員のリストがあります。このリストをテーブルとして使用し、別のテーブルと結合したい。ただし、従業員のリストはC#からパラメーターとして渡す必要があります。
配列を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が表すコンテキストを記述し、型名は総称でなければなりません。
SELECT [colA] FROM [MyTable] WHERE [Id] IN (SELECT [Id] FROM @ListOfIds)
。
私の経験に基づいて、employeeIDsから区切られた式を作成することにより、この問題に対するトリッキーで素晴らしい解決策があります。あなただけのような文字列式を作成する必要があります';123;434;365;'
する- 123
、434
および365
いくつかのemployeeIDsです。以下のプロシージャを呼び出してこの式を渡すことにより、目的のレコードをフェッチできます。「別のテーブル」をこのクエリに簡単に結合できます。このソリューションは、SQLサーバーのすべてのバージョンに適しています。また、テーブル変数や一時テーブルを使用する場合と比較して、非常に高速で最適化されたソリューションです。
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スキーマとして定義しましたが、必ずしもそうする必要はありません。これを使用することをお勧めします。ここに、常にlongを含む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
配列のサイズや複雑さなど、コンテキストは常に重要です。中小規模のリストの場合、ここに掲載されている回答のいくつかは問題ありませんが、いくつかの明確化が必要です。
text()
XML関数:XMLを使用してリストを分割する方法の詳細(つまり、パフォーマンス分析)については、フィルファクターの「XMLを使用してSQL Serverのパラメーターとしてリストを渡す」を参照してください。DataTable
データを格納するとは、元のコレクションからコピーされたデータをメモリに複製することを意味します。したがってDataTable
、TVPを渡す方法を使用しても、より大きなデータセットではうまく機能しません(つまり、適切にスケーリングされません)。DataTable
TVPメソッドと同様に、XMLは、XML文書のオーバーヘッドを追加で考慮する必要があるため、メモリ内のデータサイズを2倍以上に拡張できないため、適切にスケーリングされません。以上すべてを踏まえて、使用しているデータが大きいか、それほど大きくないが、常に増加している場合IEnumerable
、データをSQL ServerにストリーミングするTVPメソッドが(DataTable
メソッドと同様に)最良の選択ですが、(他のメソッドとは異なり)メモリ内のコレクションの複製が必要です。SQLとC#コードの例をこの回答に投稿しました:
SQLサーバーでは配列はサポートされていませんが、コレクションをストアドプロシージャに渡す方法はいくつかあります。
以下のリンクはあなたを助けるかもしれません
私は、このlinKが見つかるまで、新しいテーブルタイプを作成する手間をかけずに配列を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
楽しんでください
@s
がCSVの場合、単純にそれを分割する方が高速です(つまり、INSERT INTO ... SELECT FROM SplitFunction)。XMLへの変換はCLRよりも遅く、属性ベースのXMLはとにかく高速です。そして、これは単純なリストですが、XMLまたはTVPで渡すと、複雑な配列も処理できます。単純な1回限りの操作を回避することで何が得られるのかわからないCREATE TYPE ... AS TABLE
。
これはあなたを助けます。:)次の手順に従います。
次のコードをそのままコピーして貼り付け、文字列を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 ;
これにより、複数の行が削除されます。
これを理解するのに長い時間がかかったので、誰かがそれを必要とする場合に備えて...
これは、Aaronの回答のSQL 2005メソッドに基づいており、彼のSplitInts関数を使用しています(常にコンマを使用するため、delimパラメータを削除しました)。SQL 2008を使用していますが、型指定されたデータセット(XSD、TableAdapters)で機能するものが必要でしたが、文字列パラメーターがそれらで機能することはわかっています。
私は彼の関数を "where in(1,2,3)"型の節で動作させようとしており、簡単な方法では運がありませんでした。そこで、まず一時テーブルを作成し、次に「どこに」の代わりに内部結合を行いました。これが私の使用例です。私の場合、特定の成分を含まないレシピのリストを取得したいと思います。
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)
他の人が前述したように、これを行う1つの方法は、配列を文字列に変換してから、SQL Server内で文字列を分割することです。
SQL Server 2016以降、文字列を分割する組み込みの方法があります。
STRING_SPLIT()
一時テーブル(または実際のテーブル)に挿入できる行のセットを返します。
DECLARE @str varchar(200)
SET @str = "123;456;789;246;22;33;44;55;66"
SELECT value FROM STRING_SPLIT(@str, ';')
次のようになります:
値 ----- 123 456 789 246 22 33 44 55 66
もっと凝ったものにしたい場合:
DECLARE @tt TABLE (
thenumber int
)
DECLARE @str varchar(200)
SET @str = "123;456;789;246;22;33;44;55;66"
INSERT INTO @tt
SELECT value FROM STRING_SPLIT(@str, ';')
SELECT * FROM @tt
ORDER BY thenumber
上記と同じ結果が得られますが(列名が「thenumber」の場合を除く)、ソートされています。他のテーブルと同じようにテーブル変数を使用できるため、必要に応じてDB内の他のテーブルと簡単に結合できます。
STRING_SPLIT()
関数が認識されるためには、SQL Serverのインストールが互換性レベル130以上である必要があることに注意してください。次のクエリで互換性レベルを確認できます。
SELECT compatibility_level
FROM sys.databases WHERE name = 'yourdatabasename';
ほとんどの言語(C#を含む)には、配列から文字列を作成するために使用できる「結合」関数があります。
int[] myarray = {22, 33, 44};
string sqlparam = string.Join(";", myarray);
次にsqlparam
、パラメーターとして上記のストアドプロシージャに渡します。
CREATE TYPE dumyTable
AS TABLE
(
RateCodeId int,
RateLowerRange int,
RateHigherRange int,
RateRangeValue int
);
GO
CREATE PROCEDURE spInsertRateRanges
@dt AS dumyTable READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT tblRateCodeRange(RateCodeId,RateLowerRange,RateHigherRange,RateRangeValue)
SELECT *
FROM @dt
END