Cách truyền một mảng vào thủ tục lưu trữ SQL Server


293

Làm cách nào để chuyển một mảng vào thủ tục lưu trữ SQL Server?

Ví dụ, tôi có một danh sách nhân viên. Tôi muốn sử dụng danh sách này như một bảng và tham gia nó với một bảng khác. Nhưng danh sách nhân viên nên được thông qua dưới dạng tham số từ C #.


thưa ngài hy vọng liên kết này sẽ giúp bạn Chuyển một danh sách / mảng tới SQL Server SP
patrick choi

Câu trả lời:


437

SQL Server 2008 (hoặc mới hơn)

Đầu tiên, trong cơ sở dữ liệu của bạn, tạo hai đối tượng sau:

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

Bây giờ trong mã C # của bạn:

// 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
}

Máy chủ SQL 2005

Nếu bạn đang sử dụng SQL Server 2005, tôi vẫn sẽ đề xuất một hàm phân tách trên XML. Đầu tiên, tạo một hàm:

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

Bây giờ thủ tục lưu trữ của bạn chỉ có thể là:

CREATE PROCEDURE dbo.DoSomethingWithEmployees
  @List VARCHAR(MAX)
AS
BEGIN
  SET NOCOUNT ON;

  SELECT EmployeeID = Item FROM dbo.SplitInts(@List, ','); 
END
GO

Và trong mã C # của bạn, bạn chỉ cần vượt qua danh sách là '1,2,3,12'...


Tôi thấy phương pháp chuyển qua các tham số có giá trị của bảng đơn giản hóa khả năng duy trì của một giải pháp sử dụng nó và thường có hiệu suất tăng so với các triển khai khác bao gồm tách XML và tách chuỗi.

Các đầu vào được xác định rõ ràng (không ai phải đoán xem dấu phân cách là dấu phẩy hay dấu chấm phẩy) và chúng tôi không phụ thuộc vào các hàm xử lý khác không rõ ràng mà không kiểm tra mã cho quy trình được lưu trữ.

So với các giải pháp liên quan đến lược đồ XML do người dùng xác định thay vì UDT, điều này bao gồm một số bước tương tự nhưng theo kinh nghiệm của tôi là mã đơn giản hơn nhiều để quản lý, duy trì và đọc.

Trong nhiều giải pháp, bạn có thể chỉ cần một hoặc một vài trong số các UDT này (Loại do người dùng xác định) mà bạn sử dụng lại cho nhiều quy trình được lưu trữ. Như với ví dụ này, yêu cầu chung là thông qua danh sách các con trỏ ID, tên hàm mô tả bối cảnh mà các Id đó sẽ đại diện, tên loại nên chung chung.


3
Tôi thích ý tưởng tham số bảng - không bao giờ nghĩ về điều đó - chúc mừng. Đối với những gì đáng giá thì dấu phân cách cần chuyển vào lệnh gọi hàm SplitInts ().
Phim truyền hình

Làm cách nào tôi có thể sử dụng tham số bảng nếu chỉ có quyền truy cập vào chuỗi được phân tách bằng dấu phẩy
bdwain

@bdwain sẽ đánh bại mục đích - bạn phải sử dụng chức năng phân tách để chia nó thành các hàng để đưa vào TVP. Phá vỡ nó trong mã ứng dụng của bạn.
Aaron Bertrand

1
@AaronBertrand cảm ơn bạn đã phản hồi, tôi thực sự chỉ cần tìm ra nó. Tôi phải sử dụng một lựa chọn phụ trong ngoặc : SELECT [colA] FROM [MyTable] WHERE [Id] IN (SELECT [Id] FROM @ListOfIds).
JaKXz

3
@ th1rdey3 Chúng là tùy chọn ngầm. stackoverflow.com/a/18926590/61305
Aaron Bertrand

44

