2つのDataTableを比較して、一方の行を決定しますが、他方の行は決定しません
質問
CSVファイルから生成されたA
とB
の2つのDataTableがあります。 <=>に存在しない行が<=>に存在するかどうかを確認する必要があります。
異なる行を表示するために何らかのクエリを実行する方法はありますか、または各DataTableの各行を繰り返して同じかどうかを確認する必要がありますか?後者のオプションは、テーブルが大きくなると非常に集中するようです。
解決
同じであるかどうかを確認するには、各DataTableの各行を反復処理する必要がありますか。
CSVファイルからデータを読み込んでいるのを見て、インデックスなどを持たないため、ある時点で、コードであるかどうかに関係なく、すべての行を繰り返し処理する必要があります。またはライブラリ、その他何でも。
とにかく、これはアルゴリズムの質問であり、私の専門ではありませんが、私の素朴なアプローチは次のようになります:
1:データのプロパティを活用できますか?各テーブルのすべての行は一意であり、同じ基準で両方をソートできますか?もしそうなら、あなたはこれを行うことができます:
- 両方のテーブルをIDでソートします(クイックソートのような便利なものを使用)。それらがすでにソートされている場合、あなたは大きな勝ちです。
- 両方のテーブルを一度にステップ実行し、いずれかのテーブルのIDのギャップをスキップします。一致したIDは重複したレコードを意味します。
これにより、(ソート時間* 2)+ 1パスで実行できるため、私のビッグO表記が正しければ、(ソートタイムが何であれ)+ O(m + n)になりますかなり良いです。
(改訂:これは <!>#932; <!>#918; <!>#937; <!>#932; <!>#918; <!># 921; <!>#927; <!>#933;記述)
2:代替アプローチ。これは、データの大きさに応じて多かれ少なかれ効率的です:
- 表1を実行し、各行について、そのID(または計算されたハッシュコード、またはその行の他の一意のID)を辞書(または、呼び出したい場合はハッシュテーブル)に貼り付けます。
- 表2を実行し、各行について、ID(またはハッシュコードなど)が辞書に存在するかどうかを確認します。あなたは辞書が本当に速いという事実を利用しています-O(1)私は思う?見上げる。このステップは非常に高速になりますが、これらの辞書挿入をすべて行うことで代価を支払うことになります。
このアルゴリズムについて、私よりもアルゴリズムの知識が優れている人が何を思い付くのか本当に興味があります:-)
他のヒント
適切なタイプの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の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()メソッドを使用できます。
しかし、重複したDataRowを探しているという意味で、あなたの質問を理解しています。 (あなたの問題の説明から、両方のテーブルがCSVファイルからインポートされているため、元の行にはプライマリキー値がなく、インポート中にオートナンバーを介してプライマリキーが割り当てられていると仮定します)
単純な実装(Aの各行について、ItemArrayをBの各行のItemArrayと比較します)は実際に計算コストが高くなります。
これを行うためのはるかに安価な方法は、ハッシュアルゴリズムを使用することです。各DataRowについて、列の文字列値を1つの文字列に連結し、その文字列でGetHashCode()を呼び出してint値を取得します。 DataTable Bの各DataRowについて、ハッシュコードをキーとするエントリを含むDictionary<int, DataRow>
を作成します。次に、DataTable Aの各DataRowについて、ハッシュコードを計算し、辞書に含まれているかどうかを確認します。そうでない場合は、DataRowがDataTable Bに存在しないことがわかります。
このアプローチには、2つの文字列が等しくなくても同じハッシュコードを生成できるという事実から生じる2つの弱点があります。ハッシュがディクショナリにある行をAで見つけた場合、ディクショナリのDataRowをチェックして、2つの行が本当に等しいことを確認する必要があります。
2番目の弱点はより深刻です。Bの2つの異なるDataRowsが同じキー値にハッシュできる可能性は低いですが、可能性はあります。このため、辞書は実際にはDictionary<int, List<DataRow>>
である必要があり、リスト内の各DataRowに対して前の段落で説明したチェックを実行する必要があります。
これを機能させるにはかなりの作業が必要ですが、O(m + n)アルゴリズムです。これは、できる限り良いものになると思います。
参考までに:
一般にアルゴリズムについて言えば、2セットのソート可能(通常IDがそうであるように)の比較はO(M * N / 2)操作ではなく、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
上記のコードは明らかに擬似コードですが、自分でコーディングすることに決めた場合は、一般的な要点を説明する必要があります。
すべてのフィードバックに感謝します。
残念ながら、インデックスはありません。私の状況についてもう少し情報を提供します。
EUの7つのサーバーにインストールされているレポートプログラム(Crystalレポートに代わるもの)があります。これらのサーバーには多くのレポートがあります(各国ですべて同じではありません)。これらは、構成にXMLファイルを使用するコマンドラインアプリケーションによって呼び出されます。したがって、1つのXMLファイルで複数のレポートを呼び出すことができます。
コマンドラインアプリケーションは、夜間プロセスによってスケジュールおよび制御されます。したがって、XMLファイルは複数の場所から呼び出すことができます。
CSVの目的は、使用されているすべてのレポートとそれらの呼び出し元のリストを作成することです。
すべての参照のXMLファイルを調べ、スケジューリングプログラムにクエリを実行し、すべてのレポートのリストを作成しています。 (これはそれほど悪くはありません)。
問題は、本番環境から削除された可能性のあるすべてのレポートのリストを保持する必要があることです。そのため、古いCSVと新しいデータを比較する必要があります。そのためには、DataTablesに入れて情報を比較するのが最善だと考えました(これは間違ったアプローチかもしれません。それを保持するオブジェクトを作成し、違いを比較し、それらを反復して作成できると思います)。
各レポートに関するデータは次のとおりです。
文字列-タスク名 文字列-アクション名 Int-ActionID(アクションIDは、単一のアクションが多数のレポート、つまりXMLファイルを呼び出すことができるため、複数のレコードに存在できます)。 文字列-呼び出されたXMLファイル 文字列-レポート名
MusiGenesisのMergeアイデアを試してみます(ありがとう)。 (Mergeが機能するかどうかわからない投稿の一部を読み直すことはできますが、以前に聞いたことがないので、何か新しいことを学ぶ価値があるので試してみる価値があります。)
HashCodeのアイデアもおもしろそうです。
すべてのアドバイスをありがとう。
これを解決する簡単な方法を見つけました。以前の<!> quot; except method <!> quotとは異なり、答えは、exceptメソッドを2回使用します。これは、削除された行だけでなく、追加された行も示します。 1つの以外のメソッドのみを使用する場合は、両方ではなく1つの違いのみを通知します。このコードはテスト済みで動作します。以下を参照してください
//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 ...
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のアイデアを継続しています...
ソート可能なセットが2つある場合は、単に<!>#65306;
を使用できますList<string> diffList = new List<string>(sortedListA.Except(sortedListB));
より複雑なオブジェクトが必要な場合は、コンパレータを自分で定義して使用できます。
通常の使用シナリオでは、DataTable
を手に持っているユーザーを考慮し、DataRows
の一部を追加、削除、または変更することでそれを変更します。
変更の実行後、DataRowState
は各行の適切なOriginal
を認識し、変更された行のDataRowVersion
Merge
も追跡します。
この通常のシナリオでは、変更をソーステーブルに戻すことができます(すべての行がUnchanged
である)。マージ後、GetChanges()
を呼び出すと、変更された行のみの素晴らしい要約を取得できます。
より珍しいシナリオでは、ユーザーは同じスキーマを持つ2つのDataTables
を持ちます(または、おそらく同じ列のみで主キーがありません)。これらの2つのGetDelta()
は、PrimaryKey
行のみで構成されています。ユーザーは、2つのテーブルのいずれかに変更を適用して、もう一方のテーブルにアクセスする必要があるかどうかを知りたい場合があります。つまり、どの行を追加、削除、または変更する必要があります。
ここで、ジョブを実行するwhere
という関数を定義します。
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
テーブルに<=>がない場合、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;
}