Як передати масив у збережену процедуру 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