tablas - union datatable c#
Compare dos DataTables para determinar las filas en una pero no en la otra (13)
Tengo dos DataTables, A
y B
, producidos a partir de archivos CSV. Necesito poder verificar qué filas existen en B
que no existen en A
¿Hay alguna forma de hacer algún tipo de consulta para mostrar las diferentes filas o tendré que repetir cada fila en cada DataTable para comprobar si son las mismas? La última opción parece ser muy intensa si las tablas se vuelven grandes.
Gracias por todos los comentarios.
No tengo ningún índice desafortunadamente. Daré un poco más de información sobre mi situación.
Tenemos un programa de informes (informes 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). Son invocados por una aplicación de línea de comandos que usa archivos XML para su configuración. Así que un archivo XML puede llamar a varios informes.
La aplicación de línea de comandos está programada y controlada por nuestro proceso nocturno. Entonces el archivo XML podría ser llamado desde múltiples lugares.
El objetivo del CSV es generar una lista de todos los informes que se usan y de dónde se llaman.
Estoy revisando los archivos XML de 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 debo mantener una lista de todos los informes que pudieron haber sido eliminados de la producción. Entonces 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 equivocado. Supongo que podría crear un objeto que lo tenga y comparar la diferencia y luego crear una iteración a través de ellos).
Los datos que tengo sobre cada informe son los siguientes:
Cadena - Nombre de tarea Cadena - Nombre de acción Int - ID de acción (la ID de acción puede estar en múltiples registros ya que una sola acción puede invocar muchos informes, es decir, un archivo XML). Cadena - Archivo XML llamado String - Nombre del informe
Voy a probar la idea Merge propuesta por MusiGenesis (gracias). (Releyendo 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 ella antes, por lo que hay algo nuevo que aprender).
La Idea HashCode también suena interesante.
Gracias por todos los consejos.
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 tu pregunta significa que estás buscando DataRows duplicados. (A partir de su descripción del problema, con ambas tablas importadas desde archivos CSV, incluso asumiría que las filas originales no tenían valores de clave primaria, y que las claves principales se asignan mediante Autonumérico durante la importación).
La implementación ingenua (para cada fila en A, comparar su ArchivoArtículo con la de cada fila en B) va a ser realmente costosa desde el punto de vista computacional.
Una forma mucho menos costosa de hacer esto es con un algoritmo 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, codificada 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í, usted 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 producen el mismo código hash. Si encuentra una fila en A cuyo hash está en el diccionario, debe verificar el DataRow en el diccionario para verificar que las dos filas sean realmente iguales.
La segunda debilidad es más grave: es poco probable, pero es posible, que dos DataRows diferentes en B puedan hacer hash con el mismo valor clave. Por esta razón, el diccionario debería ser realmente un Dictionary<int, List<DataRow>>
, y debería realizar la comprobación descrita en el párrafo anterior frente a cada DataRow en la lista.
Se necesita una gran cantidad de trabajo para que funcione, pero es un algoritmo O (m + n), que creo que va a ser tan bueno como se pueda.
Solo para tu información:
Hablando en general 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. Entonces 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 la esencia general si decide codificarlo usted mismo.
Suponiendo que tiene una columna ID que es de un tipo apropiado (es decir, da un código hash e implementa igualdad) - string en este ejemplo, que es un pseudocódigo porque no estoy tan familiarizado con DataTables y no tengo tiempo para verlo todo hasta 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
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");
}
Estoy continuando la idea de tzot ...
Si tiene dos conjuntos ordenables, entonces puede usar:
List<string> diffList = new List<string>(sortedListA.Except(sortedListB));
Si necesita objetos más complicados, puede definir un comparador usted mismo y aún así usarlo.
Encontré una manera fácil de resolver esto. A diferencia de las respuestas anteriores de "excepto método", utilizo el método except dos veces. Esto no solo le indica qué filas se eliminaron, sino qué filas se agregaron. Si solo usa uno excepto el método, solo le indicará una diferencia y no ambas. Este código está probado y funciona. Vea 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 ejecuta tu 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
}
}
El escenario de uso habitual considera a un usuario que tiene un DataTable
en la mano y lo cambia agregando, borrando o modificando algunas de las DataRows
.
Después de realizar los cambios, DataTable
conoce el DataRowState
adecuado para cada fila y también realiza un seguimiento de la DataRowVersion
Original
para las filas que se cambiaron.
En este escenario habitual, uno puede Merge
los cambios nuevamente en una tabla fuente (en la que todas las filas permanecen sin Unchanged
). Después de la fusión, uno puede obtener un buen resumen de solo las filas modificadas con una llamada a GetChanges()
.
En un escenario más inusual, un usuario tiene dos DataTables
con el mismo esquema (o quizás solo las mismas columnas y sin claves principales). Estas dos DataTables
consisten solo en filas sin Unchanged
. El usuario puede querer saber 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 GetDelta()
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 PrimaryKey
, la cláusula where
en las consultas LINQ se simplifica un poco. Te dejaré que te des cuenta por tu cuenta.
Alcanzarlo 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;
}
¿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;
}
Tendría que repetir cada fila en cada DataTable para verificar si son iguales.
Al ver que has cargado los datos de un archivo CSV, no vas a tener ningún índice ni nada, por lo que en algún momento, algo tendrá que iterar en cada fila, ya sea tu 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: ¿Puedes explotar cualquier propiedad de los datos? ¿Son únicas todas las filas de cada tabla y puede ordenarlas con los mismos criterios? Si es así, puedes hacer esto:
- Ordene ambas tablas por su ID (usando algo útil como un quicksort). Si ya están clasificados, entonces ganas a lo grande.
- Paso a través de ambas tablas a la vez, omitiendo cualquier brecha en las identificaciones en cualquiera de las tablas. Los identificadores coincidentes significan registros duplicados.
Esto te permite hacerlo en (tiempo de ordenación * 2) + una pasada, por lo tanto, si mi notación en "O" es correcta, sería (lo que sea-ordenar-tiempo) + O (m + n) que es bastante bueno .
(Revisión: este es el enfoque que ΤΖΩΤΖΙΟΥ describe )
2: Un enfoque alternativo, que puede ser más o menos eficiente dependiendo de qué tan grande sea su información:
- Ejecute la tabla 1, y para cada fila, pegue su ID (o código hash calculado, u otra identificación única para esa fila) en un diccionario (o hashtable si prefiere llamar así).
- Ejecute la tabla 2 y, para cada fila, vea si la ID (o código hash, etc.) está presente en el diccionario. Está 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 todos los insertos de diccionario.
Estaría realmente interesado en ver qué personas con un mejor conocimiento de algoritmos que yo se me ocurren para este :-)