Domanda

Ho due DataTable, A e B, prodotti da file CSV. Devo essere in grado di verificare quali righe esistono in <=> che non esistono in <=>.

Esiste un modo per eseguire una sorta di query per mostrare le diverse righe o dovrei scorrere tra le righe su ciascuna DataTable per verificare se sono uguali? Quest'ultima opzione sembra essere molto intensa se le tabelle diventano grandi.

È stato utile?

Soluzione

  

dovrei scorrere tutte le righe su ogni DataTable per verificare se sono uguali.

Visto che hai caricato i dati da un file CSV, non avrai alcun indice o altro, quindi ad un certo punto qualcosa dovrà scorrere attraverso ogni riga, sia esso il tuo codice, o una biblioteca, o altro.

Comunque, questa è una domanda sugli algoritmi, che non è la mia specialità, ma il mio approccio ingenuo sarebbe il seguente:

1: puoi sfruttare le proprietà dei dati? Tutte le righe di ogni tabella sono uniche e puoi ordinarle entrambe in base agli stessi criteri? In tal caso, puoi farlo:

  • Ordina entrambe le tabelle in base al loro ID (usando alcune cose utili come un quicksort). Se sono già ordinati, allora vinci alla grande.
  • Scorri contemporaneamente entrambe le tabelle, saltando eventuali lacune negli ID in entrambe le tabelle. Media duplicati dell'ID con corrispondenza.

Questo ti permette di farlo in (tempo di ordinamento * 2) + un passaggio, quindi se la mia notazione O-grande è corretta, sarebbe (qualunque-tempo-ordinamento) + O (m + n) quale è abbastanza buono.
(Revisione: questo è l'approccio che <> # 932;! <> # 918;! <> # 937;! <> # 932;! <> # 918;!! <> # 921; & # 927; & # 933; descrive )

2: un approccio alternativo, che può essere più o meno efficiente a seconda della dimensione dei tuoi dati:

  • Esegui la tabella 1 e, per ogni riga, inserisci il suo ID (o hashcode calcolato o qualche altro ID univoco per quella riga) in un dizionario (o hashtable se preferisci chiamarlo così).
  • Esegui la tabella 2 e, per ogni riga, controlla se l'ID (o hashcode ecc.) è presente nel dizionario. Stai sfruttando il fatto che i dizionari hanno molto velocemente - O (1) penso? consultare. Questo passaggio sarà molto veloce, ma avrai pagato il prezzo facendo tutti quegli inserti nel dizionario.

Sarei davvero interessato a vedere ciò che le persone con una migliore conoscenza degli algoritmi rispetto a me escogitano per questo :-)

Altri suggerimenti

