Truyền tham số mảng cho một thủ tục được lưu trữ


53

Tôi đã có một quy trình lấy một loạt các bản ghi (1000) và vận hành chúng, và khi tôi hoàn thành, tôi cần đánh dấu một số lượng lớn chúng được xử lý. Tôi có thể chỉ ra điều này với một danh sách lớn các ID. Tôi đang cố gắng tránh mẫu "cập nhật trong một vòng lặp", vì vậy tôi muốn tìm một cách hiệu quả hơn để gửi túi ID này vào một MS SQL Server 2008 được lưu trữ.

Đề xuất số 1 - Các tham số có giá trị bảng. Tôi có thể xác định loại bảng chỉ với trường ID và gửi trong bảng có đầy đủ ID để cập nhật.

Đề xuất số 2 - Tham số XML (varchar) với OPENXML () trong phần thân.

Đề xuất số 3 - Phân tích danh sách. Tôi muốn tránh điều này, nếu có thể, vì nó có vẻ khó sử dụng và dễ bị lỗi.

Bất kỳ ưu tiên trong số này, hoặc bất kỳ ý tưởng tôi đã bỏ lỡ?


Làm thế nào bạn có được danh sách lớn ID?
Larry Coleman

Tôi đang kéo chúng xuống cùng với dữ liệu "tải trọng" thông qua một Proc được lưu trữ khác. Tuy nhiên, tôi không cần cập nhật tất cả dữ liệu đó - chỉ cần cập nhật một cờ trên các bản ghi nhất định.
D. Lambert

Câu trả lời:


42

Các bài viết hay nhất về vấn đề này là của Erland Sommarskog:

Ông bao gồm tất cả các lựa chọn và giải thích khá tốt.

Xin lỗi vì sự ngắn gọn của câu trả lời, nhưng bài viết của Erland về Mảng giống như những cuốn sách của Joe Celko về cây cối và các cách xử lý SQL khác :)


23

Có một cuộc thảo luận tuyệt vời về điều này trên StackOverflow bao gồm nhiều cách tiếp cận. Cái tôi thích cho SQL Server 2008+ là sử dụng các tham số có giá trị bảng . Đây thực chất là giải pháp của SQL Server cho vấn đề của bạn - chuyển một danh sách các giá trị cho một thủ tục được lưu trữ.

Ưu điểm của phương pháp này là:

  • thực hiện một cuộc gọi thủ tục được lưu trữ với tất cả dữ liệu của bạn được truyền dưới dạng 1 tham số
  • đầu vào bảng được cấu trúc và gõ mạnh
  • không xây dựng chuỗi / phân tích cú pháp hoặc xử lý XML
  • có thể dễ dàng sử dụng đầu vào bảng để lọc, tham gia hoặc bất cứ điều gì

Tuy nhiên, hãy lưu ý: Nếu bạn gọi một thủ tục được lưu trữ sử dụng TVP thông qua ADO.NET hoặc ODBC và hãy xem hoạt động với SQL Server Profiler, bạn sẽ nhận thấy rằng SQL Server nhận được một số INSERTcâu lệnh để tải TVP, mỗi câu lệnh cho mỗi hàng trong TVP , tiếp theo là cuộc gọi đến thủ tục. Đây là do thiết kế . Lô INSERTs này cần được biên dịch mỗi khi thủ tục được gọi và tạo thành một chi phí nhỏ. Tuy nhiên, ngay cả với chi phí này, TVP vẫn thổi bay các phương pháp khác về hiệu suất và khả năng sử dụng cho phần lớn các trường hợp sử dụng.

Nếu bạn muốn tìm hiểu thêm, Erland Sommarskog hoàn toàn hiểu rõ về cách các tham số có giá trị bảng hoạt động và cung cấp một số ví dụ.

Đây là một ví dụ khác tôi đã pha chế:

CREATE TYPE id_list AS TABLE (
    id int NOT NULL PRIMARY KEY
);
GO

CREATE PROCEDURE [dbo].[tvp_test] (
      @param1           INT
    , @customer_list    id_list READONLY
)
AS
BEGIN
    SELECT @param1 AS param1;

    -- join, filter, do whatever you want with this table 
    -- (other than modify it)
    SELECT *
    FROM @customer_list;
END;
GO

DECLARE @customer_list id_list;

INSERT INTO @customer_list (
    id
)
VALUES (1), (2), (3), (4), (5), (6), (7);

EXECUTE [dbo].[tvp_test]
      @param1 = 5
    , @customer_list = @customer_list