Dựa trên kinh nghiệm của tôi, bằng cách tạo một biểu thức được phân tách từ các workerID, có một giải pháp hay và hữu ích cho vấn đề này. Bạn chỉ nên tạo một biểu thức chuỗi như ';123;434;365;'trong đó 123, 434365một số employeeIDs. Bằng cách gọi thủ tục dưới đây và chuyển biểu thức này cho nó, bạn có thể lấy các bản ghi mong muốn của mình. Dễ dàng bạn có thể tham gia "bảng khác" vào truy vấn này. Giải pháp này phù hợp trong tất cả các phiên bản của máy chủ SQL. Ngoài ra, so với việc sử dụng biến bảng hoặc bảng tạm thời, đó là giải pháp tối ưu và nhanh hơn.

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

Đẹp! Tôi thực sự thích cách tiếp cận này, nơi tôi đang lọc trên các phím int! +1
MDV2000

@ MDV2000 cảm ơn :) Trên các khóa chuỗi, điều này cũng có hiệu suất tốt vì không truyền các cột int thành varchar ...
Hamed Nazaktabar

Tôi đến trễ trò chơi, nhưng điều này rất thông minh! Hoạt động tuyệt vời cho vấn đề của tôi.
dùng441058

Thật đáng kinh ngạc! Tôi chắc chắn sẽ sử dụng nó, cảm ơn bạn
Omar Ruder

26

Sử dụng một tham số có giá trị bảng cho thủ tục được lưu trữ của bạn.

Khi bạn chuyển nó từ C #, bạn sẽ thêm tham số với kiểu dữ liệu của SqlDb.Sturationured.

Xem tại đây: http://msdn.microsoft.com/en-us/l Library / bb675163.aspx

Thí dụ:

// 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();
}

17

Bạn cần truyền nó dưới dạng tham số XML.

Chỉnh sửa: mã nhanh từ dự án của tôi để cung cấp cho bạn một ý tưởng:

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)

Sau đó, bạn có thể sử dụng bảng tạm thời để tham gia với các bảng của bạn. Chúng tôi đã định nghĩa mảngOfUlong là một lược đồ XML tích hợp để duy trì tính toàn vẹn dữ liệu, nhưng bạn không phải làm điều đó. Tôi khuyên bạn nên sử dụng nó vì vậy đây là một mã nhanh để đảm bảo bạn luôn nhận được một XML với thời gian dài.

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

