Pergunta

Eu tenho duas tabelas de dados, A e B, produzidos a partir de arquivos CSV. Eu preciso ser capaz de verificar quais linhas existem em B que não existem no A.

Existe uma maneira de fazer algum tipo de consulta para mostrar as diferentes linhas ou eu teria que percorrer cada linha em cada DataTable para verificar se eles são os mesmos? A última opção parece ser muito intensa se as tabelas se tornam grandes.

Foi útil?

Solução

eu teria para percorrer cada linha em cada DataTable para verificar se eles são os mesmos.

Já que você carregou os dados de um arquivo CSV, você não vai ter nenhum índice ou nada, então em algum momento, alguma coisa vai ter que percorrer cada linha, quer se trate de seu código, ou uma biblioteca, ou o que quer.

De qualquer forma, esta é uma questão de algoritmos, que não é a minha especialidade, mas a minha abordagem ingênua seria o seguinte:

1: você pode explorar as propriedades dos dados? São todas as linhas em cada tabela única, e você pode classificá-los tanto pelos mesmos critérios? Se assim for, você pode fazer isso:

  • Classificar ambas as tabelas por sua ID (usando alguma coisa útil como um quicksort). Se eles já estão classificadas, então você ganhar muito.
  • Passo através de ambas as tabelas de uma vez, saltando sobre eventuais lacunas na identificação de em qualquer mesa. médios registros duplicados de ID correspondido.

Isso permite que você fazê-lo em (tempo tipo * 2) + uma passagem, por isso, se meu-O-notação grande é correto, seria (o que quer-sort-time) + (m + n) O que é muito bom.
(Revisão: esta é a abordagem que ??O????? descreve )

2: Uma abordagem alternativa, que pode ser mais ou menos eficientes dependendo de quão grande seus dados são:

  • Executar através da tabela 1, e para cada linha, colá-la de ID (ou hashcode computadorizada, ou alguma outra identificação única para essa linha) em um dicionário (ou hashtable se você preferir chamar assim).
  • Executar através da tabela 2, e para cada linha, ver se o ID (ou hashcode etc) está presente no dicionário. Você está explorando o fato de que os dicionários têm muito rápido - O (1) que eu acho? olho para cima. Esta etapa será muito rápido, mas você vai ter pago o preço de fazer todas essas inserções de dicionário.

Eu ficaria muito interessado em ver o que as pessoas com maior conhecimento de algoritmos do que eu chegar a para um presente: -)

Outras dicas

Supondo que você tenha uma coluna de identificação que é de um tipo apropriado (ou seja, dá um hashcode e implementos igualdade) - seqüência neste exemplo, que é ligeiramente pseudocódigo porque eu não sou tão familiarizado com DataTables e não têm tempo para olhar tudo isso agora:)

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);

Você pode usar os métodos de mesclagem e getchanges no DataTable para fazer isso:

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

As respostas até agora supor que você está simplesmente à procura de chaves primárias duplicados. Isso é um problema muito fácil -. Você pode usar o método Merge (), por exemplo

Mas eu entendi sua pergunta para significar que você está procurando DataRows duplicados. (De sua descrição do problema, com as duas tabelas que estão sendo importados de arquivos CSV, eu mesmo assumir que as linhas originais não têm valores de chave primária, e que quaisquer chaves primárias estão sendo atribuído através de numeração automática durante a importação.)

A implementação ingênua (para cada linha A, comparar a sua ItemArray com a de cada linha B) é de fato vai ser computacionalmente caro.

Uma maneira muito menos dispendioso para fazer isso é com um algoritmo de hash. Para cada DataRow, concatenar os valores de cadeia de suas colunas em uma única cadeia, e depois chamar GetHashCode () sobre essa seqüência para obter um valor int. Criar um Dictionary<int, DataRow> que contém uma entrada, chaveadas com o código de hash, para cada DataRow no DataTable B. Então, para cada DataRow no DataTable A, calcular o código hash, e ver se ele está contido no dicionário. Se não for, você sabe que o DataRow não existe no DataTable B.

Esta abordagem tem dois pontos fracos que surgem do fato de que duas cordas pode ser desigual, mas produzem o mesmo código hash. Se você encontrar uma linha em Um cujo hash é no dicionário, então você precisa verificar o DataRow no dicionário para verificar se as duas linhas são realmente iguais.

A segunda fraqueza é mais grave: é improvável, mas possível, que dois DataRows diferentes em B poderia botar para o mesmo valor de chave. Por esta razão, o dicionário deve realmente ser um Dictionary<int, List<DataRow>>, e você deve executar a verificação descrita no parágrafo anterior contra cada DataRow na lista.

É preciso uma quantidade justa de trabalho para começar este trabalho, mas é um O (m + n) algoritmo, que eu acho que vai ser tão bom quanto ele ganha.

Apenas FYI:

