Pregunta

Tengo dos tablas de datos, A y B, producido a partir de archivos CSV.Necesito poder comprobar qué filas existen en B que no existen en A.

¿Hay alguna manera de hacer algún tipo de consulta para mostrar las diferentes filas o tendría que recorrer cada fila en cada DataTable para verificar si son iguales?Esta última opción parece muy intensiva si las mesas se vuelven grandes.

¿Fue útil?

Solución

  

tendría que iterar a través de cada fila en cada DataTable para verificar si son iguales.

Al ver que ha cargado los datos de un archivo CSV, no tendrá ningún índice ni nada, por lo que en algún momento, algo tendrá que recorrer cada fila, ya sea su código, o una biblioteca, o lo que sea.

De todos modos, esta es una pregunta de algoritmos, que no es mi especialidad, pero mi enfoque ingenuo sería el siguiente:

1: ¿Puede explotar alguna propiedad de los datos? ¿Todas las filas de cada tabla son únicas y puede ordenarlas por el mismo criterio? Si es así, puede hacer esto:

  • Ordene ambas tablas por su ID (usando algo útil como una clasificación rápida). Si ya están ordenados, entonces ganas a lo grande.
  • Pase a través de ambas tablas a la vez, omitiendo las lagunas en las ID en cualquiera de las tablas. ID duplicado significa registros duplicados.

Esto le permite hacerlo en (tiempo de clasificación * 2) + una pasada, por lo que si mi notación O grande es correcta, sería (tiempo de clasificación) + O (m + n) que es bastante bueno.
(Revisión: este es el enfoque que & # 932; & # 918; & # 937; & # 932; & # 918; & # 921; & # 927; & # 933; describe )

2: Un enfoque alternativo, que puede ser más o menos eficiente según el tamaño de sus datos:

  • Ejecute la tabla 1 y, para cada fila, pegue su ID (o código hash calculado o alguna otra ID única para esa fila) en un diccionario (o tabla hash si prefiere llamarlo así).
  • Ejecute la tabla 2 y, para cada fila, vea si el ID (o código hash, etc.) está presente en el diccionario. Estás explotando el hecho de que los diccionarios son realmente rápidos - O (1) creo? buscar. Este paso será realmente rápido, pero habrá pagado el precio haciendo todas esas inserciones de diccionario.

Me interesaría mucho ver qué personas con mejor conocimiento de algoritmos que yo inventamos para este :-)

Otros consejos

Suponiendo que tiene una columna de ID que es del tipo apropiado (es decir, proporciona un código hash e implementa la igualdad): cadena en este ejemplo, que es un poco pseudocódigo porque no estoy tan familiarizado con DataTables y no tengo tiempo para búscalo todo ahora :)

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

Puede usar los métodos Merge y GetChanges en DataTable para hacer esto:

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

Las respuestas hasta ahora suponen que simplemente está buscando claves primarias duplicadas. Ese es un problema bastante fácil: puede usar el método Merge (), por ejemplo.

Pero entiendo que su pregunta significa que está buscando DataRows duplicados. (Según su descripción del problema, con ambas tablas importadas de archivos CSV, incluso supondría que las filas originales no tenían valores de clave primaria, y que cualquier clave primaria se asignaba a través de AutoNumber durante la importación).

La implementación ingenua (para cada fila en A, compare su ItemArray con la de cada fila en B) va a ser computacionalmente costosa.

Una forma mucho menos costosa de hacer esto es con un algoritmo de hash. Para cada DataRow, concatene los valores de cadena de sus columnas en una sola cadena y luego llame a GetHashCode () en esa cadena para obtener un valor int. Cree un Dictionary<int, DataRow> que contenga una entrada, tecleada en el código hash, para cada DataRow en DataTable B. Luego, para cada DataRow en DataTable A, calcule el código hash y vea si está contenido en el diccionario. Si no es así, sabe que DataRow no existe en DataTable B.

Este enfoque tiene dos puntos débiles que surgen del hecho de que dos cadenas pueden ser desiguales pero producir el mismo código hash. Si encuentra una fila en A cuyo hash está en el diccionario, debe verificar la DataRow en el diccionario para verificar que las dos filas sean realmente iguales.

La segunda debilidad es más grave: es poco probable, pero posible, que dos DataRows diferentes en B puedan generar el mismo valor clave. Por esta razón, el diccionario debería ser realmente un Dictionary<int, List<DataRow>>, y debe realizar la verificación descrita en el párrafo anterior con cada DataRow en la lista.