Tôi nghĩ rằng đó là một ý tưởng tồi để sử dụng các biến bảng khi có nhiều hàng? Thay vào đó, có nên sử dụng bảng tạm thời (#table) không?
ganders

@ganders: Tôi sẽ nói ngược lại.
abatishchev

14

Bối cảnh luôn luôn quan trọng, chẳng hạn như kích thướcđộ phức tạp của mảng. Đối với các danh sách từ nhỏ đến trung bình, một số câu trả lời được đăng ở đây là tốt, mặc dù một số làm rõ nên được thực hiện:

  • Để tách danh sách được phân tách, bộ chia dựa trên SQLCLR là nhanh nhất. Có rất nhiều ví dụ xung quanh nếu bạn muốn tự viết hoặc bạn chỉ có thể tải xuống thư viện SQL # miễn phí của các hàm CLR (mà tôi đã viết, nhưng hàm String_Split và nhiều thứ khác, hoàn toàn miễn phí).
  • Việc tách các mảng dựa trên XML thể nhanh chóng, nhưng bạn cần sử dụng XML dựa trên thuộc tính, không phải XML dựa trên thành phần (là loại duy nhất được hiển thị trong các câu trả lời ở đây, mặc dù ví dụ XML của @ AaronBertrand là tốt nhất vì mã của anh ấy đang sử dụng text()Hàm XML. Để biết thêm thông tin (tức là phân tích hiệu suất) về việc sử dụng XML để phân chia danh sách, hãy xem "Sử dụng XML để chuyển danh sách dưới dạng tham số trong SQL Server" của Phil Factor.
  • Sử dụng TVP là rất tốt (giả sử bạn đang sử dụng ít nhất SQL Server 2008 hoặc mới hơn) khi dữ liệu được truyền đến Proc và hiển thị được phân tích cú pháp trước và được gõ mạnh dưới dạng biến bảng. TUY NHIÊN, trong hầu hết các trường hợp, lưu trữ tất cả dữ liệu DataTablecó nghĩa là sao chép dữ liệu trong bộ nhớ khi nó được sao chép từ bộ sưu tập gốc. Do đó, sử dụng DataTablephương thức truyền trong TVP không hoạt động tốt đối với các tập dữ liệu lớn hơn (nghĩa là không mở rộng tốt).
  • XML, không giống như các danh sách Ints hoặc String được phân tách đơn giản, có thể xử lý nhiều hơn các mảng một chiều, giống như TVP. Nhưng cũng giống như DataTablephương pháp TVP, XML không mở rộng tốt hơn gấp đôi số liệu dữ liệu trong bộ nhớ vì nó cần tính toán thêm cho chi phí chung của tài liệu XML.

Với tất cả những gì đã nói, NẾU dữ liệu bạn đang sử dụng lớn hoặc chưa lớn lắm nhưng vẫn phát triển ổn định, thì IEnumerablephương thức TVP là lựa chọn tốt nhất vì nó truyền dữ liệu đến SQL Server (như DataTablephương thức), nhưng NHƯNG không yêu cầu bất kỳ sự trùng lặp của bộ sưu tập trong bộ nhớ (không giống như bất kỳ phương thức nào khác). Tôi đã đăng một ví dụ về mã SQL và C # trong câu trả lời này:

Chuyển từ điển sang thủ tục lưu trữ T-SQL


6

Không có hỗ trợ cho mảng trong máy chủ sql nhưng có một số cách mà bạn có thể chuyển bộ sưu tập đến một Proc được lưu trữ.

  1. Bằng cách sử dụng dữ liệu
  2. Bằng cách sử dụng XML.Try chuyển đổi bộ sưu tập của bạn ở định dạng xml và sau đó chuyển nó làm đầu vào cho một thủ tục được lưu trữ

Liên kết dưới đây có thể giúp bạn

chuyển bộ sưu tập đến một thủ tục được lưu trữ


5

Tôi đã tìm kiếm tất cả các ví dụ và câu trả lời về cách chuyển bất kỳ mảng nào sang máy chủ sql mà không gặp rắc rối khi tạo loại Bảng mới, cho đến khi tôi tìm thấy linK này , dưới đây là cách tôi áp dụng nó cho dự án của mình:

- Đoạn mã sau sẽ lấy Mảng làm Thông số và chèn các giá trị của --array vào bảng khác

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 

Hy vọng la bạn se thich no


2
@zaitsman: SẠCH không có nghĩa là tốt nhất hoặc phù hợp nhất. Người ta thường từ bỏ tính linh hoạt và / hoặc "độ phức tạp" phù hợp và / hoặc hiệu suất để có được mã "sạch". Câu trả lời này ở đây là "ok" nhưng chỉ dành cho các tập dữ liệu nhỏ. Nếu mảng đến @slà CSV thì sẽ nhanh hơn khi phân tách nó (tức là XÁC NHẬN VÀO ... CHỌN TỪ Chia tách). Chuyển đổi sang XML chậm hơn CLR và dù sao thì XML dựa trên thuộc tính cũng nhanh hơn nhiều. Và đây là một danh sách đơn giản chưa có trong XML hoặc TVP cũng có thể xử lý các mảng phức tạp. Không chắc chắn những gì đạt được bằng cách tránh một lần đơn giản CREATE TYPE ... AS TABLE.
Solomon Rutzky

5

Điều này sẽ giúp bạn. :) Thực hiện theo các bước tiếp theo,

  1. Mở Trình thiết kế truy vấn
  2. Sao chép Dán mã sau đây, nó sẽ tạo Hàm chuyển đổi Chuỗi thành 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. Tạo thủ tục lưu sẵn sau đây

     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. Thực thi SP này Sử dụng exec sp_DeleteId '1,2,3,12'đây là một chuỗi Id mà bạn muốn xóa,

  5. Bạn chuyển đổi mảng của mình thành chuỗi trong C # và truyền nó dưới dạng tham số Thủ tục lưu trữ

    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 ;

Điều này sẽ xóa nhiều hàng, Tất cả tốt nhất


Tôi đã sử dụng chức năng phân tích cú pháp riêng biệt bằng dấu phẩy này, nó sẽ hoạt động với tập dữ liệu nhỏ, nếu bạn kiểm tra kế hoạch thực hiện của nó, nó sẽ gây ra sự cố trên tập dữ liệu lớn và trong đó bạn phải liệt kê nhiều danh sách csv trong thủ tục được lưu trữ
Saboor Awan

2

Tôi đã mất một thời gian dài để tìm ra điều này, vì vậy trong trường hợp bất cứ ai cần nó ...

Điều này dựa trên phương pháp SQL 2005 trong câu trả lời của Aaron và sử dụng hàm SplitInts của anh ấy (Tôi vừa xóa thông số delim vì tôi sẽ luôn sử dụng dấu phẩy). Tôi đang sử dụng SQL 2008 nhưng tôi muốn một cái gì đó hoạt động với các bộ dữ liệu được gõ (XSD, TableAd chương) và tôi biết các tham số chuỗi hoạt động với các bộ dữ liệu đó.

Tôi đã cố gắng để chức năng của anh ta hoạt động theo mệnh đề "where in (1,2,3)" và không gặp may mắn theo cách đơn giản. Vì vậy, tôi đã tạo một bảng tạm thời trước, và sau đó thực hiện một phép nối bên trong thay vì "where in". Dưới đây là cách sử dụng ví dụ của tôi, trong trường hợp của tôi, tôi muốn có một danh sách các công thức nấu ăn không chứa các thành phần nhất định:

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)

