【问题标题】:Deleting multiple rows based on row number(s)根据行号删除多行
【发布时间】:2023-03-21 23:06:01
【问题描述】:

我想根据从 WinForm 传入的行号删除行。当然,ROW_NUMBER() 计算的输出在你删除一行时总是会更新,所以行号也会改变,所以我想一次性完成而不是循环。

我有这个代码:

CREATE PROCEDURE Delete_Record (@RowNum INT)
AS     
  ;WITH REC_ROW AS
  (
    SELECT *, ROW_NUMBER() OVER (ORDER BY [Record Number]) AS RN
        FROM User_Data
  )
  DELETE
    FROM REC_ROW
    WHERE RN IN (@RowNum)

当我输入时:

 exec Delete_Record @RowNum = '1,2'

它会产生错误:

将数据类型 varchar 转换为 int 时出错。

如果我将@RowNum INT 更改为@RowNum varchar(max),它只会产生错误消息:

将数据类型 varchar 转换为 bigint 时出错。

当我对值进行硬编码时:

;WITH REC_ROW AS
(
    SELECT *, ROW_NUMBER() OVER (ORDER BY [Record Number]) AS RN
        FROM User_Data
)
DELETE
  FROM REC_ROW
  WHERE RN IN (1,2)

它将成功删除第 1 行和第 2 行。问题是如何将其集成到存储过程中并输入'1,2' 以传递给IN 子句?

【问题讨论】:

    标签: c# sql sql-server-2008 stored-procedures sql-delete


    【解决方案1】:

    如果您要传递比1,2 更复杂的字符串,您可能需要考虑使用表值参数 (TVP)。

    CREATE TYPE dbo.Integers AS TABLE
    (
      RowNumber INT PRIMARY KEY
    );
    

    现在您可以创建存储过程并从应用程序中传递 DataTable 或其他集合,而无需费心构造逗号分隔的字符串或尝试将它们分开。

    CREATE PROCEDURE dbo.Delete_Record
      @RowNums dbo.Integers READONLY
    AS
    BEGIN
      SET NOCOUNT ON;
    
      ;WITH REC_ROW AS (...your CTE unchanged here...)
      DELETE REC_ROW
       FROM REC_ROW INNER JOIN @RowNums AS r
       ON r.RowNumber = REC_ROW.RN;
    END
    GO
    

    如果你真的想使用拆分功能:

    CREATE FUNCTION dbo.SplitInts
    (
       @List      VARCHAR(MAX),
       @Delimiter VARCHAR(255)
    )
    RETURNS TABLE WITH SCHEMABINDING 
    AS
      RETURN 
      (  
        SELECT Item = y.i.value('(./text())[1]', 'int')
        FROM ( SELECT x = CONVERT(XML, '<i>' 
            + REPLACE(@List, @Delimiter, '</i><i>') 
            + '</i>').query('.')) AS a 
        CROSS APPLY x.nodes('i') AS y(i));
    GO
    

    现在你的存储过程:

    CREATE PROCEDURE dbo.Delete_Record -- always use a schema prefix!
      @RowNums VARCHAR(MAX)
    AS 
    BEGIN
      SET NOCOUNT ON;
    
      ;WITH REC_ROW AS
      (
        SELECT *, ROW_NUMBER() OVER (ORDER BY [Record Number]) AS RN
            FROM dbo.User_Data
      )
      DELETE REC_ROW
        FROM REC_ROW 
        INNER JOIN dbo.SplitInts(@RowNums, ',') AS r
        ON r.Item = REC_ROW.RN;
    END
    GO
    

    但我向你保证,TVP 的表现会更好。

    【讨论】:

    • 我应该把 CREATE TYPE dbo.Integers 部分放在哪里?它应该在 Create Procedure 部分内吗?我试过了,它说'('附近的语法不正确。
    • @CaressCastañares,将其作为一次性单独命令运行。它设置表参数的“类型”:dbo.Integers,然后您可以在过程中使用它。
    • @Caress 不,您创建类型一次,而不是在存储过程中。认为它相当于创建一个表。
    • @AaronBertrand 效果很好!总结一下我所做的......首先,我创建了 Integers 数据表类型。其次,我使用代码来获取 DataTable(您的答案中的第二个)。创建存储过程后,(在我的 WINFORM ---> 上)我创建了一个名为 Integers 和列 RowNumber 类型为 Int32 的 DataTable。我填充了它。最后,我使用 SqlConnection、SqlCommand、SqlParameters 和 SqlReader 调用它。万岁,非常感谢你!
    【解决方案2】:

    这个表达式并没有像你想象的那样做:

     WHERE RN IN (@RowNum)
    

    它正在寻找@RowNum'1,2' 的情况。这会导致问题,因为'1,2' 无法转换为整数。您在定义过程时已将参数声明为整数,然后用字符串调用它。

    你可以随心所欲:

    WHERE ','+@RowNum+',' like '%,'+RN+',%'
    

    额外的逗号确保“1”不匹配“10”。

    编辑:

    基本上有三种方法(我能想到)将逗号分隔的整数字符串处理为实整数(这更好,因为查询将使用整数)。

    一种是使用split() 函数,它返回一个分隔字符串的元素表。你可以用谷歌搜索它的各种实现。

    二是使用动态SQL。这对于不需要即时的查询很好。它可能会在编译查询时产生额外的开销,这在事务系统中可能很明显。

    第三个是使用递归CTE来解析字符串。我可能会将此方法用于存储过程。

    【讨论】:

    • 这行不通。行“1”或“2”永远不会像%,1,2,%。错误是1,2 不是int。将参数设为 varchar 并在其上使用拆分函数,然后将您的删除加入到这些拆分行中。
    • 是的,没错。我想使用 LIKE 命令,但我看到会产生问题。有什么方法可以搜索确切的数字,而不仅仅是搜索任何相似之处?
    • @KM,您可以为此创建一个示例代码吗?即使您说的是拆分功能,这也会有很大帮助。谢谢!
    • @CaressCastañares,只要搜索sql server split function,这里有一个很好的参考:sommarskog.se/arrays-in-sql.html
    猜你喜欢
    • 1970-01-01
    • 2019-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多