我有两个从 CSV 文件生成的 DataTablesA
和B
。我需要能够检查哪些行存在于哪些行中B
不存在于A
.
有没有办法进行某种查询来显示不同的行,或者我必须遍历每个 DataTable 上的每一行以检查它们是否相同?如果表变大,后一种选择似乎非常密集。
假设你有一个适当类型的 ID 列(即给出一个哈希码并实现相等) - 在这个例子中是字符串,它有点伪代码,因为我对 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 上的每一行以检查它们是否相同。
当您从 CSV 文件加载数据时,您不会有任何索引或任何东西,因此在某些时候,必须遍历每一行,无论是您的代码还是库, 管他呢。
无论如何,这是一个算法问题,这不是我的专长,但我的幼稚方法如下:
1:你能利用数据的任何属性吗?每个表中的所有行是否都是唯一的,您可以按照相同的标准对它们进行排序吗?如果是这样,您可以这样做:
这使您可以在 (sort time * 2 ) + 一次通过中完成,所以如果我的 big-O-notation 是正确的,那就是 (whatever-sort-time) + O(m+n) 非常好.
(修订:这是ΤZΩΤZΙΟΥ描述的方法)
2:另一种方法,它可能或多或少取决于您的数据有多大:
我真的很想看看比我更了解算法的人会为这个提出什么:-)
您可以使用 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 文件导入的,我什至假设原始行没有主键值,并且在导入期间通过自动编号分配了任何主键。)
幼稚的实现(对于 A 中的每一行,将其 ItemArray 与 B 中的每一行进行比较)确实在计算上会很昂贵。
一种更便宜的方法是使用散列算法。对于每个 DataRow,将其列的字符串值连接成一个字符串,然后对该字符串调用 GetHashCode() 以获取一个 int 值。为 DataTable B 中的每个 DataRow创建一个Dictionary<int, DataRow>
包含以哈希码为键的条目。然后,对于 DataTable A 中的每个 DataRow,计算哈希码,并查看它是否包含在字典中。如果不是,您知道 DataRow 在 DataTable B 中不存在。
这种方法有两个弱点,这两个弱点都源于两个字符串可能不相等但产生相同的哈希码。如果您在 A 中找到一行其哈希在字典中,则需要检查字典中的 DataRow 以验证这两行是否真的相等。
第二个弱点更严重:B 中的两个不同 DataRows 不太可能但有可能散列到相同的键值。出于这个原因,字典实际上应该是 a Dictionary<int, List<DataRow>>
,并且您应该对列表中的每个 DataRow 执行上一段中描述的检查。
让它工作需要做大量的工作,但它是一个 O(m+n) 算法,我认为它会尽可能好。
仅供参考:
一般来说,关于算法,比较两组可排序的(通常是 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
上面的代码显然是伪代码,但如果您决定自己编写代码,应该会给您大致的要点。
感谢所有的反馈。
不幸的是,我没有任何索引。我将提供更多关于我的情况的信息。
我们有一个报告程序(替换 Crystal 报告)安装在整个欧盟的 7 台服务器中。这些服务器有很多关于它们的报告(每个国家/地区并不完全相同)。它们由使用 XML 文件进行配置的命令行应用程序调用。所以一个 XML 文件可以调用多个报告。
命令行应用程序由我们的通宵进程安排和控制。因此可以从多个位置调用 XML 文件。
CSV 的目标是生成所有正在使用的报告以及调用它们的位置的列表。
我正在浏览所有参考的 XML 文件,查询调度程序并生成所有报告的列表。(这还不错)。
我遇到的问题是我必须保留所有可能已从生产中删除的报告的列表。所以我需要将旧的 CSV 与新的数据进行比较。为此,我认为最好将其放入 DataTables 并比较信息(这可能是错误的方法。我想我可以创建一个保存它的对象并比较差异然后创建迭代它们)。
我对每份报告的数据如下:
String - 任务名称 String - 动作名称 Int - ActionID(动作 ID 可以在多个记录中,因为单个动作可以调用多个报告,即 XML 文件)。字符串 - 名为字符串的 XML 文件 - 报告名称
我将尝试 MusiGenesis 给出的 Merge 想法(谢谢)。(重读一些帖子不确定合并是否会起作用,但值得一试,因为我以前没有听说过它,所以需要学习一些新东西)。
HashCode 的想法听起来也很有趣。
感谢所有的建议。
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;
}
我找到了一个简单的方法来解决这个问题。与以前的“例外方法”答案不同,我使用了两次例外方法。这不仅告诉您删除了哪些行,还告诉您添加了哪些行。如果您只使用一种 except 方法 - 它只会告诉您一种差异,而不是两者。此代码经过测试并且可以工作。见下文
//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
}
}
您不能在将 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 ...
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 的想法...
如果你有两个可排序的集合,那么你可以使用:</p>
List<string> diffList = new List<string>(sortedListA.Except(sortedListB));
如果您需要更复杂的对象,您可以自己定义一个比较器并仍然使用它。
通常的使用场景考虑的是一个用户,他DataTable
手头上有一个并通过添加、删除或修改一些DataRows
.
执行更改后,将DataTable
知道DataRowState
每一行的正确性,并跟踪Original
DataRowVersion
已更改的任何行。
在这种通常情况下,可以Merge
将更改返回到源表(其中所有行都是Unchanged
)。合并后,可以通过调用GetChanges()
.
在更不寻常的情况下,用户有两个DataTables
具有相同架构(或者可能只有相同的列并且缺少主键)。这两个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/中测试。结果输出如下所示:
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
,where
则 LINQ 查询中的子句会稍微简化一些。我会让你自己弄清楚。
只需使用 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;
}