Nói chung, tốt nhất là không tham gia vào Biến bảng, mà thay vào đó là Bảng tạm thời. Theo mặc định, các biến bảng chỉ xuất hiện một hàng, mặc dù có một hoặc hai mẹo xung quanh đó (xem bài viết xuất sắc và chi tiết của @ AaronBertrand: sqlperformance.com/2014/06/t-sql-queries/ tựa ).
Solomon Rutzky

1

Như những người khác đã lưu ý ở trên, một cách để làm điều này là chuyển đổi mảng của bạn thành một chuỗi và sau đó phân tách chuỗi bên trong SQL Server.

Kể từ SQL Server 2016, có một cách tích hợp để phân tách các chuỗi được gọi là

STRINGinksLIT ()

Nó trả về một tập hợp các hàng mà bạn có thể chèn vào bảng tạm thời (hoặc bảng thực).

DECLARE @str varchar(200)
SET @str = "123;456;789;246;22;33;44;55;66"
SELECT value FROM STRING_SPLIT(@str, ';')

sẽ mang lại:

giá trị
-----
  123
  456
  789
  246
   22
   33
   44
   55
   66

Nếu bạn muốn nhận fancier:

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

sẽ cung cấp cho bạn các kết quả tương tự như trên (ngoại trừ tên cột là "thenumber"), nhưng được sắp xếp. Bạn có thể sử dụng biến bảng như bất kỳ bảng nào khác, vì vậy bạn có thể dễ dàng nối nó với các bảng khác trong DB nếu muốn.

Lưu ý rằng cài đặt SQL Server của bạn phải ở mức tương thích 130 trở lên để STRING_SPLIT()chức năng được nhận dạng. Bạn có thể kiểm tra mức độ tương thích của mình với truy vấn sau:

SELECT compatibility_level
FROM sys.databases WHERE name = 'yourdatabasename';

Hầu hết các ngôn ngữ (bao gồm C #) có chức năng "tham gia" mà bạn có thể sử dụng để tạo một chuỗi từ một mảng.

int[] myarray = {22, 33, 44};
string sqlparam = string.Join(";", myarray);

Sau đó, bạn chuyển sqlparamnhư tham số của bạn cho các thủ tục được lưu trữ ở trên.


0
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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.