두 개의 DataTables를 비교하여 하나의 행을 결정하지만 다른 행은 아닙니다.

StackOverflow https://stackoverflow.com/questions/164144

  •  03-07-2019
  •  | 
  •  

문제

나는 두 개의 DataTables가 있습니다. A 그리고 B, CSV 파일에서 생성. 어떤 행에 존재하는지 확인할 수 있어야합니다. B 존재하지 않습니다 A.

다른 행을 보여주기 위해 일종의 쿼리를 수행하는 방법이 있습니까? 아니면 각 데이터 가능한 각 행의 각 행을 반복하여 동일인지 확인해야합니까? 후자의 옵션은 테이블이 커지면 매우 집중적 인 것 같습니다.

도움이 되었습니까?

해결책

각 데이터 테이블의 각 행을 반복하여 동일인지 확인해야합니까?

CSV 파일에서 데이터를로드 할 때 인덱스 나 아무것도가 없을 것이므로 어느 시점에서 코드이든 라이브러리이든 모든 행을 반복해야 할 것입니다. , 또는 무엇이든.

어쨌든, 이것은 알고리즘 질문이지만, 나의 전문 분야는 아니지만 순진한 접근 방식은 다음과 같습니다.

1 : 데이터의 속성을 이용할 수 있습니까? 각 테이블의 모든 행이 독특하고 동일한 기준으로 둘 다 분류 할 수 있습니까? 그렇다면이 작업을 수행 할 수 있습니다.

  • ID로 두 테이블을 정렬하십시오 (QuickSort와 같은 유용한 것을 사용). 그들이 이미 분류된다면 당신은 큰 승리를 얻습니다.
  • 두 테이블을 한 번에 밟아 두 테이블의 ID의 간격을 뛰어 넘습니다. 일치하는 ID의 평균 복제 레코드.

이렇게하면 (Sort Time * 2) + 하나의 패스로 수행 할 수 있으므로, 내 Big-O-Notation이 정확하다면 (무엇이든간에) + O (M + N)는 꽤 좋습니다. .
(개정 : 이것은 접근 방식입니다 τζωτζιου가 설명합니다 )

2 : 데이터가 얼마나 큰지에 따라 다소 효율적일 수있는 대체 접근법.

  • 표 1을 통해 실행되고 각 행에 대해 ID (또는 계산 된 해시 코드 또는 해당 행의 다른 고유 ID)를 사전 (또는이를 선호하는 경우 해시 가능)에 고착하십시오.
  • 표 2를 통해 실행되고 각 행에 대해 ID (또는 해시 코드 등)가 사전에 있는지 확인하십시오. 당신은 사전이 정말 빠르다는 사실을 악용하고 있습니다 - o (1) 나는 생각합니까? 조회. 이 단계는 정말 빠르지 만, 모든 사전 삽입물을하는 가격을 지불하게 될 것입니다.

나는 나 자신보다 알고리즘에 대한 더 나은 지식을 가진 사람들이 이것을 위해 무엇을 생각 나게하는지 정말로 관심이 있습니다 :-)

다른 팁

적절한 유형 인 ID 열이 있다고 가정하면 (즉, 해시 코드를 제공하고 평등을 구현 함) -이 예에서 문자열은 데이터에 대해 익숙하지 않고 모든 것을 볼 시간이 없기 때문에 약간 의사 코드입니다. 지금 당장 :)

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에서 병합 및 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 () 메소드를 사용할 수 있습니다.

그러나 나는 당신이 중복 데이터를 찾고 있다는 것을 의미하는 당신의 질문을 이해합니다. (문제에 대한 설명에서 두 테이블이 CSV 파일에서 가져 오면 원래 행에 기본 키 값이없고 가져 오기 동안 Autonumber를 통해 기본 키가 할당된다고 가정합니다.)

순진한 구현 (a의 각 행에 대해 aitarray를 각 행의 각 행과 비교)는 실제로 계산적으로 비싸다.

이를 수행하는 훨씬 저렴한 방법은 해싱 알고리즘을 사용하는 것입니다. 각 데이터에 대해 열의 문자열 값을 단일 문자열로 연결 한 다음 해당 문자열의 gethashCode ()를 호출하여 int 값을 얻습니다. a Dictionary<int, DataRow> 여기에는 DataTable B의 각 데이터에 대해 해시 코드에 키가 표시된 항목이 포함되어 있습니다. 그런 다음 DataTable A의 각 데이터에 대해 해시 코드를 계산하고 사전에 포함되어 있는지 확인하십시오. 그렇지 않은 경우 DataRow가 DataTable B에 존재하지 않는다는 것을 알고 있습니다.