Supponendo che tu abbia una colonna ID che è di un tipo appropriato (cioè fornisce un hashcode e implementa l'uguaglianza) - stringa in questo esempio, che è leggermente pseudocodice perché non ho familiarità con DataTables e non ho tempo di cerca tutto ora :)

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

È possibile utilizzare i metodi Merge e GetChanges su DataTable per eseguire questa operazione:

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

Le risposte finora presuppongono che stai semplicemente cercando chiavi primarie duplicate. Questo è un problema abbastanza semplice: puoi usare il metodo Merge (), per esempio.

Ma capisco la tua domanda nel senso che stai cercando DataRows duplicati. (Dalla tua descrizione del problema, con entrambe le tabelle importate da file CSV, suppongo persino che le righe originali non abbiano valori di chiave primaria e che eventuali chiavi primarie vengano assegnate tramite AutoNumber durante l'importazione.)

L'implementazione ingenua (per ogni riga in A, confronta il suo ItemArray con quello di ogni riga in B) sarà effettivamente computazionalmente costosa.

Un modo molto meno costoso per farlo è con un algoritmo di hashing. Per ciascun DataRow, concatenare i valori di stringa delle sue colonne in una singola stringa, quindi chiamare GetHashCode () su quella stringa per ottenere un valore int. Creare un Dictionary<int, DataRow> che contenga una voce, digitata sul codice hash, per ciascun DataRow nella DataTable B. Quindi, per ciascun DataRow nella DataTable A, calcolare il codice hash e vedere se è contenuto nel dizionario. In caso contrario, sai che DataRow non esiste in DataTable B.

Questo approccio ha due punti deboli che emergono entrambi dal fatto che due stringhe possono essere disuguali ma produrre lo stesso codice hash. Se trovi una riga in A il cui hash è nel dizionario, devi controllare DataRow nel dizionario per verificare che le due righe siano davvero uguali.

Il secondo punto debole è più grave: è improbabile, ma possibile, che due diversi DataRows in B possano avere lo stesso valore chiave. Per questo motivo, il dizionario dovrebbe essere in realtà un Dictionary<int, List<DataRow>> e dovresti eseguire il controllo descritto nel paragrafo precedente su ciascun DataRow nell'elenco.

Ci vuole un bel po 'di lavoro per farlo funzionare, ma è un algoritmo O (m + n), che penso sarà buono come si arriva.

Solo FYI:

Parlando generalmente di algoritmi, il confronto tra due insiemi di ordinabili (come in genere sono gli id) non è un'operazione O (M * N / 2), ma O (M + N) se i due insiemi sono ordinati. Quindi scansiona una tabella con un puntatore all'inizio dell'altra 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

Il codice sopra è ovviamente pseudocodice, ma dovrebbe darti un'idea generale se decidi di codificarlo da solo.

Grazie per tutto il feedback.

Purtroppo non ho alcun indice. Darò qualche informazione in più sulla mia situazione.

Abbiamo un programma di reporting (sostituito report Crystal) installato in 7 server in tutta l'UE. Questi server hanno molti rapporti (non tutti uguali per ciascun paese). Sono invocati da un'applicazione a riga di comando che utilizza file XML per la loro configurazione. Quindi un file XML può chiamare più report.

L'applicazione della riga di comando è programmata e controllata dal nostro processo durante la notte. Quindi il file XML potrebbe essere chiamato da più posizioni.

L'obiettivo del CSV è quello di produrre un elenco di tutti i report che vengono utilizzati e da dove vengono chiamati.

Sto esaminando i file XML per tutti i riferimenti, interrogando il programma di pianificazione e producendo un elenco di tutti i rapporti. (questo non è poi così male).

Il problema che ho è che devo tenere un elenco di tutti i report che potrebbero essere stati rimossi dalla produzione. Quindi ho bisogno di confrontare il vecchio CSV con i nuovi dati. Per questo ho pensato che fosse meglio metterlo in DataTables e confrontare le informazioni, (questo potrebbe essere l'approccio sbagliato. Suppongo che potrei creare un oggetto che lo trattiene e confronta la differenza per poi crearlo attraverso di essi).

I dati che ho su ogni rapporto sono i seguenti:

Stringa - Nome attività String - Nome azione Int - ActionID (l'ID azione può essere presente in più record poiché un'unica azione può richiamare molti report, ad esempio un file XML). String - File XML chiamato String - Nome rapporto

Proverò l'idea Merge data da MusiGenesis (grazie). (rileggere alcuni dei post non sono sicuro che Merge funzionerà, ma vale la pena provare perché non ne ho mai sentito parlare prima, quindi qualcosa di nuovo da imparare).

Anche l'idea di HashCode sembra interessante.

Grazie per tutti i consigli.

Ho trovato un modo semplice per risolvere questo. A differenza del precedente & Quot; tranne il metodo & Quot; risposte, uso il metodo tranne due volte. Questo non solo ti dice quali righe sono state eliminate ma quali righe sono state aggiunte. Se ne usi solo uno tranne il metodo, ti dirà solo una differenza e non entrambe. Questo codice è testato e funziona. Vedi sotto

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

quindi esegui il tuo codice rispetto ai risultati

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


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



        }

Non potresti semplicemente confrontare i file CSV prima caricandoli in 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");
        }

Sto continuando l'idea di tzot ...

Se hai due set ordinabili, puoi semplicemente usare & # 65306;

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

Se hai bisogno di oggetti più complicati, puoi definire tu stesso un comparatore e usarlo ancora.

Il normale scenario di utilizzo considera un utente che ha in mano un DataTable e lo modifica aggiungendo, eliminando o modificando alcuni dei DataRows.

Dopo aver eseguito le modifiche, DataRowState è a conoscenza del Original corretto per ogni riga e tiene anche traccia di DataRowVersion Merge per tutte le righe che sono state modificate.

In questo scenario normale, è possibile Unchanged tornare alle modifiche in una tabella di origine (in cui tutte le righe sono GetChanges()). Dopo l'unione, si può ottenere un bel riepilogo delle sole righe modificate con una chiamata a DataTables.

In uno scenario più insolito, un utente ha due GetDelta() con lo stesso schema (o forse solo le stesse colonne e mancano le chiavi primarie). Queste due PrimaryKey sono composte solo da where righe. L'utente potrebbe voler scoprire quali modifiche deve applicare a una delle due tabelle per arrivare all'altra. Cioè, quali righe devono essere aggiunte, eliminate o modificate.

Definiamo qui una funzione chiamata <=> che fa il lavoro:

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

Il codice sopra può essere testato in https://dotnetfiddle.net/ . L'output risultante è mostrato di seguito:

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

Nota che se le tue tabelle non hanno un <=>, la clausola <=> nelle query LINQ viene semplificata un po '. Ti lascerò capire da solo.

Raggiungilo semplicemente 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;

    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top