Сравните две таблицы данных, чтобы определить строки в одной, но не в другой - PullRequest
17 голосов
/ 02 октября 2008

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

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

Ответы [ 13 ]

20 голосов
/ 03 октября 2008

Предполагая, что у вас есть столбец идентификатора, который имеет соответствующий тип (т.е. дает хэш-код и реализует равенство) - строка в этом примере, которая является немного псевдокодом, потому что я не очень знаком с 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);
9 голосов
/ 02 октября 2008

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

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

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

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

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

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

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

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

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

7 голосов
/ 02 октября 2008

Вы можете использовать методы 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
4 голосов
/ 03 октября 2008

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

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

1 голос
/ 30 июня 2016

Не могли бы вы просто сравнить файлы 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 ...
1 голос
/ 10 июля 2013

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

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



        }
1 голос
/ 18 марта 2009
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;
}
1 голос
/ 03 октября 2008

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

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

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

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

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

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

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

У меня есть данные о каждом отчете:

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

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

Идея HashCode звучит также интересно.

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

1 голос
/ 02 октября 2008

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

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

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

0 голосов
/ 26 мая 2016

Достигните этого просто используя 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;

    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...