Как передать массив в хранимую процедуру SQL Server

Как передать массив в хранимую процедуру SQL Server?

например, у меня есть список сотрудников. Я хочу использовать этот список в качестве таблицы и присоединить его к другой таблице. Но список сотрудников должен быть передан как параметр из C#.

9 ответов


SQL Server 2008 (или новее)

во-первых, в базе данных создайте следующие два объекта:

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

если вы используете 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 вместо UDTs, это включает в себя аналогичное количество шагов, но, по моему опыту, гораздо проще управлять, поддерживать и читать код.

во многих решениях вам может понадобиться только один или несколько из этих UDTs (определяемых пользователем типов), которые вы повторно используете для многих хранимых процедур. Как и в этом примере, общим требованием является прохождение через список указателей ID, имя функции описывает, что контекст эти идентификаторы должны представлять, имя типа должно быть общим.


основываясь на моем опыте, создавая разделенное выражение из employeeIDs, есть сложное и хорошее решение этой проблемы. Вы должны создать только строковое выражение, например ';123;434;365;' in-which 123, 434 и 365 некоторые employeeIDs. Вызывая приведенную ниже процедуру и передавая ей это выражение, Вы можете получить нужные записи. Легко вы можете присоединиться к" другой таблице " в этом запросе. Это решение подходит для всех версий SQL server. Кроме того, в сравнение с использованием переменной таблицы или таблицы 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.Структурированный.

смотрите здесь: 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.

Edit: быстрый код из моего проекта, чтобы дать вам представление:

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

контекст всегда важен, например в размере и сложности массива. Для небольших и средних списков некоторые ответы, опубликованные здесь, просто прекрасны, хотя некоторые разъяснения должны быть сделаны:

  • для разделения списка с разделителями самый быстрый разделитель на основе SQLCLR. Существует множество примеров вокруг, если вы хотите написать свой собственный, или вы можете просто скачать бесплатно SQL# библиотека функций CLR (которые я написал, но функция String_Split, и многие другие, абсолютно бесплатны).
  • разделение массивов на основе XML can быть быстрым, но вам нужно использовать XML на основе атрибутов, а не на основе элементов XML (который является единственным типом, показанным в ответах здесь, хотя пример XML @AaronBertrand является лучшим, поскольку его код использует text() функция XML. Для получения дополнительной информации (т. е. анализа производительности) об использовании XML для разделения списков, проверьте " использование XML для передачи списков в качестве параметров в SQL Сервер" Фил фактор.
  • использование TVPs отлично (предполагая, что вы используете по крайней мере SQL Server 2008 или новее), поскольку данные передаются в proc и отображаются предварительно проанализированными и строго типизированными как табличная переменная. Однако в большинстве случаев хранение всех данных в DataTable означает дублирование данных в памяти по мере их копирования из исходной коллекции. Следовательно, используя DataTable метод передачи в TVPs не работает хорошо для больших наборов данных (т. е. не масштабируется ну.)
  • XML, в отличие от простых списков с разделителями int или строк, может обрабатывать более чем одномерные массивы, как и TVPs. Но и так же, как DataTable TVP метод, XML не масштабируется хорошо, так как он более чем удваивает datasize в памяти, поскольку ему необходимо дополнительно учитывать накладные расходы XML-документа.

со всем сказанным, если данные, которые вы используете, большие или не очень большие, но постоянно растут, то IEnumerable метод TVP лучший выбор, поскольку он передает данные на SQL Server (например,DataTable метод), но не требует дублирования коллекции в памяти (в отличие от любого другого метода). Я опубликовал пример кода SQL и C# в этом ответе:

передать словарь хранимой процедуре T-SQL


в sql server Нет поддержки array, но есть несколько способов передачи коллекции в сохраненный proc .

  1. С помощью datatable
  2. С помощью XML.Попробуйте преобразовать свою коллекцию в формат xml, а затем передать ее как вход в хранимую процедуру

ссылка ниже может помочь вам

передача коллекции в хранимую процедуру


Я искал все примеры и ответы о том,как передать любой массив на sql server без хлопот создания нового типа таблицы, пока не нашел это ссылке, ниже, как я применил его к своему проекту:

--следующий код собирается получить массив в качестве параметра и вставить значения этого -- массив в другую таблицу

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 

Надеюсь, вам понравится


Это поможет вам. :) Выполните следующие действия,

  1. откройте конструктор запросов
  2. копировать вставьте следующий код, как он есть, он создаст функцию, которая преобразует строку в 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
    
  3. создать следующую хранимую процедуру

     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
    
  4. выполните этот SP, используя exec sp_DeleteId '1,2,3,12' это строка идентификаторов, которые вы хотите удалить,

  5. вы конвертируете массив в строка в 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 param, так как я всегда буду использовать запятые). Я использую SQL 2008, но мне нужно что-то, что работает с типизированными наборами данных (XSD, TableAdapters), и я знаю, что с ними работают строковые параметры.

Я пытался заставить его функцию работать в предложении типа" where in (1,2,3) " и не имея удачи на прямом пути. Поэтому я сначала создал временную таблицу, а затем сделал внутреннее соединение вместо "where in". Вот мой пример использования в моем случае я хотел получить список рецептов, которые не содержат определенных ингредиентов:

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)