Sql 如何将数组传递给存储过程,以便连续插入一些记录

Sql 如何将数组传递给存储过程,以便连续插入一些记录,sql,sql-server,stored-procedures,user-defined-types,table-valued-parameters,Sql,Sql Server,Stored Procedures,User Defined Types,Table Valued Parameters,众所周知,SQL Server中没有数组,因此有一些变通方法可以实现这一点,比如表值参数TVP和用户定义的数据类型UDT,甚至还有一个参数作为逗号分隔的值CSV internet上也有一些示例演示了如何使用它,但大多数示例都应用SELECT语句并直接使用参数,如下所示: CREATE PROCEDURE dbo.GetOrderList1 (@OrderList VARCHAR(500)) AS BEGIN SET NOCOUNT ON DECLARE @SQL VAR

众所周知,SQL Server中没有数组,因此有一些变通方法可以实现这一点,比如表值参数TVP和用户定义的数据类型UDT,甚至还有一个参数作为逗号分隔的值CSV

internet上也有一些示例演示了如何使用它,但大多数示例都应用SELECT语句并直接使用参数,如下所示:

CREATE PROCEDURE dbo.GetOrderList1
    (@OrderList VARCHAR(500))
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @SQL VARCHAR(600)

    SET @SQL =  'SELECT OrderID, CustomerID, EmployeeID, OrderDate
                 FROM dbo.Orders
                 WHERE OrderID IN (' + @OrderList + ')'

    EXEC(@SQL)  
END
EXEC sp_AddToMessageReceivers 100, (1001, 1002, 1003, 1004, 1005)

CREATE PROCEDURE sp_AddToMessageReceivers
    @messageId INT, 
    @ReceiversIds VARCHAR(500)
AS
BEGIN
    DECLARE @i INT = 0

    WHILE @i < @ReceiversIds.length
    BEGIN
        INSERT INTO [dbo].[MessageReceiver] (MessageId, ReceiverIds)
        VALUES (@MessageId, @ReceiversIds[i])

        SET @i = @i + 1
    END
END
但我需要使用INSERT语句,并逐个使用参数的每个部分

在我的例子中,我通过信号器向多个参与者发送消息,并且我还需要将其保存在数据库中。假设我有一个MessageId,我想这样保存它:

ID   |    MessageId   | ReceiverId
-----+----------------+-----------
     |                |
我需要我的存储过程只获取一个MessageId(100)和多个receiverid(1001、1002、1003、1004、1005),然后将它们逐个插入到相关表中,如下所示:

CREATE PROCEDURE dbo.GetOrderList1
    (@OrderList VARCHAR(500))
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @SQL VARCHAR(600)

    SET @SQL =  'SELECT OrderID, CustomerID, EmployeeID, OrderDate
                 FROM dbo.Orders
                 WHERE OrderID IN (' + @OrderList + ')'

    EXEC(@SQL)  
END
EXEC sp_AddToMessageReceivers 100, (1001, 1002, 1003, 1004, 1005)

CREATE PROCEDURE sp_AddToMessageReceivers
    @messageId INT, 
    @ReceiversIds VARCHAR(500)
AS
BEGIN
    DECLARE @i INT = 0

    WHILE @i < @ReceiversIds.length
    BEGIN
        INSERT INTO [dbo].[MessageReceiver] (MessageId, ReceiverIds)
        VALUES (@MessageId, @ReceiversIds[i])

        SET @i = @i + 1
    END
END
我知道如何在存储过程中获取表值参数,但不知道如何在while循环中逐个使用它们


如果您为这个问题提供了一个解决方案,我非常适合使用它。

您需要创建一个表值函数,从csv值返回一个表表单

CREATE FUNCTION [dbo].[fn_csvToColumns]
    (@CommadelimitedString NVARCHAR(MAX),
     @delimiter CHAR(1)
)
RETURNS @Result TABLE (Value VARCHAR(100))
AS
BEGIN
    -- Fill the table variable with the rows for your result set
    DECLARE @IntLocation INT

    WHILE (CHARINDEX(',',    @CommadelimitedString, 0) > 0)
    BEGIN
        SET @IntLocation =   CHARINDEX(@delimiter, @CommadelimitedString, 0)      

        INSERT INTO @Result (Value)
            --LTRIM and RTRIM to ensure blank spaces are   removed
            SELECT RTRIM(LTRIM(SUBSTRING(@CommadelimitedString, 0, @IntLocation)))   

        SET @CommadelimitedString = STUFF(@CommadelimitedString, 1, @IntLocation, '') 
    END

    INSERT INTO @Result (Value)
        SELECT RTRIM(LTRIM(@CommadelimitedString))--LTRIM and RTRIM to ensure blank     spaces are removed

    DELETE FROM @Result 
    WHERE Value = ''

    RETURN 
END
更改存储过程以使用上述功能:

ALTER PROCEDURE sp_AddToMessageReceivers
    @messageId INT, 
    @ReceiversIds VARCHAR(500)
AS
BEGIN
    DECLARE @temp TABLE (tempid INT)

    INSERT INTO @temp(tempid)
        SELECT value 
        FROM dbo.fn_csvToColumns(@ReceiversIds, ',')

    WHILE EXISTS (SELECT TOP 1 null FROM @temp)
    BEGIN
        SELECT TOP 1 @ReceiverIds = tempid 
        FROM @temp

        INSERT INTO [dbo].[MessageReceiver] (MessageId, ReceiverIds)
        VALUES (@MessageId, @ReceiverIds)

        DELETE FROM @temp 
        WHERE tempid = @ReceiverIds
    END
END

我会用一个拆分函数很好的例子可以找到

例如:

INSERT INTO yourTable (messageId, ReceiversId)
SELECT @messageId, fn_ssm.Item
FROM dbo.SplitStrings_Moden fn_ssm (@ReceiversIds, N',')
我强烈反对任何使用WHILE循环之类的方法,尤其是上面示例中的nestes循环,因为这可能会显著影响代码性能

您可以从应用程序端创建一个1-Many关系数据表,并将其作为表值参数传递给存储过程,然后插入到表中

例如,首先创建一个带有MessageId和ReceiverId的TVP

然后像这样创建存储过程

CREATE PROCEDURE uspAddToMessageReceivers
    @MsgTvp MessageTableType readonly
AS
BEGIN
        INSERT INTO [dbo].[MessageReceiver] 
        SELECT MessageId, ReceiverId from @MsgTvp
END
然后从应用程序端(假设为C应用程序)创建一个DataTable,并将其传递给存储过程

DataTable dataTable = new DataTable();
dataTable.Columns.Add("MessageId", typeof(int));
dataTable.Columns.Add("ReceiverId", typeof(int));
//dataTable.Rows.Add.. Add rows here

旁注:存储过程不应使用sp_uu前缀。微软已经这样做了,而且你确实有可能在将来的某个时候发生名称冲突。最好只是简单地避免使用sp_u,并使用其他东西作为前缀——或者根本不使用前缀!我会注意到问题中的第一个示例中的一些注意事项-像这样连接和执行动态SQL是在应用程序中打开一些不错的SQL注入问题的好方法。谢谢@Paddy,但不,这不应该损害任何东西,因为我用C代码填充了一个列表ReceiverID,并通过一个“,'字符来生成CSV,因此不可能进行SQL注入。再加上一个字符,这将比上面的CSV版本性能更好,并且具有更强的类型。