Сравните две таблицы данных для определения строк в одном, но не другом

У меня есть два DataTables, A и B , созданные из файлов CSV. Мне нужно проверить, какие строки существуют в B , которых нет в A

Есть ли способ сделать какой-то запрос, чтобы показать разные строки, или мне придется перебирать каждую строку на каждом DataTable, чтобы проверить, совпадают ли они? Последний вариант кажется очень интенсивным, если таблицы становятся большими.

мне придется перебирать каждую строку на каждом DataTable, чтобы проверить, совпадают ли они.

Увидев, как вы загрузили данные из файла CSV, у вас не будет никаких индексов или чего-то еще, поэтому в какой-то момент что-то придется перебирать по каждой строке, будь то ваш код или библиотека , или что-то еще.

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

1: Можете ли вы использовать любые свойства данных? Все ли строки в каждой таблице уникальны, и можете ли вы отсортировать их по одинаковым критериям? Если это так, вы можете сделать следующее:

  • Сортируйте обе таблицы по их идентификатору (используя некоторую полезную вещь, такую ​​как quicksort). Если они уже отсортированы, вы выиграете большой.
  • Пройдите через обе таблицы сразу, пропуская любые пробелы в ID в любой таблице. Средние дублированные записи совпадающего идентификатора.

Это позволяет вам сделать это (время сортировки * 2) + один проход, поэтому, если моя большая O-нотация правильная, это будет (независимо от времени сортировки) + O (m + n), что довольно хорошо ,
(Пересмотр: это подход, который описывает TΖΩΤΖΙΟΥ )

2: Альтернативный подход, который может быть более или менее эффективным в зависимости от того, насколько велики ваши данные:

  • Запустите таблицу 1 и для каждой строки вставьте его идентификатор (или вычисленный hash-код или какой-либо другой уникальный идентификатор для этой строки) в словарь (или hash-таблицу, если вы предпочитаете называть это).
  • Запустите таблицу 2 и для каждой строки посмотрите, есть ли в словаре словарь (или hash-код и т. Д.). Вы используете тот факт, что словари действительно быстрые – O (1) Я думаю? Погляди. Этот шаг будет очень быстрым, но вы заплатите цену за все эти словарные вставки.

Мне было бы очень интересно узнать, что люди с лучшими знаниями алгоритмов, чем я, придумали для этого 🙂

Предполагая, что у вас есть столбец идентификатора, который имеет соответствующий тип (т. Е. Дает hash-код и реализует равенство) – строка в этом примере, которая немного псевдокода, потому что я не знаком с DataTables и не имею времени, чтобы посмотреть все только сейчас 🙂

 IEnumerable idsInA = tableA.AsEnumerable().Select(row => (string)row["ID"]); IEnumerable idsInB = tableB.AsEnumerable().Select(row => (string)row["ID"]); IEnumerable bNotA = idsInB.Except(idsInA); 

Вы можете использовать методы Merge и GetChanges в DataTable для этого:

 A.Merge(B); // this will add to A any records that are in B but not A return A.GetChanges(); // returns records originally only in B 

Ответы до сих пор предполагают, что вы просто ищете дублирующие первичные ключи. Это довольно простая проблема – вы можете использовать метод Merge (), например.

Но я понимаю, что ваш вопрос означает, что вы ищете дубликаты DataRows. (Из вашего описания проблемы, когда обе таблицы импортируются из файлов CSV, я бы даже предположил, что исходные строки не имеют значений первичного ключа и что любые первичные ключи назначаются через AutoNumber во время импорта.)

Наивная реализация (для каждой строки в A, сравните ее ItemArray с позицией каждой строки в B) действительно будет дорого вычислительной.

Гораздо менее дорогой способ сделать это – с помощью алгоритма hashирования. Для каждого DataRow объединяйте строковые значения его столбцов в одну строку, а затем вызывайте GetHashCode () в этой строке, чтобы получить значение int. Создайте Dictionary , содержащий запись с hash-кодом для каждого DataRow в DataTable B. Затем для каждого DataRow в DataTable A вычислите hash-код и посмотрите, содержится ли он в словаре. Если это не так, вы знаете, что DataRow не существует в DataTable B.

Этот подход имеет две слабые стороны, которые возникают из-за того, что две строки могут быть неравными, но создают один и тот же хеш-код. Если вы найдете строку в A, чей hash находится в словаре, вам необходимо проверить DataRow в словаре, чтобы убедиться, что две строки действительно равны.

