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

StackOverflow https://stackoverflow.com/questions/164144

  •  03-07-2019
  •  | 
  •  

Вопрос

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

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

Это было полезно?

Решение

должен ли я перебирать каждую строку в каждой таблице данных, чтобы проверить, совпадают ли они?

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

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

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

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

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

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

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

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

Другие советы

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

IEnumerable<string> idsInA = tableA.AsEnumerable().Select(row => (string)row["ID"]);
IEnumerable<string> idsInB = tableB.AsEnumerable().Select(row => (string)row["ID"]);
IEnumerable<string> 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) действительно будет вычислительно дорогой.

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

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

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

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

Просто к вашему сведению:

Вообще говоря, об алгоритмах сравнение двух наборов сортируемых (как правило, идентификаторов) - это не операция 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-файл может быть вызван из нескольких мест.

Цель CSV-файла - создать список всех используемых отчетов и откуда они вызываются.

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

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

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

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

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

Идея хэш-кода тоже звучит интересно.

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

Я нашел простой способ решить эту проблему. В отличие от предыдущего & Quot; кроме метода & Quot; ответы, я использую метод исключения дважды. Это не только говорит вам, какие строки были удалены, но какие строки были добавлены. Если вы используете только один, кроме метода - он покажет вам только одно отличие, а не оба. Этот код протестирован и работает. Смотрите ниже

//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
            }



        }

Не могли бы вы просто сравнить 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<string> diff = b.Except(a);

//... parse b into DataTable ...
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");
        }

Я продолжаю идею Цота ...

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

List<string> diffList = new List<string>(sortedListA.Except(sortedListB));

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

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

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

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

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

Здесь мы определяем функцию с именем <=>, которая выполняет эту работу:

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<DataRow> 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<DataRow> 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<DataRow> added = table2.AsEnumerable().Except(modified2, DataRowComparer.Default).Except(table1.AsEnumerable(), DataRowComparer.Default);

        // Deleted : row1 not in row2 AND row1 not in modified1
        IEnumerable<DataRow> 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

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

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

private DataTable CompareDT(DataTable TableA, DataTable TableB)
    {
        DataTable TableC = new DataTable();
        try
        {

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

    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top