Se necesita una gran cantidad de trabajo para que esto funcione, pero es un algoritmo O (m + n), que creo que será tan bueno como sea posible.

Solo para tu información:

Generalmente hablando de algoritmos, comparar dos conjuntos de ordenables (como suelen ser los identificadores) no es una operación O (M * N / 2), sino O (M + N) si los dos conjuntos están ordenados. Así que escanea una tabla con un puntero al comienzo de la otra y:

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

El código anterior es obviamente pseudocódigo, pero debería darle una idea general si decide codificarlo usted mismo.

Gracias por todos los comentarios.

Desafortunadamente no tengo ningún índice. Daré un poco más de información sobre mi situación.

Tenemos un programa de informes (informes de Crystal reemplazados) que está instalado en 7 servidores en toda la UE. Estos servidores tienen muchos informes sobre ellos (no todos iguales para cada país). Los invoca una aplicación de línea de comandos que utiliza archivos XML para su configuración. Por lo tanto, un archivo XML puede llamar a varios informes.

La aplicación de línea de comandos está programada y controlada por nuestro proceso nocturno. Por lo tanto, el archivo XML podría llamarse desde varios lugares.

El objetivo del CSV es producir una lista de todos los informes que se están utilizando y de dónde se los llama.

Estoy revisando los archivos XML para todas las referencias, consultando el programa de programación y produciendo una lista de todos los informes. (esto no es tan malo).

El problema que tengo es que tengo que mantener una lista de todos los informes que podrían haberse eliminado de la producción. Así que necesito comparar el viejo CSV con los nuevos datos. Para esto, pensé que era mejor ponerlo en DataTables y comparar la información (este podría ser el enfoque incorrecto. Supongo que podría crear un objeto que lo contenga y comparar la diferencia y luego crear iterar a través de ellos).

Los datos que tengo sobre cada informe son los siguientes:

Cadena - Nombre de la tarea Cadena - Nombre de acción Int - ActionID (el ID de acción puede estar en varios registros ya que una sola acción puede llamar a muchos informes, es decir, un archivo XML). Cadena: archivo XML llamado Cadena - Nombre del informe

Probaré la idea Merge dada por MusiGenesis (gracias). (volviendo a leer algunas de las publicaciones no estoy seguro de si la combinación funcionará, pero vale la pena intentarlo, ya que no he oído hablar de él antes, así que hay algo nuevo que aprender).

La Idea HashCode también suena interesante.

Gracias por todos los consejos.

Encontré una manera fácil de resolver esto. A diferencia de & Quot; excepto el método & Quot; respuestas, yo uso el método excepto dos veces. Esto no solo le dice qué filas se eliminaron sino qué filas se agregaron. Si solo usa uno excepto el método, solo le dirá una diferencia y no ambas. Este código está probado y funciona. Ver abajo

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

luego ejecute su código contra los resultados

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


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



        }

¿No podría simplemente comparar los archivos CSV antes de cargarlos en 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");
        }

Sigo con la idea de tzot ...

Si tiene dos conjuntos ordenables, puede usar & # 65306;

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

Si necesita objetos más complicados, puede definir un comparador usted mismo y seguir usándolo.

El escenario de uso habitual considera a un usuario que tiene un DataTable en la mano y lo cambia agregando, eliminando o modificando algunos de los DataRows.

Después de que se realizan los cambios, el DataRowState es consciente del Original apropiado para cada fila, y también realiza un seguimiento del DataRowVersion Merge para cualquier fila que se haya cambiado.

En este escenario habitual, uno puede Unchanged los cambios nuevamente en una tabla fuente (en la que todas las filas son GetChanges()). Después de fusionar, se puede obtener un buen resumen de solo las filas modificadas con una llamada a DataTables.

En un escenario más inusual, un usuario tiene dos GetDelta() con el mismo esquema (o tal vez solo las mismas columnas y sin claves principales). Estas dos PrimaryKey consisten solo en where filas. El usuario puede querer averiguar qué cambios necesita aplicar a una de las dos tablas para llegar a la otra. Es decir, qué filas deben agregarse, eliminarse o modificarse.

Definimos aquí una función llamada <=> que hace el trabajo:

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

El código anterior se puede probar en https://dotnetfiddle.net/ . El resultado resultante se muestra a continuación:

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

Tenga en cuenta que si sus tablas no tienen una <=>, la cláusula <=> en las consultas LINQ se simplifica un poco. Dejaré que lo descubras por tu cuenta.

Logra simplemente 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 bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top