Вторая слабость более серьезная: маловероятно, но возможно, что два разных DataRows в B могут хешировать до одного и того же ключевого значения. По этой причине словарь действительно должен быть Dictionary> , и вы должны выполнить проверку, описанную в предыдущем абзаце, против каждого DataRow в списке.

Для получения этой работы требуется немалая работа, но это алгоритм O (m + n), который, я думаю, будет таким же хорошим, как и он.

Просто FYI:

Говоря об алгоритмах, сравнение двух наборов сортируемых (как правило, это идентификаторы) не является операцией O (M * N / 2), а O (M + N), если эти два набора упорядочены. Таким образом, вы просматриваете одну таблицу с указателем на начало другого и:

 other_item= A.first() only_in_B= empty_list() for item in B: while other_item > item: other_item= A.next() if A.eof(): only_in_B.add( all the remaining B items) return only_in_B if item < other_item: empty_list.append(item) return only_in_B 

Вышеприведенный код явно псевдокод, но должен дать вам общий смысл, если вы решите сам его закодировать.

Спасибо всем за отзывы.

К сожалению, у меня нет никакого индекса. Я дам немного больше информации о своей ситуации.

У нас есть программа отчетности (заменена Crystal Reports), которая установлена ​​на 7 серверах по всему ЕС. На этих серверах есть много сообщений о них (не все одинаково для каждой страны). Они вызываются приложением командной строки, которое использует XML-файлы для их конфигурации. Таким образом, один XML-файл может вызывать несколько отчетов.

Приложение командной строки запланировано и контролируется нашим ночным процессом. Таким образом, XML-файл можно было бы вызывать из нескольких мест.

objectiveю CSV является составление списка всех отчетов, которые используются и от которых они вызывают.

Я просматриваю файлы XML для всех ссылок, запрашивая программу планирования и составляя список всех отчетов. (это не так уж плохо).

Проблема, которую я имею, – это сохранить список всех отчетов, которые могли быть удалены из производства. Поэтому мне нужно сравнить старый CSV с новыми данными. Для этого я подумал, что лучше всего поместить его в DataTables и сравнить информацию (это может быть неправильный подход. Предположим, я мог бы создать объект, который его удерживает, и сравнивает разницу, а затем создает итерацию через них).

Данные, которые я имею о каждом отчете, следующие:

String – Название задачи String – Имя действия Int – ActionID (идентификатор действия может быть в нескольких записях, поскольку одно действие может вызывать многие отчеты, то есть XML-файл). String – XML-файл под названием String – название отчета

Я попробую идею слияния, данную MusiGenesis (спасибо). (перечитывая некоторые из сообщений, не уверенных в том, что Merge будет работать, но стоит попробовать, поскольку я не слышал об этом раньше, чем что-то новое для изучения).

Идея HashCode также интересна.

Спасибо за все советы.

Я нашел простой способ решить эту проблему. В отличие от предыдущих ответов «за исключением метода», я дважды использую метод except. Это не только говорит о том, какие строки были удалены, но какие строки были добавлены. Если вы используете только один метод – он скажет только одно различие, а не то, и другое. Этот код проверен и работает. Смотри ниже

 //Pass in your two datatables into your method //build the queries based on id. var qry1 = datatable1.AsEnumerable().Select(a => new { ID = a["ID"].ToString() }); var qry2 = datatable2.AsEnumerable().Select(b => new { ID = b["ID"].ToString() }); //detect row deletes - a row is in datatable1 except missing from datatable2 var exceptAB = qry1.Except(qry2); //detect row inserts - a row is in datatable2 except missing from datatable1 var exceptAB2 = qry2.Except(qry1); 

