C# 通过向SQL Server&C传递逗号分隔值删除记录

C# 通过向SQL Server&C传递逗号分隔值删除记录,c#,sql,sql-server,C#,Sql,Sql Server,我试图通过使用下面的查询和ado.net ExecuteOnQuery方法将多个值作为逗号分隔的字符串传递,从SQL Server数据库表中删除记录。这里我传递表名、列名和逗号分隔的值 string delQuery = "DELETE FROM " + delTblName + " WHERE " + delColumnName + " IN (" + toDel+ ")"; 如果toDel中的完整值列表没有问题,则删除过程将成功。假设如果外键与任何值冲

我试图通过使用下面的查询和ado.net ExecuteOnQuery方法将多个值作为逗号分隔的字符串传递,从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。

默认情况下,如果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
    {

    }

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

您希望批量删除行,而不是逐个删除。为了速度。 您希望删除操作部分完成,忽略可能的约束冲突。 我不知道如何使DELETE语句部分工作。一条语句是最小的原子工作量,因此如果1000行中有一行违反了约束,则整个语句将回滚

这就引出了下面的一般想法。在尝试删除一条影响多行的语句之前,请确保可以删除受影响行的整个列表。显式检查数据中是否没有任何内容可以阻止要删除的行被删除。实际检查取决于您的约束。确定无法删除的行,并将其从主删除列表中删除

范例

两个表-TestMaster和TestDetails,具有一对多关系

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');
简单尝试删除项目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,然后可以运行最终删除:

单一查询

与运行单独的SELECT、通过网络向客户机检索冲突ID列表、调整原始ID列表、运行最终删除不同,您可以进行一次查询来完成这一切。对于这个简单的示例,它可以如下所示:

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的两行。

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

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

分批处理删除操作。如果不使用存储过程,我看没有办法解决这个问题。 每批: 尝试删除最大窗口大小的所有记录 如果删除失败,请尝试在较小的窗口中删除记录。 继续尝试并缩小窗口,直到找到问题记录。 对于单个错误,将该值添加到错误列表中。 继续检查每个记录。 如果下一次删除成功,请自信地增加窗口大小。 窗口大小调整要处理的批次中的记录数

最后,您可能已经删除了您可以删除的内容,并且将有一个无法删除的值列表

不幸的是,您无法运行delete并获取导致问题的值列表。数据库事务不是这样工作的,但上面类似二进制搜索的方法至少比检查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;
            }
        }
    }
}

使用,您就可以开始了。尝试了,但出现了错误,因为我想通过将表名也作为参数传递来创建动态查询。此外,我还想从IN toDel子句中获取出错值。您能给我们看一下参数化代码和相关错误消息吗?toDel中大致有多少个元素?@Balah-最多可以有15k个元素。更确切地说,任何合理的数据库都遵循ACID原则。A代表原子。事务中的一条语句或一组语句要么完全执行,要么根本不执行。基本数据库原理。在一个巨大的数据库中,我们不知道什么是具有关系的表。我们只需要删除没有conf的记录
找到其他人。就是这样。关系数据库,如SQL Server,设计用于使用稳定且已知的模式。使用未知或动态模式通常很困难。您可以尝试从信息模式和系统表中提取有关约束的所需信息。或者,逐个删除行。
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)
    );
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;
            }
        }
    }
}