【问题标题】:Deleting records by passing comma separated values SQL Server & C#通过传递逗号分隔值删除记录 SQL Server & C#
【发布时间】:2016-02-10 12:11:19
【问题描述】:

我正在尝试通过使用以下查询和 ado.net ExecuteNonQuery() 方法将多个值作为逗号分隔的字符串传递来从我的 SQL Server 数据库表中删除记录。在这里,我传递了表名、列名和逗号分隔值。

string delQuery = "DELETE FROM " + delTblName + 
                  " WHERE " + delColumnName + " IN (" + toDel+ ")";

如果toDel 中的完整值列表没有问题,则删除过程成功。假设如果外键与任何值发生冲突,则整个语句将被终止,并且不会删除任何记录。

由于toDel 变量中的元素数量很大,我不能使用顺序处理,也不能按照要求使用存储过程。

我需要根据成功的元素进行删除,并从这个方法中返回错误的元素。任何帮助,将不胜感激。

使用参数使用下面的代码

try
{
   using (var sc = new SqlConnection(dbConnString))
     using (var cmd = sc.CreateCommand())
     {
         sc.Open();
         cmd.CommandText = "DELETE FROM @delTblName WHERE @delColumnName IN ( @valConcat) ";                                       

         cmd.Parameters.AddWithValue("@delTblName", tblName);


         cmd.Parameters.AddWithValue("@delColumnName", delColumnName);


         cmd.Parameters.AddWithValue("@valConcat", nosConcat);


         cmd.CommandType = CommandType.Text;

         rowsAffected = rowsAffected + cmd.ExecuteNonQuery();
     }
}
catch
{

}

收到此错误 - 必须声明表变量“@delTblName”。

【问题讨论】:

  • 使用parameterized queries,一切顺利。
  • 尝试但出错,因为我想通过将表名也作为参数传递来创建动态查询。我还想从 IN ( toDel) 子句中获取错误值。
  • 你能告诉我们参数化的代码和相关的错误信息吗?
  • toDel 中大概有多少个元素?
  • @Balah - 最多可以有 15k 个元素。

标签: c# sql sql-server


【解决方案1】:

无论是否使用存储过程,您都有两个相互矛盾的要求:

  1. 您希望批量删除行,而不是一个一个地删除。为了速度。
  2. 您希望删除操作完成部分忽略可能的约束违规。

我不知道如何使DELETE 语句部分工作。一条语句是最小的原子工作量,因此如果 1000 行中有一行违反约束,则整个语句将回滚。

这导致了以下一般想法。在尝试在一个影响许多行的语句中 DELETE 之前,请确保可以删除受影响行的整个列表。明确检查数据中是否没有任何内容可以阻止您要删除的行被删除。实际检查取决于您的约束。找出那些无法删除的行并将它们从主 DELETE 列表中删除。

示例

两个表 - TestMasterTestDetails 具有一对多关系。

CREATE TABLE [dbo].[TestMaster](
    [ID] [int] NOT NULL,
    [MasterData] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_TestMaster] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
))

CREATE TABLE [dbo].[TestDetails](
    [ID] [int] NOT NULL,
    [MasterID] [int] NOT NULL,
    [DetailData] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_TestDetails] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
))
GO

ALTER TABLE [dbo].[TestDetails]  WITH CHECK 
ADD  CONSTRAINT [FK_TestDetails_TestMaster] FOREIGN KEY([MasterID])
REFERENCES [dbo].[TestMaster] ([ID])
GO

ALTER TABLE [dbo].[TestDetails] CHECK CONSTRAINT [FK_TestDetails_TestMaster]
GO

一些样本数据:

INSERT INTO [dbo].[TestMaster] ([ID],[MasterData]) VALUES
(1, 'Master1'),
(2, 'Master2'),
(3, 'Master3'),
(4, 'Master4');

INSERT INTO [dbo].[TestDetails] ([ID],[MasterID],[DetailData]) VALUES
(10, 1, 'Detail10'),
(11, 1, 'Detail11'),
(20, 2, 'Detail20');

简单尝试DELETE项目(1, 2, 3, 4)

DELETE FROM [dbo].[TestMaster]
WHERE ID IN (1, 2, 3, 4);

这失败了:

Msg 547, Level 16, State 0, Line 13
The DELETE statement conflicted with the REFERENCE constraint "FK_TestDetails_TestMaster". The conflict occurred in database "AdventureWorks2014", table "dbo.TestDetails", column 'MasterID'.
The statement has been terminated.

我们只能删除那些没有相应详细信息的主行。在这个例子中它只是3, 4

先找到不能删除的MasterID

SELECT DISTINCT [dbo].[TestDetails].MasterID
FROM [dbo].[TestDetails]
WHERE [dbo].[TestDetails].MasterID IN (1, 2, 3, 4);

返回:

MasterID
1
2

编辑您的原始 ID 列表并从中删除冲突的 ID,然后您可以运行最终的 DELETE

DELETE FROM [dbo].[TestMaster]
WHERE ID IN (3, 4);

单一查询

您无需运行单独的SELECT、通过网络向客户端检索冲突 ID 的列表、调整原始 ID 列表、运行最终的 DELETE,您可以通过单个查询完成所有操作。对于这个简单的示例,它可能如下所示:

DELETE FROM [dbo].[TestMaster]
WHERE 
    [dbo].[TestMaster].ID IN (1, 2, 3, 4)
    AND [dbo].[TestMaster].ID NOT IN
    (
        SELECT [dbo].[TestDetails].MasterID
        FROM [dbo].[TestDetails]
        WHERE [dbo].[TestDetails].MasterID IN (1, 2, 3, 4)
    );

此查询将仅删除 ID 为 3 和 4 的两行。

【讨论】:

  • 在一个巨大的数据库中,我们不知道什么是有关系的表。我们只需要删除没有冲突的记录并找到其他记录。就是这样。
  • 关系型数据库,如 SQL Server,旨在与稳定且已知的架构一起工作。使用未知或动态模式通常很困难。您可以尝试从INFORMATION_SCHEMA 和系统表中提取有关约束的所需信息。或者,逐个删除行。
【解决方案2】:

默认情况下,如果您的 sql 语句失败,则事务将回滚。如果您通过单个语句来获取错误的语句,那么您将无法满足您的需求

也许你可以按照下面给出的方法

    try
    {
        using (var sc = new SqlConnection(dbConnString))
        using (var cmd = sc.CreateCommand())
        {
            sc.Open();
            var nosConcat = "1,2,3,4,5";
            string failedIds = string.Empty;
            var ids = nosConcat.Split(',');
            string @delTblName = "sometable";
            string @delColumnName = "somecolumn";
            for(int i = 0 ; i < ids.Length ; i++)
            {
                try
                {
                    cmd.CommandText = "DELETE FROM " + @delTblName + " WHERE " + @delColumnName + " = @valConcat ";
                    cmd.Parameters.AddWithValue("@valConcat", ids[i]);
                    cmd.CommandType = CommandType.Text;
                    cmd.ExecuteNonQuery();
                }
                catch (SqlException ex)
                {
                    if (ex.Errors[0].Number == 444) //Use the actual error number that you get on foreign key confilict
                        failedIds += "," + ids[i];
                }
            }
        }
    }
    catch
    {

    }

【讨论】:

  • 更准确地说:任何合理的数据库都遵循 ACID 原则。 A代表原子。一条语句(或事务中的一组语句)要么完全执行,要么根本不执行。基本数据库原理。
【解决方案3】:

如果您只需要删除,这将是一个简单的答案 - 批处理删除语句并将它们包装在事务中。但是,由于您需要导致问题的密钥,并且您不能使用存储过程,这使得问题变得棘手。

这是一个思想实验,可能会让你更接近你想要的。思路是这样的:

  1. 批量处理删除。如果不使用存储过程,我认为没有办法解决。
  2. 在每个批次中:
    • 尝试删除所有记录(最大“窗口”大小)
    • 如果删除失败,请尝试在较小的“窗口”中删除记录。
    • 继续尝试并缩小“窗口”,直到找到问题记录。
    • 在单个错误上将该值添加到错误列表中。
    • 继续检查每条记录。
    • 如果下一次删除成功,请自信地增加“窗口”大小。

“窗口”大小调整批处理中要处理的记录数。


最后你会删除你能删除的,并且会有一个未能删除的值列表。

不幸的是,您无法运行删除并获取出现问题的值列表(数据库事务不能那样工作),但上面的“类似二进制搜索”的方法至少比检查 15k 中的每个值要快列表...仅当存在少量冲突时。否则不会更快。


以下是详细说明步骤的代码:

int batchSize = 1024; // define this elsewhere
// allValuesToDel is your list of values. I've just defined it as an empty list
var allValuesToDel = new List<int>();
var valueErrors = new List<int>();

for (int i = 0; i < allValuesToDel.Count; i += batchSize)
{
    using (var sc = new SqlConnection(dbConnString))
    {
        sc.Open();
        var valueBatch = allValuesToDel.Skip(i)
            .Take(batchSize)
            // annotating each value with a 'processed' flag allows us to track them
            .Select(k => new { Value = k, Processed = false }).ToList();

        // the starting window size
        int windowSize = valueBatch.Count;
        while (valueBatch.Count > 0 && valueBatch.Any(o => !o.Processed))
        {
            // get the unprocessed values within the window
            var valuesToDel = valueBatch.Where(k => !k.Processed).Take(windowSize).ToList();
            string nosConcat = string.Join(",", valuesToDel.Select(k => k.Value));
            try
            {
                using (var cmd = sc.CreateCommand())
                {
                    cmd.CommandText = string.Format("DELETE FROM {0} WHERE {1} IN ({2})", tblName, delColumnName, nosConcat); 
                    cmd.ExecuteNonQuery();
                }

                // on success we can mark them as processed
                valuesToDel.ForEach(k => k.Processed = true);

                // Since delete worked, let's try on more records
                windowSize = windowSize * 2;
            }
            catch (SqlException ex)
            {
                if (windowSize == 1)
                {
                    // we found a value that failed - add that to the list of failures
                    valueErrors.Add(valuesToDel[0].Value);
                    valuesToDel.ForEach(k => k.Processed = true); // mark it as processed
                }

                // decrease the window size (until 1) to try and catch the problematic record
                windowSize = (windowSize / 2) + 1;
            }
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-01-10
    • 1970-01-01
    • 1970-01-01
    • 2010-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-29
    相关资源
    最近更新 更多