이 접근법은 두 줄이 불평등 할 수 있지만 동일한 해시 코드를 생성한다는 사실에서 나오는 두 가지 약점이 있습니다. 해시가 사전에있는 행을 찾으면 사전에서 Datarow를 확인하여 두 행이 실제로 동일하다는 것을 확인해야합니다.

두 번째 약점은 더 심각합니다. B의 두 개의 다른 데이터가 동일한 키 값으로 해시 될 가능성은 거의 없지만 가능합니다. 이런 이유로 사전은 실제로 Dictionary<int, List<DataRow>>, 그리고 목록의 각 데이터에 대해 이전 단락에 설명 된 수표를 수행해야합니다.

이 작업을 수행하려면 상당한 양의 작업이 필요하지만 O (M+N) 알고리즘이므로 얻는만큼 좋을 것 같습니다.

그냥 참고 :

일반적으로 알고리즘에 대해 말하면, 두 세트의 정렬 가능한 세트 (일반적으로 IDS와 같이)를 비교하는 것은 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

위의 코드는 분명히 의사 코드이지만 직접 코딩하기로 결정하면 일반적인 요점을 제공해야합니다.

모든 피드백에 감사드립니다.

불행히도 색인이 없습니다. 내 상황에 대한 좀 더 많은 정보를 줄 것입니다.

EU 전역의 7 개의 서버에 설치된보고 프로그램 (Crystal Reports)이 있습니다. 이 서버에는 많은 보고서가 있습니다 (각 국가마다 모두 동일하지 않음). 구성에 XML 파일을 사용하는 CommandLine 응용 프로그램에 의해 호출됩니다. 따라서 하나의 XML 파일은 여러 보고서를 호출 할 수 있습니다.

CommandLine 응용 프로그램은 하룻밤 프로세스에 의해 예약되고 제어됩니다. 따라서 XML 파일은 여러 곳에서 호출 될 수 있습니다.

CSV의 목표는 사용중인 모든 보고서의 목록을 작성하는 것입니다.

모든 참조에 대한 XML 파일을 살펴보고 스케줄링 프로그램을 쿼리하고 모든 보고서 목록을 작성합니다. (이것은 나쁘지 않습니다).

내가 가진 문제는 생산에서 제거되었을 수있는 모든 보고서의 목록을 유지해야한다는 것입니다. 따라서 이전 CSV를 새로운 데이터와 비교해야합니다. 이를 위해 나는 데이터를 데이터에 넣고 정보를 비교하는 것이 가장 좋다고 생각했습니다 (이것은 잘못된 접근법 일 수 있습니다. 나는 그것을 보유하고있는 객체를 만들고 차이를 비교 한 다음 반복을 만들 수 있다고 생각합니다).

각 보고서에 대한 데이터는 다음과 같습니다.

문자열 - 작업 이름 문자열 - 액션 이름 int -ActionId (동작 ID는 단일 작업이 많은 보고서, 즉 XML 파일을 호출 할 수 있으므로 여러 레코드에있을 수 있음). 문자열 - xml 파일이라는 문자열 - 보고서 이름

Musigenesis (감사합니다)가 제공 한 병합 아이디어를 시도하겠습니다. (일부 게시물이 합병이 작동하는지 확실하지 않지만 이전에 들어 보지 못했기 때문에 시도 할 가치가 있으므로 새로운 배울 수있는 것입니다).

해시 코드 아이디어도 흥미로운 것 같습니다.

모든 조언에 감사드립니다.

나는 이것을 해결하는 쉬운 방법을 찾았다. 이전의 "메서드 제외"답변과 달리 제외 방법을 두 번 사용합니다. 이것은 삭제 된 행뿐만 아니라 어떤 행이 추가되었는지 알려줍니다. 방법을 제외한 하나만 사용하는 경우 - 둘 다가 아니라 하나의 차이 만 알려줍니다. 이 코드는 테스트되고 작동합니다. 아래를 참조하십시오

//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의 아이디어를 계속하고있다 ...

두 개의 분류 가능한 세트가 있으면 사용할 수 있습니다.

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

테이블에 a가없는 경우에 유의하십시오 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;

    }
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top