;
GO

DROP PROCEDURE dbo.tvp_test;
DROP TYPE id_list;
GO

Khi tôi chạy cái này, tôi gặp lỗi: Msg 2715, Cấp 16, Trạng thái 3, Quy trình tvp_test, Dòng 4 [Hàng loạt bắt đầu Dòng 4] Cột, tham số hoặc biến số 2: Không thể tìm thấy id_list loại dữ liệu. Tham số hoặc biến '@customer_list' có kiểu dữ liệu không hợp lệ. Msg 1087, Cấp 16, Trạng thái 1, Quy trình tvp_test, Dòng 13 [Batch Start Line 4] Phải khai báo biến bảng "@customer_list".
Damian

@Damian - CREATE TYPETuyên bố khi bắt đầu chạy thành công? Phiên bản SQL Server nào bạn đang chạy?
Nick Chammas

Trong mã SP, bạn có câu này nội tuyến `SELECT @ param1 AS param1; ' . Mục đích gì? Bạn không sử dụng hoặc param1 vậy tại sao bạn lại đặt thông số này làm tham số trong tiêu đề SP?
EAmez

@EAmez - Đó chỉ là một ví dụ tùy tiện. Điểm @customer_listkhông phải là @param1. Ví dụ đơn giản chỉ ra rằng bạn có thể trộn các loại tham số khác nhau.
Nick Chammas

21

Toàn bộ chủ đề được thảo luận trên các bài viết dứt khoát bởi Erland Sommarskog: "Mảng và Danh mục trong SQL Server" . Hãy chọn phiên bản nào để chọn.

Tóm lại, đối với trước SQL Server 2008 nơi TVPs trump phần còn lại

  • CSV, phân chia cách bạn thích (Tôi thường sử dụng bảng Số)
  • XML và phân tích cú pháp (tốt hơn với SQL Server 2005+)
  • Tạo một bảng tạm thời trên máy khách

Bài viết này đáng để đọc dù sao để xem các kỹ thuật và suy nghĩ khác.

Chỉnh sửa: câu trả lời trễ cho danh sách lớn ở nơi khác: Truyền tham số mảng cho thủ tục được lưu trữ


14

Tôi biết rằng tôi đến trễ bữa tiệc này, nhưng tôi đã gặp một vấn đề như vậy trong quá khứ, phải gửi tới 100 nghìn số lớn, và đã làm một vài điểm chuẩn. Cuối cùng chúng tôi đã gửi chúng ở định dạng nhị phân, dưới dạng hình ảnh - nhanh hơn mọi thứ khác với số lượng lên tới 100 nghìn.

Đây là mã cũ (SQL Server 2005) của tôi:

SELECT  Number * 8 + 1 AS StartFrom ,
        Number * 8 + 8 AS MaxLen
INTO    dbo.ParsingNumbers
FROM    dbo.Numbers
GO

CREATE FUNCTION dbo.ParseImageIntoBIGINTs ( @BIGINTs IMAGE )
RETURNS TABLE
AS RETURN
    ( SELECT    CAST(SUBSTRING(@BIGINTs, StartFrom, 8) AS BIGINT) Num
      FROM      dbo.ParsingNumbers
      WHERE     MaxLen <= DATALENGTH(@BIGINTs)
    )
GO

Các mã sau đây là đóng gói số nguyên vào một blob nhị phân. Tôi đang đảo ngược thứ tự của byte ở đây:

static byte[] UlongsToBytes(ulong[] ulongs)
{
int ifrom = ulongs.GetLowerBound(0);
int ito   = ulongs.GetUpperBound(0);
int l = (ito - ifrom + 1)*8;
byte[] ret = new byte[l];
int retind = 0;
for(int i=ifrom; i<=ito; i++)
{
ulong v = ulongs[i];
ret[retind++] = (byte) (v >> 0x38);
ret[retind++] = (byte) (v >> 0x30);
ret[retind++] = (byte) (v >> 40);
ret[retind++] = (byte) (v >> 0x20);
ret[retind++] = (byte) (v >> 0x18);
ret[retind++] = (byte) (v >> 0x10);
ret[retind++] = (byte) (v >> 8);
ret[retind++] = (byte) v;
}
return ret;
}

9

Tôi bị giằng xé giữa việc giới thiệu bạn với SO hoặc trả lời nó ở đây, vì đây gần như là một câu hỏi lập trình. Nhưng vì tôi đã có một giải pháp tôi sử dụng ... Tôi sẽ đăng nó;)

Cách thức hoạt động của cái này là bạn cung cấp một chuỗi được phân tách bằng dấu phẩy (tách đơn giản, không thực hiện phân tách kiểu CSV) vào quy trình được lưu trữ dưới dạng varchar (4000) và sau đó đưa danh sách đó vào hàm này và lấy ra một bảng tiện dụng, một bảng chỉ varchars.

Điều này cho phép bạn gửi các giá trị của chỉ các id mà bạn muốn xử lý và bạn có thể thực hiện một phép nối đơn giản tại thời điểm đó.

Thay phiên, bạn có thể làm điều gì đó với DataR CLR và cung cấp thông tin đó, nhưng đó là một chút chi phí để hỗ trợ và mọi người đều hiểu danh sách CSV.

USE [Database]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER FUNCTION [dbo].[splitListToTable] (@list      nvarchar(MAX), @delimiter nchar(1) = N',')
      RETURNS @tbl TABLE (value     varchar(4000)      NOT NULL) AS
/*
http://www.sommarskog.se/arrays-in-sql.html
This guy is apparently THE guy in SQL arrays and lists 

Need an easy non-dynamic way to split a list of strings on input for comparisons

Usage like thus:

DECLARE @sqlParam VARCHAR(MAX)
SET @sqlParam = 'a,b,c'

SELECT * FROM (

select 'a' as col1, '1' as col2 UNION
select 'a' as col1, '2' as col2 UNION
select 'b' as col1, '3' as col2 UNION
select 'b' as col1, '4' as col2 UNION
select 'c' as col1, '5' as col2 UNION
select 'c' as col1, '6' as col2 ) x 
WHERE EXISTS( SELECT value FROM splitListToTable(@sqlParam,',') WHERE x.col1 = value )

*/
BEGIN
   DECLARE @endpos   int,
           @startpos int,
           @textpos  int,
           @chunklen smallint,
           @tmpstr   nvarchar(4000),
           @leftover nvarchar(4000),
           @tmpval   nvarchar(4000)

   SET @textpos = 1
   SET @leftover = ''
   WHILE @textpos <= datalength(@list) / 2
   BEGIN
      SET @chunklen = 4000 - datalength(@leftover) / 2
      SET @tmpstr = @leftover + substring(@list, @textpos, @chunklen)
      SET @textpos = @textpos + @chunklen

      SET @startpos = 0
      SET @endpos = charindex(@delimiter, @tmpstr)

      WHILE @endpos > 0
      BEGIN
         SET @tmpval = ltrim(rtrim(substring(@tmpstr, @startpos + 1,
                                             @endpos - @startpos - 1)))
         INSERT @tbl (value) VALUES(@tmpval)
         SET @startpos = @endpos
         SET @endpos = charindex(@delimiter, @tmpstr, @startpos + 1)
      END

      SET @leftover = right(@tmpstr, datalength(@tmpstr) / 2 - @startpos)
   END

   INSERT @tbl(value) VALUES (ltrim(rtrim(@leftover)))
   RETURN
END

Chà, tôi đặc biệt cố gắng tránh danh sách được phân cách bằng dấu phẩy để tôi không phải viết một cái gì đó tương tự, nhưng vì nó đã được viết rồi, tôi đoán tôi phải ném giải pháp đó lại. ;-)
D. Lambert

1
Tôi nói đã thử và đúng là dễ nhất. Bạn có thể tạo ra một danh sách được phân tách bằng dấu phẩy trong C # trong vài giây mã và bạn có thể ném nó vào chức năng này (sau khi đưa nó vào vòng quay của bạn) đủ nhanh và bạn thậm chí không cần phải nghĩ về nó. ~ Và tôi biết bạn nói rằng bạn không muốn sử dụng một chức năng, nhưng tôi nghĩ đó là cách đơn giản nhất (có thể không hiệu quả nhất)
jcolebrand

5

Tôi thường xuyên nhận được bộ 1000 hàng và 10000 hàng được gửi từ ứng dụng của chúng tôi để được xử lý bởi các quy trình lưu trữ SQL Server khác nhau.

Để đáp ứng nhu cầu về hiệu năng, chúng tôi sử dụng TVP, nhưng bạn phải triển khai bản tóm tắt dbDataReader của riêng mình để khắc phục một số vấn đề về hiệu suất trong chế độ xử lý mặc định của nó. Tôi sẽ không đi sâu vào vấn đề vì họ ở ngoài phạm vi cho yêu cầu này.

Tôi đã không xem xét xử lý XML vì tôi không tìm thấy một triển khai XML nào vẫn hoạt động với hơn 10.000 "hàng".

Việc xử lý danh sách có thể được xử lý bằng cách xử lý bảng một chiều và hai chiều (số). Chúng tôi đã sử dụng thành công những thứ này trong các lĩnh vực khác nhau, nhưng TVP được quản lý tốt sẽ có hiệu suất cao hơn khi có hơn vài trăm "hàng".

Như với tất cả các lựa chọn liên quan đến xử lý SQL Server, bạn phải lựa chọn dựa trên mô hình sử dụng.


5

Cuối cùng tôi cũng có cơ hội thực hiện một số TableValuedParameter và chúng hoạt động rất tốt, vì vậy tôi sẽ dán toàn bộ mã lotta cho thấy cách tôi sử dụng chúng, với một mẫu từ một số mã hiện tại của tôi: (lưu ý: chúng tôi sử dụng ADO .MẠNG LƯỚI)

Cũng lưu ý: Tôi đang viết một số mã cho một dịch vụ và tôi đã có rất nhiều bit mã được xác định trước trong lớp khác, nhưng tôi đang viết đây là một ứng dụng bảng điều khiển để tôi có thể gỡ lỗi nó, vì vậy tôi đã tách tất cả mã này từ ứng dụng giao diện điều khiển. Xin lỗi phong cách mã hóa của tôi (như chuỗi kết nối được mã hóa cứng) vì nó là loại "xây dựng một để loại bỏ". Tôi muốn chỉ ra cách tôi sử dụng a List<customObject>và đẩy nó vào cơ sở dữ liệu một cách dễ dàng như một bảng, mà tôi có thể sử dụng trong thủ tục được lưu trữ. Mã C # và TSQL dưới đây:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using a;

namespace a.EventAMI {
    class Db {
        private static SqlCommand SqlCommandFactory(string sprocName, SqlConnection con) { return new SqlCommand { CommandType = CommandType.StoredProcedure, CommandText = sprocName, CommandTimeout = 0, Connection = con }; }

        public static void Update(List<Current> currents) {
            const string CONSTR = @"just a hardwired connection string while I'm debugging";
            SqlConnection con = new SqlConnection( CONSTR );

            SqlCommand cmd = SqlCommandFactory( "sprocname", con );
            cmd.Parameters.Add( "@CurrentTVP", SqlDbType.Structured ).Value = Converter.GetDataTableFromIEnumerable( currents, typeof( Current ) ); //my custom converter class

            try {
                using ( con ) {
                    con.Open();
                    cmd.ExecuteNonQuery();
                }
            } catch ( Exception ex ) {
                ErrHandler.WriteXML( ex );
                throw;
            }
        }
    }
    class Current {
        public string Identifier { get; set; }
        public string OffTime { get; set; }
        public DateTime Off() {
            return Convert.ToDateTime( OffTime );
        }

        private static SqlCommand SqlCommandFactory(string sprocName, SqlConnection con) { return new SqlCommand { CommandType = CommandType.StoredProcedure, CommandText = sprocName, CommandTimeout = 0, Connection = con }; }

        public static List<Current> GetAll() {
            List<Current> l = new List<Current>();

            const string CONSTR = @"just a hardcoded connection string while I'm debugging";
            SqlConnection con = new SqlConnection( CONSTR );

            SqlCommand cmd = SqlCommandFactory( "sprocname", con );

            try {
                using ( con ) {
                    con.Open();
                    using ( SqlDataReader reader = cmd.ExecuteReader() ) {
                        while ( reader.Read() ) {
                            l.Add(
                                new Current {
                                    Identifier = reader[0].ToString(),
                                    OffTime = reader[1].ToString()
                                } );
                        }
                    }

                }
            } catch ( Exception ex ) {
                ErrHandler.WriteXML( ex );
                throw;
            }

            return l;
        }
    }
}

-------------------
the converter class
-------------------
using System;
using System.Collections;
using System.Data;
using System.Reflection;

namespace a {
    public static class Converter {
        public static DataTable GetDataTableFromIEnumerable(IEnumerable aIEnumerable) {
            return GetDataTableFromIEnumerable( aIEnumerable, null );
        }

        public static DataTable GetDataTableFromIEnumerable(IEnumerable aIEnumerable, Type baseType) {
            DataTable returnTable = new DataTable();

            if ( aIEnumerable != null ) {
                //Creates the table structure looping in the in the first element of the list
                object baseObj = null;

                Type objectType;

                if ( baseType == null ) {
                    foreach ( object obj in aIEnumerable ) {
                        baseObj = obj;
                        break;
                    }

                    objectType = baseObj.GetType();
                } else {
                    objectType = baseType;
                }

                PropertyInfo[] properties = objectType.GetProperties();

                DataColumn col;

                foreach ( PropertyInfo property in properties ) {
                    col = new DataColumn { ColumnName = property.Name };
                    if ( property.PropertyType == typeof( DateTime? ) ) {
                        col.DataType = typeof( DateTime );
                    } else if ( property.PropertyType == typeof( Int32? ) ) {
                        col.DataType = typeof( Int32 );
                    } else {
                        col.DataType = property.PropertyType;
                    }
                    returnTable.Columns.Add( col );
                }

                //Adds the rows to the table

                foreach ( object objItem in aIEnumerable ) {
                    DataRow row = returnTable.NewRow();

                    foreach ( PropertyInfo property in properties ) {
                        Object value = property.GetValue( objItem, null );
                        if ( value != null )
                            row[property.Name] = value;
                        else
                            row[property.Name] = "";
                    }

                    returnTable.Rows.Add( row );
                }
            }
            return returnTable;
        }

    }
}

USE [Database]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER PROC [dbo].[Event_Update]
    @EventCurrentTVP    Event_CurrentTVP    READONLY
AS

/****************************************************************
    author  cbrand
    date    
    descrip I'll ask you to forgive me the anonymization I've made here, but hope this helps
    caller  such and thus application
****************************************************************/

BEGIN TRAN Event_Update

DECLARE @DEBUG INT

SET @DEBUG = 0 /* test using @DEBUG <> 0 */

/*
    Replace the list of outstanding entries that are still currently disconnected with the list from the file
    This means remove all existing entries (faster to truncate and insert than to delete on a join and insert, yes?)
*/
TRUNCATE TABLE [database].[dbo].[Event_Current]

INSERT INTO [database].[dbo].[Event_Current]
           ([Identifier]
            ,[OffTime])
SELECT [Identifier]
      ,[OffTime]
  FROM @EventCurrentTVP

IF (@@ERROR <> 0 OR @DEBUG <> 0) 
BEGIN
ROLLBACK TRAN Event_Update
END
ELSE
BEGIN
COMMIT TRAN Event_Update
END

USE [Database]
GO

CREATE TYPE [dbo].[Event_CurrentTVP] AS TABLE(
    [Identifier] [varchar](20) NULL,
    [OffTime] [datetime] NULL
)
GO

Ngoài ra, tôi sẽ chỉ trích mang tính xây dựng về phong cách mã hóa của tôi nếu bạn có điều đó để cung cấp (cho tất cả các độc giả gặp phải câu hỏi này) nhưng vui lòng giữ nó mang tính xây dựng;) ... Nếu bạn thực sự muốn tôi, hãy tìm tôi trong phòng chat tại đây . Hy vọng với đoạn mã này, người ta có thể thấy cách họ có thể sử dụng List<Current>như tôi đã định nghĩa nó là một bảng trong db và một List<T>trong ứng dụng của họ.


3

Tôi sẽ đi với đề xuất số 1 hoặc, thay vào đó, tạo một bảng cào chỉ chứa các id được xử lý. Chèn vào bảng đó trong khi xử lý, sau khi hoàn tất, hãy gọi một Proc tương tự như dưới đây:

BEGIN TRAN

UPDATE dt
SET processed = 1
FROM dataTable dt
JOIN processedIds pi ON pi.id = dt.id;

TRUNCATE TABLE processedIds

COMMIT TRAN

Bạn sẽ thực hiện nhiều thao tác chèn, nhưng chúng sẽ nằm trên một cái bàn nhỏ, vì vậy nó sẽ nhanh. Bạn cũng có thể bó các phần chèn của mình bằng ADO.net hoặc bất kỳ bộ điều hợp dữ liệu nào bạn đang sử dụng.


2

Tiêu đề câu hỏi bao gồm nhiệm vụ truyền dữ liệu từ một ứng dụng vào thủ tục được lưu trữ. Phần đó được loại trừ bởi cơ quan câu hỏi, nhưng hãy để tôi cố gắng trả lời điều này quá.

Trong ngữ cảnh của sql-server-2008 như được chỉ định bởi các thẻ, có một bài viết tuyệt vời khác của E. Sommarskog Mảng và Danh sách trong SQL Server 2008 . BTW Tôi đã tìm thấy nó trong bài viết mà Marian đề cập đến trong câu trả lời của anh ấy.

Thay vì chỉ đưa ra liên kết, tôi trích dẫn danh sách nội dung của nó:

  • Giới thiệu
  • Lý lịch
  • Các tham số có giá trị bảng trong T-SQL
  • Vượt qua các tham số có giá trị bảng từ ADO .NET
    • Sử dụng danh sách
    • Sử dụng một DataTable
    • Sử dụng DataReader
    • Chú thích cuối
  • Sử dụng tham số có giá trị bảng từ các API khác
    • ODBC
    • DB OLE
    • QUẢNG CÁO
    • LINQ và khung thực thể
    • JDBC
    • PHP
    • Perl
    • Điều gì xảy ra nếu API của bạn không hỗ trợ TVP
  • Cân nhắc hiệu suất
    • Phía máy chủ
    • Phía khách hàng
    • Khóa chính hay không?
  • Lời cảm ơn và phản hồi
  • Lịch sử sửa đổi

Ngoài các kỹ thuật được đề cập ở đó, tôi có cảm giác rằng trong một số trường hợp, khối lượng lớn và chèn số lượng lớn xứng đáng được đề cập đến phạm vi với trường hợp chung.


1

Truyền tham số mảng cho một thủ tục được lưu trữ

Dành cho MS SQL 2016 phiên bản mới nhất

Với MS SQL 2016, họ giới thiệu một hàm mới: SPLIT_STRING () để phân tích nhiều giá trị.

Điều này có thể giải quyết vấn đề của bạn một cách dễ dàng.

Đối với MS SQL Phiên bản cũ hơn

Nếu bạn đang sử dụng phiên bản cũ hơn, hãy làm theo bước này:

Đầu tiên Tạo một chức năng:

 ALTER FUNCTION [dbo].[UDF_IDListToTable]
 (
    @list          [varchar](MAX),
    @Seperator     CHAR(1)
  )
 RETURNS @tbl TABLE (ID INT)
 WITH 

 EXECUTE AS CALLER
 AS
  BEGIN
    DECLARE @position INT
    DECLARE @NewLine CHAR(2) 
    DECLARE @no INT
    SET @NewLine = CHAR(13) + CHAR(10)

    IF CHARINDEX(@Seperator, @list) = 0
    BEGIN
    INSERT INTO @tbl
    VALUES
      (
        @list
      )
END
ELSE
BEGIN
    SET @position = 1
    SET @list = @list + @Seperator
    WHILE CHARINDEX(@Seperator, @list, @position) <> 0
    BEGIN
        SELECT @no = SUBSTRING(
                   @list,
                   @position,
                   CHARINDEX(@Seperator, @list, @position) - @position
               )

        IF @no <> ''
            INSERT INTO @tbl
            VALUES
              (
                @no
              )

        SET @position = CHARINDEX(@Seperator, @list, @position) + 1
    END
END
RETURN
END

Sau khi thực hiện điều này, chỉ cần chuyển chuỗi của bạn đến hàm này bằng dấu phân cách.

Tôi hy vọng điều này hữu ích cho bạn. :-)


-1

Sử dụng cái này để tạo "tạo kiểu bảng". ví dụ đơn giản cho người dùng

CREATE TYPE unit_list AS TABLE (
    ItemUnitId int,
    Amount float,
    IsPrimaryUnit bit
);

GO
 CREATE TYPE specification_list AS TABLE (
     ItemSpecificationMasterId int,
    ItemSpecificationMasterValue varchar(255)
);

GO
 declare @units unit_list;
 insert into @units (ItemUnitId, Amount, IsPrimaryUnit) 
  values(12,10.50, false), 120,100.50, false), (1200,500.50, true);

 declare @spec specification_list;
  insert into @spec (ItemSpecificationMasterId,temSpecificationMasterValue) 
   values (12,'test'), (124,'testing value');

 exec sp_add_item "mytests", false, @units, @spec


//Procedure definition
CREATE PROCEDURE sp_add_item
(   
    @Name nvarchar(50),
    @IsProduct bit=false,
    @UnitsArray unit_list READONLY,
    @SpecificationsArray specification_list READONLY
)
AS


BEGIN
    SET NOCOUNT OFF     

    print @Name;
    print @IsProduct;       
    select * from @UnitsArray;
    select * from @SpecificationsArray;
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.