วิธีการส่งอาร์เรย์ไปยัง SQL Server ที่เก็บไว้ในกระบวนงาน?
ตัวอย่างเช่นฉันมีรายชื่อพนักงาน ฉันต้องการใช้รายการนี้เป็นตารางและเข้าร่วมกับตารางอื่น แต่รายชื่อพนักงานควรถูกส่งผ่านเป็นพารามิเตอร์จาก C #
วิธีการส่งอาร์เรย์ไปยัง SQL Server ที่เก็บไว้ในกระบวนงาน?
ตัวอย่างเช่นฉันมีรายชื่อพนักงาน ฉันต้องการใช้รายการนี้เป็นตารางและเข้าร่วมกับตารางอื่น แต่รายชื่อพนักงานควรถูกส่งผ่านเป็นพารามิเตอร์จาก C #
คำตอบ:
ก่อนอื่นในฐานข้อมูลของคุณให้สร้างวัตถุสองรายการต่อไปนี้
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 และการแยกสตริง
อินพุตถูกกำหนดไว้อย่างชัดเจน (ไม่มีใครเดาได้ว่าตัวคั่นเป็นเครื่องหมายจุลภาคหรือเซมิโคลอน) และเราไม่มีการพึ่งพาฟังก์ชันการประมวลผลอื่น ๆ ที่ไม่ชัดเจนโดยไม่ตรวจสอบรหัสสำหรับกระบวนงานที่เก็บไว้
เปรียบเทียบกับโซลูชันที่เกี่ยวข้องกับ XML schema ที่ผู้ใช้กำหนดแทน UDT สิ่งนี้เกี่ยวข้องกับจำนวนขั้นตอนที่คล้ายกัน แต่ในประสบการณ์ของฉันมันเป็นรหัสที่ง่ายกว่าในการจัดการบำรุงรักษาและอ่าน
ในโซลูชันจำนวนมากคุณอาจต้องการ UDT เหล่านี้เพียงหนึ่งหรือสองสามอย่าง (ประเภทที่ผู้ใช้กำหนด) ที่คุณใช้ซ้ำสำหรับกระบวนงานที่เก็บไว้จำนวนมาก เช่นเดียวกับตัวอย่างนี้ข้อกำหนดทั่วไปคือการส่งผ่านรายการตัวชี้ ID ชื่อฟังก์ชันอธิบายว่าบริบทใดที่รหัสเหล่านั้นควรเป็นตัวแทนชื่อประเภทควรเป็นแบบทั่วไป
SELECT [colA] FROM [MyTable] WHERE [Id] IN (SELECT [Id] FROM @ListOfIds)
ฉันต้องใช้ย่อยเลือกในวงเล็บ:
จากประสบการณ์ของฉันโดยการสร้างนิพจน์ที่คั่นด้วยจาก employeeID มีวิธีแก้ไขที่ดีและยุ่งยากสำหรับปัญหานี้ คุณควรสร้างนิพจน์สตริงเช่น';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 schema ในตัวเพื่อรักษาความถูกต้องของข้อมูล แต่คุณไม่จำเป็นต้องทำเช่นนั้น ฉันขอแนะนำให้ใช้เพื่อให้นี่เป็นรหัสย่อของเพื่อให้แน่ใจว่าคุณจะได้รับ 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"โดย Phil FactorDataTable
หมายถึงการทำซ้ำข้อมูลในหน่วยความจำเมื่อมีการคัดลอกจากคอลเลกชันดั้งเดิม ดังนั้นการใช้DataTable
วิธีการส่งผ่าน TVPs จึงใช้งานไม่ได้กับชุดข้อมูลขนาดใหญ่DataTable
วิธี TVP XML มีขนาดไม่ดีเนื่องจากมีขนาดข้อมูลเป็นสองเท่าในหน่วยความจำเนื่องจากต้องการบัญชีเพิ่มเติมสำหรับค่าใช้จ่ายของเอกสาร XMLจากข้อมูลทั้งหมดที่กล่าวมาหากข้อมูลที่คุณใช้มีขนาดใหญ่หรือไม่ใหญ่มาก แต่เติบโตขึ้นเรื่อย ๆIEnumerable
วิธีการ TVP เป็นตัวเลือกที่ดีที่สุดเพราะจะส่งข้อมูลไปยัง SQL Server (เช่นเดียวกับDataTable
วิธีการ) แต่ไม่ได้ ต้องมีการทำสำเนาของคอลเลกชันในหน่วยความจำ (ซึ่งแตกต่างจากวิธีอื่นใด) ฉันโพสต์ตัวอย่างของ SQL และรหัส C # ในคำตอบนี้:
ไม่มีการรองรับอาเรย์ในเซิร์ฟเวอร์ sql แต่มีหลายวิธีที่คุณสามารถส่งคอลเลกชันไปยัง proc ที่เก็บไว้ได้
ลิงค์ด้านล่างอาจช่วยคุณได้
ฉันได้ค้นหาตัวอย่างและคำตอบทั้งหมดเกี่ยวกับวิธีส่งอาร์เรย์ไปยังเซิร์ฟเวอร์ sql โดยไม่ต้องวุ่นวายกับการสร้างประเภทตารางใหม่จนกระทั่งฉันพบlinKนี้ด้านล่างคือวิธีที่ฉันใช้กับโครงการของฉัน:
- รหัสต่อไปนี้จะได้รับ Array เป็นพารามิเตอร์และใส่ค่าของที่ - อธิบายลงในตารางอื่น
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 จาก SplitFunction) การแปลงเป็น XML ช้ากว่า CLR และ XML ที่อิงตามคุณลักษณะนั้นเร็วกว่ามาก และนี่เป็นรายการง่าย ๆ ที่ส่งผ่าน XML หรือ TVP ก็สามารถจัดการอาร์เรย์ที่ซับซ้อนได้ ไม่แน่ใจว่าสิ่งที่ได้รับจากการหลีกเลี่ยงสิ่งที่ง่าย ๆ เพียงครั้ง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 # และส่งผ่านเป็นพารามิเตอร์ Stored Procedure
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 ;
สิ่งนี้จะลบหลายแถวทั้งหมดที่ดีที่สุด
ฉันใช้เวลานานกว่าจะคิดออกดังนั้นในกรณีที่ใครต้องการมัน ...
นี่เป็นไปตามวิธี SQL 2005 ในคำตอบของ Aaron และใช้ฟังก์ชัน SplitInts ของเขา (ฉันเพิ่งลบ delim param เนื่องจากฉันจะใช้เครื่องหมายจุลภาคเสมอ) ฉันใช้ SQL 2008 แต่ฉันต้องการสิ่งที่ทำงานกับชุดข้อมูลที่พิมพ์ (XSD, TableAdapters) และฉันรู้ว่าสตริง params ทำงานกับสิ่งเหล่านั้นได้
ฉันพยายามทำให้งานของเขาทำงานในประโยค "ประเภทที่ (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)
ตามที่คนอื่น ๆ ได้ระบุไว้ข้างต้นวิธีหนึ่งในการทำเช่นนี้คือการแปลงอาร์เรย์ของคุณเป็นสตริงแล้วแยกสตริงภายใน 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 ได้อย่างง่ายดายหากคุณต้องการ
โปรดทราบว่าการติดตั้ง SQL Server ของคุณจะต้องอยู่ในระดับความเข้ากันได้ 130 หรือสูงกว่าเพื่อให้STRING_SPLIT()
ฟังก์ชั่นได้รับการยอมรับ คุณสามารถตรวจสอบระดับความเข้ากันได้ของคุณด้วยแบบสอบถามต่อไปนี้:
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