De um modo geral cerca de algoritmos, comparando dois conjuntos de classificáveis ??(como IDs são tipicamente) não é um O (M * N / 2) funcionamento, mas O (M + N), se os dois conjuntos são ordenados. Então você digitalizar uma tabela com um ponteiro para o início do outro, e:

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

O código acima é, obviamente, pseudocódigo, mas deve dar-lhe a essência geral, se você decidir código-lo sozinho.

Obrigado por todo o feedback.

Eu não tenho qualquer índice é infelizmente. Vou dar um pouco mais informações sobre a minha situação.

Temos um programa de relatórios (substituído relatórios de cristal) que é instalado em 7 servidores através da UE. Esses servidores têm muitos relatos sobre eles (não são todos iguais para cada país). Eles são invocados por um aplicativo de linha de comando que usa arquivos XML para sua configuração. Então, um arquivo XML pode chamar vários relatórios.

O aplicativo de linha de comando está programado e controlado por nosso processo durante a noite. Portanto, o arquivo XML poderia ser chamado de vários lugares.

O objetivo do CSV é produzir uma lista de todos os relatórios que estão sendo usados ??e onde eles estão sendo chamados a partir.

Eu estou passando os arquivos XML para todas as referências, consultar o programa de agendamento e produzindo uma lista de todos os relatórios. (Isso não é tão ruim).

O problema que tenho é que eu tenho que manter uma lista de todos os relatórios que possam ter sido retirados da produção. Então eu preciso comparar o antigo CSV com os novos dados. Para isso achei melhor colocá-lo em tabelas de dados e comparar as informações, (esta poderia ser a abordagem errada. Acho que eu poderia criar um objeto que mantém e compara a diferença, então, criar iterate através deles).

Os dados que tenho sobre cada relatório é a seguinte:

String - Nome da Tarefa String - Nome Ação Int - ActionID (o ID de ação pode estar em vários registros como uma única ação pode chamar muitos relatórios, ou seja, um arquivo XML). String - arquivo XML chamado String - Nome do relatório

vou tentar a idéia de mesclagem dada pelo MusiGenesis (graças). (Relendo algumas das mensagens não tenho certeza se a Fusão vai funcionar, mas vale a pena tentar como eu não ouvi sobre isso antes, então algo novo para aprender).

Os sons HashCode idéia interessante também.

Obrigado por todos os conselhos.

Eu encontrei uma maneira fácil de resolver isso. Ao contrário de anteriores "exceto método" respostas, eu uso o método, exceto duas vezes. Isto não só lhe diz o que linhas foram excluídos, mas quais linhas foram adicionadas. Se você usar apenas uma exceção método - ele só irá dizer-lhe uma diferença e não ambos. Este código é testado e funciona. Veja abaixo

//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);

, em seguida, executar o seu código contra os resultados

        if (exceptAB.Any())
        {
            foreach (var id in exceptAB)
            {
   //execute code here
            }


        }
        if (exceptAB2.Any())
        {
            foreach (var id in exceptAB2)
            {
//execute code here
            }



        }

Você não poderia simplesmente comparar os arquivos CSV antes carregá-los em tabelas de dados?

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");
        }

Eu estou continuando a ideia de tzot ...

Se você tem dois conjuntos de classificáveis, então você pode apenas usar:

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

Se precisar de objetos mais complicados, você pode definir um comparador si mesmo e ainda usá-lo.

O cenário de uso habitual considera um usuário que tem um DataTable na mão e muda-lo adicionando, excluindo ou modificando alguns dos DataRows.

Após as alterações são realizadas, o DataTable está ciente da DataRowState adequada para cada linha, e também mantém o controle do Original DataRowVersion para quaisquer linhas que foram alteradas.

Neste cenário habitual, pode-se Merge as alterações de volta em uma tabela de origem (em que todas as linhas estão Unchanged). Após a fusão, pode-se obter um resumo agradável de apenas as linhas alteradas com uma chamada para GetChanges().

Em um cenário mais incomum, o usuário tem duas DataTables com o mesmo esquema (ou talvez apenas as mesmas colunas e sem chaves primárias). Estes dois DataTables consistem em apenas as linhas Unchanged. O usuário pode querer descobrir o que muda é que ele precisa para aplicar a uma das duas tabelas, a fim de chegar ao outro. Ou seja, que as linhas precisam ser adicionados, excluídos ou modificado.

Nós definimos aqui uma função chamada GetDelta() que faz o trabalho:

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();
    }
}

O código acima pode ser testado em https://dotnetfiddle.net/ . A saída resultante é apresentada abaixo:

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

Note que, se suas tabelas não tem um PrimaryKey, a cláusula where nas consultas LINQ fica simplificado um pouco. Eu vou deixar você descobrir isso em seu próprio país.

alcançá-lo simplesmente usando 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;

    }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top