затем выполните свой код против результатов

  if (exceptAB.Any()) { foreach (var id in exceptAB) { //execute code here } } if (exceptAB2.Any()) { foreach (var id in exceptAB2) { //execute code here } } 
 public DataTable compareDataTables(DataTable First, DataTable Second) { First.TableName = "FirstTable"; Second.TableName = "SecondTable"; //Create Empty Table DataTable table = new DataTable("Difference"); DataTable table1 = new DataTable(); try { //Must use a Dataset to make use of a DataRelation object using (DataSet ds4 = new DataSet()) { //Add tables ds4.Tables.AddRange(new DataTable[] { First.Copy(), Second.Copy() }); //Get Columns for DataRelation DataColumn[] firstcolumns = new DataColumn[ds4.Tables[0].Columns.Count]; for (int i = 0; i < firstcolumns.Length; i++) { firstcolumns[i] = ds4.Tables[0].Columns[i]; } DataColumn[] secondcolumns = new DataColumn[ds4.Tables[1].Columns.Count]; for (int i = 0; i < secondcolumns.Length; i++) { secondcolumns[i] = ds4.Tables[1].Columns[i]; } //Create DataRelation DataRelation r = new DataRelation(string.Empty, firstcolumns, secondcolumns, false); ds4.Relations.Add(r); //Create columns for return table for (int i = 0; i < First.Columns.Count; i++) { table.Columns.Add(First.Columns[i].ColumnName, First.Columns[i].DataType); } //If First Row not in Second, Add to return table. table.BeginLoadData(); foreach (DataRow parentrow in ds4.Tables[0].Rows) { DataRow[] childrows = parentrow.GetChildRows(r); if (childrows == null || childrows.Length == 0) table.LoadDataRow(parentrow.ItemArray, true); table1.LoadDataRow(childrows, false); } table.EndLoadData(); } } catch (Exception ex) { Console.WriteLine(ex.Message); } return table; } 
  try { if (ds.Tables[0].Columns.Count == ds1.Tables[0].Columns.Count) { for (int i = 0; i < ds.Tables[0].Rows.Count; i++) { for (int j = 0; j < ds.Tables[0].Columns.Count; j++) { if (ds.Tables[0].Rows[i][j].ToString() == ds1.Tables[0].Rows[i][j].ToString()) { } else { MessageBox.Show(i.ToString() + "," + j.ToString()); } } } } else { MessageBox.Show("Table has different columns "); } } catch (Exception) { MessageBox.Show("Please select The Table"); } 

Я продолжаю идею тзота …

Если у вас есть два сортируемых набора, вы можете просто использовать:

 List diffList = new List(sortedListA.Except(sortedListB)); 

Если вам нужны более сложные объекты, вы можете сами определить компаратор и использовать его.

В обычном сценарии использования учитывается пользователь, у которого есть DataTable в руке и изменяет его, DataRows или DataRows некоторые из DataRows .

После того, как изменения будут выполнены, DataTable будет знать правильный DataRowState для каждой строки, а также отслеживает Original DataRowVersion для любых строк, которые были изменены.

В этом обычном сценарии можно Merge изменения обратно в исходную таблицу (в которой все строки не Unchanged ). После слияния можно получить хорошее резюме только измененных строк с вызовом GetChanges() .

В более необычном сценарии пользователь имеет две DataTables с одной и той же схемой (или, возможно, только те же столбцы и без первичных ключей). Эти две DataTables состоят только из строк без Unchanged . Пользователь может захотеть выяснить, какие изменения необходимо применить к одной из двух таблиц, чтобы перейти к другой. То есть, какие строки должны быть добавлены, удалены или изменены.

Мы определяем здесь функцию, называемую GetDelta() которая выполняет задание:

 using System; using System.Data; using System.Xml; using System.Linq; using System.Collections.Generic; using System.Data.DataSetExtensions; public class Program { private static DataTable GetDelta(DataTable table1, DataTable table2) { // Modified2 : row1 keys match rowOther keys AND row1 does not match row2: IEnumerable modified2 = ( from row1 in table1.AsEnumerable() from row2 in table2.AsEnumerable() where table1.PrimaryKey.Aggregate(true, (boolAggregate, keycol) => boolAggregate & row1[keycol].Equals(row2[keycol.Ordinal])) && !row1.ItemArray.SequenceEqual(row2.ItemArray) select row2); // Modified1 : IEnumerable modified1 = ( from row1 in table1.AsEnumerable() from row2 in table2.AsEnumerable() where table1.PrimaryKey.Aggregate(true, (boolAggregate, keycol) => boolAggregate & row1[keycol].Equals(row2[keycol.Ordinal])) && !row1.ItemArray.SequenceEqual(row2.ItemArray) select row1); // Added : row2 not in table1 AND row2 not in modified2 IEnumerable added = table2.AsEnumerable().Except(modified2, DataRowComparer.Default).Except(table1.AsEnumerable(), DataRowComparer.Default); // Deleted : row1 not in row2 AND row1 not in modified1 IEnumerable deleted = table1.AsEnumerable().Except(modified1, DataRowComparer.Default).Except(table2.AsEnumerable(), DataRowComparer.Default); Console.WriteLine(); Console.WriteLine("modified count =" + modified1.Count()); Console.WriteLine("added count =" + added.Count()); Console.WriteLine("deleted count =" + deleted.Count()); DataTable deltas = table1.Clone(); foreach (DataRow row in modified2) { // Match the unmodified version of the row via the PrimaryKey DataRow matchIn1 = modified1.Where(row1 => table1.PrimaryKey.Aggregate(true, (boolAggregate, keycol) => boolAggregate & row1[keycol].Equals(row[keycol.Ordinal]))).First(); DataRow newRow = deltas.NewRow(); // Set the row with the original values foreach(DataColumn dc in deltas.Columns) newRow[dc.ColumnName] = matchIn1[dc.ColumnName]; deltas.Rows.Add(newRow); newRow.AcceptChanges(); // Set the modified values foreach (DataColumn dc in deltas.Columns) newRow[dc.ColumnName] = row[dc.ColumnName]; // At this point newRow.DataRowState should be : Modified } foreach (DataRow row in added) { DataRow newRow = deltas.NewRow(); foreach (DataColumn dc in deltas.Columns) newRow[dc.ColumnName] = row[dc.ColumnName]; deltas.Rows.Add(newRow); // At this point newRow.DataRowState should be : Added } foreach (DataRow row in deleted) { DataRow newRow = deltas.NewRow(); foreach (DataColumn dc in deltas.Columns) newRow[dc.ColumnName] = row[dc.ColumnName]; deltas.Rows.Add(newRow); newRow.AcceptChanges(); newRow.Delete(); // At this point newRow.DataRowState should be : Deleted } return deltas; } private static void DemonstrateGetDelta() { DataTable table1 = new DataTable("Items"); // Add columns DataColumn column1 = new DataColumn("id1", typeof(System.Int32)); DataColumn column2 = new DataColumn("id2", typeof(System.Int32)); DataColumn column3 = new DataColumn("item", typeof(System.Int32)); table1.Columns.Add(column1); table1.Columns.Add(column2); table1.Columns.Add(column3); // Set the primary key column. table1.PrimaryKey = new DataColumn[] { column1, column2 }; // Add some rows. DataRow row; for (int i = 0; i <= 4; i++) { row = table1.NewRow(); row["id1"] = i; row["id2"] = i*i; row["item"] = i; table1.Rows.Add(row); } // Accept changes. table1.AcceptChanges(); PrintValues(table1, "table1:"); // Create a second DataTable identical to the first. DataTable table2 = table1.Clone(); // Add a row that exists in table1: row = table2.NewRow(); row["id1"] = 0; row["id2"] = 0; row["item"] = 0; table2.Rows.Add(row); // Modify the values of a row that exists in table1: row = table2.NewRow(); row["id1"] = 1; row["id2"] = 1; row["item"] = 455; table2.Rows.Add(row); // Modify the values of a row that exists in table1: row = table2.NewRow(); row["id1"] = 2; row["id2"] = 4; row["item"] = 555; table2.Rows.Add(row); // Add a row that does not exist in table1: row = table2.NewRow(); row["id1"] = 13; row["id2"] = 169; row["item"] = 655; table2.Rows.Add(row); table2.AcceptChanges(); Console.WriteLine(); PrintValues(table2, "table2:"); DataTable delta = GetDelta(table1,table2); Console.WriteLine(); PrintValues(delta,"delta:"); // Verify that the deltas DataTable contains the adequate Original DataRowVersions: DataTable originals = table1.Clone(); foreach (DataRow drow in delta.Rows) { if (drow.RowState != DataRowState.Added) { DataRow originalRow = originals.NewRow(); foreach (DataColumn dc in originals.Columns) originalRow[dc.ColumnName] = drow[dc.ColumnName, DataRowVersion.Original]; originals.Rows.Add(originalRow); } } originals.AcceptChanges(); Console.WriteLine(); PrintValues(originals,"delta original values:"); } private static void Row_Changed(object sender, DataRowChangeEventArgs e) { Console.WriteLine("Row changed {0}\t{1}", e.Action, e.Row.ItemArray[0]); } private static void PrintValues(DataTable table, string label) { // Display the values in the supplied DataTable: Console.WriteLine(label); foreach (DataRow row in table.Rows) { foreach (DataColumn col in table.Columns) { Console.Write("\t " + row[col, row.RowState == DataRowState.Deleted ? DataRowVersion.Original : DataRowVersion.Current].ToString()); } Console.Write("\t DataRowState =" + row.RowState); Console.WriteLine(); } } public static void Main() { DemonstrateGetDelta(); } } 

Вышеприведенный код может быть протестирован в https://dotnetfiddle.net/ . Полученный результат показан ниже:

 table1: 0 0 0 DataRowState =Unchanged 1 1 1 DataRowState =Unchanged 2 4 2 DataRowState =Unchanged 3 9 3 DataRowState =Unchanged 4 16 4 DataRowState =Unchanged table2: 0 0 0 DataRowState =Unchanged 1 1 455 DataRowState =Unchanged 2 4 555 DataRowState =Unchanged 13 169 655 DataRowState =Unchanged modified count =2 added count =1 deleted count =2 delta: 1 1 455 DataRowState =Modified 2 4 555 DataRowState =Modified 13 169 655 DataRowState =Added 3 9 3 DataRowState =Deleted 4 16 4 DataRowState =Deleted delta original values: 1 1 1 DataRowState =Unchanged 2 4 2 DataRowState =Unchanged 3 9 3 DataRowState =Unchanged 4 16 4 DataRowState =Unchanged 

Обратите внимание, что если ваши таблицы не имеют PrimaryKey , предложение where в запросах LINQ немного упрощается. Я позволю тебе понять это самостоятельно.

Достигайте его просто используя linq.

 private DataTable CompareDT(DataTable TableA, DataTable TableB) { DataTable TableC = new DataTable(); try { var idsNotInB = TableA.AsEnumerable().Select(r => r.Field(Keyfield)) .Except(TableB.AsEnumerable().Select(r => r.Field(Keyfield))); TableC = (from row in TableA.AsEnumerable() join id in idsNotInB on row.Field(ddlColumn.SelectedItem.ToString()) equals id select row).CopyToDataTable(); } catch (Exception ex) { lblresult.Text = ex.Message; ex = null; } return TableC; } 

Не могли бы вы просто сравнить файлы CSV перед их загрузкой в ​​DataTables?

 string[] a = System.IO.File.ReadAllLines(@"cvs_a.txt"); string[] b = System.IO.File.ReadAllLines(@"csv_b.txt"); // get the lines from b that are not in a IEnumerable diff = b.Except(a); //... parse b into DataTable ... 
  • Слияние 2 DataTables и сохранение в новом
  • Как читать файл CSV в .NET Datatable
  • Преобразование DataTable в общий список в C #
  • Эффективная группа DataTable
  • Как мне перебирать строки с помощью устройства чтения данных в C #?
  • Запрос LINQ в DataTable
  • Как сравнить 2 таблицы данных
  • p: dataExporter не распознает p: cellEditor
  • Использование java.util.Map в h: dataTable
  • Как выбрать различные строки в datatable и хранить в массиве
  • Почему или не доступен ?
  • Interesting Posts

    Отключить стрелки страницы поиска Google, кроме результатов при прокрутке вверх / вниз с помощью клавиатуры

    Пароль сброса идентификатора ASP.NET

    GCC и предварительно скомпилированные заголовки

    Дополнительные параметры C # для переопределенных методов

    Диалоговое изображение с четким фоном (не затемнено)

    Изменение стиля текста в стиле tabhost Android

    Почему для нулевого указателя используется нулевой адрес?

    Как реализовать эффект масштабирования для представления изображения в Android?

    Angular2: Невозможно прочитать имя свойства «неопределенного»

    Контроллер лесов не работает с обновлением Visual Studio 2013 2

    Есть ли клиент BitTorrent, который может загружать файлы «последовательно»?

    Все команды не работают в Mac OS X Lion

    Есть ли DVI-конвертер с аудио?

    Как обновить ОС на Mac Mini с помощью внешнего USB-накопителя?

    Сеанс удаленного рабочего стола закрывается сразу после входа в систему

    Давайте будем гением компьютера.