Как передать массив в хранимую процедуру 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)
.
Исходя из моего опыта, путем создания выражения с разделителями из employeeID, есть хитрое и хорошее решение для этой проблемы. Вы должны только создать строковое выражение, например, ';123;434;365;'
in-which 123
, 434
и 365
это несколько employeeID. Вызвав приведенную ниже процедуру и передав ей это выражение, вы можете получить нужные записи. Вы можете легко присоединить «другую таблицу» к этому запросу. Это решение подходит во всех версиях 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-схему для поддержания целостности данных, но вам не нужно этого делать. Я бы порекомендовал использовать его, так что вот быстрый код, чтобы убедиться, что вы всегда получаете 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 Factor.DataTable
означает дублирование данных в памяти, когда они копируются из оригинальной коллекции. Следовательно, использование DataTable
метода передачи в TVP не работает хорошо для больших наборов данных (т.е. не хорошо масштабируется).DataTable
метод TVP, XML не масштабируется, так как он более чем удваивает размер данных в памяти, поскольку ему необходимо дополнительно учитывать накладные расходы XML-документа.С учетом всего вышесказанного, ЕСЛИ данные, которые вы используете, являются большими или не очень большими, но все же постоянно растут, то IEnumerable
метод TVP является лучшим выбором, так как он передает данные на SQL Server (как DataTable
метод), НО не делает требуется любое дублирование коллекции в памяти (в отличие от любого другого метода). Я разместил пример кода SQL и C # в этом ответе:
В sql server нет поддержки массива, но есть несколько способов передать коллекцию в сохраненный процесс.
Ссылка ниже может помочь вам
Я искал все примеры и ответы о том, как передать любой массив на сервер sql без хлопот создания нового типа таблицы, пока не нашел этот linK , ниже показано , как я применил его к своему проекту:
Следующий код получит массив в качестве параметра и вставит значения этого массива в другую таблицу.
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 также может обрабатывать сложные массивы. Не уверен, что получается, избегая простого, одноразового 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'
эту строку идентификаторов, которые вы хотите удалить,
Вы конвертируете свой массив в строку в 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 (я просто удалил параметр delim, так как я всегда буду использовать запятые). Я использую 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
даст вам те же результаты, что и выше (за исключением того, что имя столбца «thenumber»), но отсортированы. Вы можете использовать переменную таблицы как любую другую таблицу, так что вы можете легко объединить ее с другими таблицами в БД, если хотите.
Обратите внимание, что ваша установка 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