比较两个DataTables来确定一个行,而不是另一个

我有两个数据表, AB ,从CSV文件产生。 我需要能够检查B中哪些行不存在于A

有没有办法做某种查询来显示不同的行,或者我必须遍历每个DataTable中的每一行来检查它们是否相同? 如果桌子变大,后一种select似乎非常密集。

我将不得不迭代每个DataTable中的每一行来检查它们是否相同。

看到从CSV文件中加载数据时,你不会有任何索引或任何东西,所以在某些时候,某些东西将不得不迭代遍历每一行,无论是你的代码还是一个库, pipe他呢。

无论如何,这是一个algorithm问题,这不是我的专业,但我的天真方法如下:

1:你可以利用数据的任何属性? 每个表格中的所有行都是唯一的,您可以按照相同的标准对它们进行sorting吗? 如果是这样,你可以这样做:

  • 按照IDsorting两个表(使用一些有用的东西,比如快速sorting)。 如果他们已经sorting,那么你赢了很大。
  • 一次跳过两张表,跳过任何一张表中的ID。 匹配的ID意味着重复的logging。

这允许你在(sorting时间* 2)+一遍的情况下做到这一点,所以如果我的大O符号是正确的,它将是(无论什么时间)+ O(M + N),这是非常好的。
(修订版:这是TZTTT技术描述的方法)

2:另一种方法,根据数据量有多大,效率可能更高或更低:

  • 通过表1运行,并为每一行,坚持它的ID(或计算哈希码,或该行的其他一些唯一的ID)到一个字典(或散列表,如果你喜欢这样做)。
  • 通过表2运行,对于每一行,查看字典中是否存在ID(或散列码等)。 你正在利用字典真的很快的事实 – O(1)我认为? 抬头。 这一步将会非常快,但是您将付出所有字典插入的代价。

我真的很感兴趣,看看有什么人比我自己提出了更好的algorithm知识:-)

假设你有一个适当types的ID列(即给出一个散列码并实现相等) – 在这个例子中的string,这是稍微伪代码,因为我不熟悉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); 

您可以使用DataTable上的Merge和GetChanges方法来执行此操作:

 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 

到目前为止的答案假设你只是在寻找重复的主键。 这是一个非常简单的问题 – 例如,您可以使用Merge()方法。

但我理解你的问题意味着你正在寻找重复的DataRows。 (从你对问题的描述中,两个表格都是从CSV文件导入的,我甚至认为原始行没有主键值,并且在导入过程中通过AutoNumber分配了任何主键。

天真的实现(对于A中的每一行,比较它的ItemArray和B中每一行的那一行)确实将在计算上是昂贵的。

使用散列algorithm的方法要便宜得多。 对于每个DataRow,将其列的string值连接成单个string,然后调用该string的GetHashCode()以获取一个int值。 为DataTable B中的每个DataRow创build一个Dictionary<int, DataRow> ,其中包含一个条目,并input散列码。然后,对DataTable A中的每个DataRow计算散列码,并查看它是否包含在字典中。 如果不是,则知道DataRow不存在于DataTable B.

这种方法有两个缺点,都是由于两个string可能不相等而产生相同的哈希码。 如果在A中find一个散列在字典中的行,则需要检查字典中的DataRow,以validation这两行是否真的相等。

第二个弱点更为严重:B中的两个不同的DataRows可能会散列到相同的键值,但这是不可能的。 出于这个原因,字典应该是一个Dictionary<int, List<DataRow>> ,您应该对列表中的每个DataRow执行上一段中描述的检查。

这需要大量的工作来完成,但是这是一个O(m + n)algorithm,我认为这个algorithm会变得很好。

只是供参考:

一般来说,algorithm比较两组可sorting(如id通常是)不是一个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 

上面的代码显然是伪代码,但如果您决定自己编写代码,则应该给出一般要点。

感谢所有的反馈。

我不幸有任何索引。 我会再提供一些关于我的情况的信息。

我们有一个安装在欧盟7个服务器上的报告程序(取代了Crystal报告)。 这些服务器上有很多报告(每个国家都不一样)。 它们由使用XML文件进行configuration的命令行应用程序调用。 所以一个XML文件可以调用多个报告。

命令行应用程序由我们的隔夜进程安排和控制。 所以XML文件可以从多个地方调用。

CSV的目标是生成所有正在使用的报告的列表以及它们从哪里被调用。

我正在浏览所有引用的XML文件,查询计划程序并生成所有报告的列表。 (这不算太坏)。

我的问题是我必须保留所有可能已经从生产中删除的报告的清单。 所以我需要比较旧的CSV和新的数据。 为此,我认为最好把它放入DataTables并比较信息(这可能是错误的方法,我想我可以创build一个持有它的对象并比较差异,然后通过它们来创build迭代)。

我对每个报告的数据如下:

string – 任务名称string – 操作名称Int – 操作ID(操作ID可以在多个logging中,因为单个操作可以调用许多报告,即XML文件)。 string – 称为string的XML文件 – 报告名称

我会尝试MusiGenesis提供的合并的想法(谢谢)。 (重新读一些post不知道合并是否会奏效,但值得尝试,因为之前我还没有听说过这样的新东西)。

HashCode想法听起来也很有趣。

感谢所有的build议。

我发现一个简单的方法来解决这个问题。 与以前的“除法”答案不同,我使用了两次除法。 这不仅告诉你哪些行被删除,而是添加了哪些行。 如果你只使用一种方法 – 它只会告诉你一个区别,而不是两个。 这段代码已经过testing和工作。 见下文

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

我正在继续tzot的想法…

如果你有两个可sorting的集合,那么你可以使用:

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

如果你需要更复杂的对象,你可以自己定义一个比较器,然后继续使用它。

通常的使用场景考虑手中有DataTable的用户,并通过添加,删除或修改某些DataRows更改它。

执行更改之后, DataTable会知道每行的正确DataRowState ,并且还会跟踪任何已更改行的Original DataRowVersion

在这种通常情况下,可以将更改Merge回源表(其中所有行均未Unchanged )。 合并后,可以通过调用GetChanges()来获得更改的行的很好的总结。

在更不寻常的情况下,用户有两个具有相同模式的DataTables (或者也许只有相同的列,并且缺less主键)。 这两个DataTables只包含未Unchanged行。 用户可能想要找出他需要对两个表中的一个应用什么变化才能到达另一个表。 也就是说,哪些行需要添加,删除或修改。

我们在这里定义一个名为GetDelta()的函数来完成这个工作:

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

上面的代码可以在https://dotnetfiddle.net/中testing。; 结果输出如下所示:

 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 

请注意,如果您的表没有PrimaryKey ,则LINQ查询中的where子句会被简化一点。 我会让你自己解决这个问题。

使用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; } 

你能不能简单地比较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 ...