Як передати масив у збережену процедуру 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 замість UDT, це включає аналогічну кількість кроків, але, на мій досвід, набагато простіший код для управління, обслуговування та читання.
У багатьох рішеннях вам може знадобитися лише один або кілька цих UDT (типів, визначених користувачем), які ви повторно використовуєте для багатьох збережених процедур. Як і в цьому прикладі, загальною вимогою є проходження списку покажчиків ідентифікаторів, ім'я функції описує, який контекст повинні представляти ці ІД, назва типу має бути загальним.
SELECT [colA] FROM [MyTable] WHERE [Id] IN (SELECT [Id] FROM @ListOfIds).
Виходячи з мого досвіду, створюючи розмежоване вираження з ідентифікаторів співробітників, є складне і приємне рішення цієї проблеми. Ви повинні створити тільки вираз рядки , як ';123;434;365;'в-якої 123, 434і 365деякі employeeIDs. Зателефонувавши до наведеної нижче процедури та передавши їй цей вираз, ви можете отримати бажані записи. Ви легко можете приєднати "іншу таблицю" до цього запиту. Це рішення підходить у всіх версіях SQL-сервера. Крім того, порівняно з використанням таблиці змінної або temp, це дуже швидке та оптимізоване рішення.
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 з longs.
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 Factor.DataTableозначає копіювання даних у пам’яті під час копіювання з оригінальної колекції. Отже, використання DataTableметоду передачі в ТВП не працює добре для більших наборів даних (тобто не масштабує).DataTableметод TVP, XML не розширює масштаб, тому що більше, ніж в два рази збільшує розмір даних у пам'яті, оскільки йому необхідно додатково враховувати накладні витрати документа XML.З урахуванням сказаного, якщо дані, які ви використовуєте, великі або не дуже великі, але постійно зростають, то IEnumerableметод TVP - найкращий вибір, оскільки він передає дані на SQL Server (як DataTableметод), але НЕ вимагають будь-якого дублювання колекції в пам'яті (на відміну від будь-якого з інших методів). У цій відповіді я розмістив приклад коду SQL та C #:
Немає підтримки для масиву на сервері sql, але є кілька способів, за допомогою яких можна передати колекцію збереженій програмі.
Нижче посилання може вам допомогти
Я шукав усі приклади та відповіді, як передавати будь-який масив на 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, то це було б швидше просто розділити це (тобто ВСТАВИТИ У ... ВИБІРАТИ Із SplitFunction). Перетворення в XML відбувається повільніше, ніж CLR, і XML на основі атрибутів у будь-якому разі набагато швидший. І це простий список, але передається в XML або TVP також може обробляти складні масиви. Не впевнений, що отримується, уникаючи простого, разового CREATE TYPE ... AS TABLE.
Це вам допоможе. :) Виконайте наступні кроки,
Скопіюйте Вставте наступний код таким, який він є, він створить функцію, яка перетворить String в 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'цей рядок, який потрібно видалити,
Ви перетворюєте масив у рядок у 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 ;Це видалить кілька рядків, Все найкраще
Мені знадобилося багато часу, щоб розібратися в цьому, тому, якщо комусь це потрібно ...
Це ґрунтується на методі SQL 2005 у відповіді Аарона та на його функції SplitInts (я щойно видалив параметр розділення, оскільки я завжди буду використовувати коми). Я використовую SQL 2008, але я хотів щось, що працює з набраними наборами даних (XSD, TableAdapters), і я знаю, що з ними працюють параметри строків.
Я намагався змусити його функцію працювати в пункті "де в (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
дасть вам ті ж результати, що і вище (за винятком назви стовпців "тоді число"), але відсортовано. Ви можете використовувати змінну таблиці, як і будь-яку іншу таблицю, так що ви можете легко приєднати її до інших таблиць у БД, якщо хочете.
Зверніть увагу, що для встановлення 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