C# 通过向SQL Server&C传递逗号分隔值删除记录
我试图通过使用下面的查询和ado.net ExecuteOnQuery方法将多个值作为逗号分隔的字符串传递,从SQL Server数据库表中删除记录。这里我传递表名、列名和逗号分隔的值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中的完整值列表没有问题,则删除过程将成功。假设如果外键与任何值冲
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;
}
}
}
}