2016-02-10 4 views
2

Я пытаюсь удалить записи из таблицы базы данных SQL Server, передав несколько значений в виде строки с запятой, используя приведенный ниже запрос, и метод ado.net ExecuteNonQuery(). Здесь я передаю имя таблицы, имя столбца и значения, разделенные запятыми.Удаление записей путем передачи значений, разделенных запятыми SQL Server & C#

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».

+2

Используйте [параметризованных запросов] (http://blog.codinghorror.com/give-me-parameterized-sql-or-give- я-смерть /), и вам хорошо идти. –

+0

Пробовал, но получал ошибку, потому что я хочу создать динамический запрос, передав имя таблицы также в качестве параметра. Также я хочу получить значение ошибки, полученное из предложения IN (toDel). – ByteCruncher

+0

Можете ли вы показать нам параметризованный код и соответствующее сообщение об ошибке? –

ответ

1

По умолчанию, если ваше состояние 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 
    { 

    } 
+0

Точнее: Любая разумная база данных следует принципам ACID. A обозначает ATOMIC. Заявление (или группа операторов в транзакции) выполняется полностью или вообще не выполняется. Фундаментальный принцип db. – TomTom

2

Независимо от того, с помощью хранимых процедур или нет, имеют два противоречащих друг другу требования:

  1. Вы хотите удалить строки в пакетах, а не по очереди. Ради скорости.
  2. Вы хотите, чтобы операция удаления частично игнорировала возможные нарушения ограничений.

Я не знаю, как сделать DELETE заявление частично. Одним из утверждений является минимальная атомная сумма работы, поэтому, если одна строка из 1000 нарушает ограничение, весь оператор будет откат.

Это приводит к следующей общей идее. Прежде чем приступать к DELETE в одном утверждении, которое затрагивает многие строки, убедитесь, что весь список затронутых строк можно удалить. Явно проверьте, что в данных нет ничего, что может помешать удалению строк, которые вы собираетесь удалить. Фактическая проверка зависит от ваших ограничений. Определите те строки, которые не могут быть удалены, и удалите их из основного списка DELETE.

Пример

двух таблиц - 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'); 

Простая попытка 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. 

Мы можем удалить только те Master г которые не имеют соответствующих сведений. В этом примере это только 3, 4.

Найти MasterID, которые не могут быть удалены первым:

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

Который возвращает:

MasterID 
1 
2 

Изменить свой первоначальный список идентификаторов и удалить конфликтующие идентификаторы из него, то вы можете запустить окончательный DELETE:

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

Одноместный запрос

Вместо запуска отдельного SELECT, извлекая список конфликтующих идентификаторов клиента по сети, регулируя первоначальный список идентификаторов, запустив окончательный 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) 
    ); 

Этот запрос будет удалить только две строки с идентификаторами 3 и 4.

+0

В огромном db мы не знаем, каковы таблицы с отношениями. Нам просто нужно удалить записи без конфликтов и найти других. Это оно. – ByteCruncher

+0

Реляционные базы данных, такие как SQL Server, предназначены для работы со стабильной и известной схемой. Работа с неизвестной или динамической схемой обычно сложна. Вы можете попытаться извлечь необходимую информацию об ограничениях из 'INFORMATION_SCHEMA' и системных таблиц. ИЛИ, удалять строки по одному. –

1

Если вам необходимо просто удалить, это был бы простой ответ - партия инструкции delete и обернуть их в транзакцию. Но так как вам нужны ключи, которые вызвали проблему, и вы не можете использовать хранимые процедуры, это затрудняет задачу.

Вот мысленный эксперимент, который поможет вам приблизиться к тому, что вы хотите. Идея такова:

  1. Обработать удаление пакетами. Я не вижу никакого способа обойти это без использования хранимых процедур.
  2. В каждой партии:
    • Попробуйте удалить все записи (максимум «окно» размер)
    • Если удаление не удается, попробуйте удалить записи в меньшем «окна».
    • Продолжайте пробовать и уменьшать «окно» до тех пор, пока не будет найдена проблема.
    • По одной ошибке добавьте это значение в список ошибок.
    • Продолжайте проверку каждой записи.
    • Если следующее удаление удастся, уверенно увеличьте размер окна.

«Окно» размер регулирует количество записей в пакете к процессу.

В конце вы удалили бы то, что можете, и получите список значений, которые не удалось удалить.

К сожалению, вы не можете запустить удаление и получить список значений, которые дали проблемы (транзакции базы данных не так работают), но метод «двоичного поиска», по крайней мере, будет быстрее, чем проверка каждого значения в 15k list ... только в случае небольшого количества конфликтов. Иначе это будет не так быстро.


Вот код, который детализирует шаги:

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; 
      } 
     } 
    } 
} 
Смежные вопросы