قارن بين جدولي DataTables لتحديد الصفوف في أحدهما وليس الآخر

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

  •  03-07-2019
  •  | 
  •  

سؤال

لدي جدولين بيانات A و B, ، تم إنتاجها من ملفات CSV.أحتاج أن أكون قادرًا على التحقق من الصفوف الموجودة فيها B التي لا وجود لها في A.

هل هناك طريقة للقيام بنوع من الاستعلام لإظهار الصفوف المختلفة أم هل يجب علي التكرار خلال كل صف في كل DataTable للتحقق مما إذا كانت متماثلة؟يبدو الخيار الأخير مكثفًا جدًا إذا أصبحت الجداول كبيرة.

هل كانت مفيدة؟

المحلول

هل سيتعين علي التكرار خلال كل صف في كل DataTable للتحقق مما إذا كانت متماثلة.

نظرًا لأنك قمت بتحميل البيانات من ملف CSV، فلن يكون لديك أي فهارس أو أي شيء، لذا في مرحلة ما، سيتعين تكرار شيء ما خلال كل صف، سواء كان ذلك رمزًا أو مكتبة ، أو أيا كان.

على أية حال، هذا سؤال خوارزمي، وهو ليس من تخصصي، لكن أسلوبي الساذج سيكون كما يلي:

1:هل يمكنك استغلال أي خصائص للبيانات؟هل جميع الصفوف في كل جدول فريدة من نوعها، وهل يمكنك فرزها بنفس المعايير؟إذا كان الأمر كذلك، يمكنك القيام بذلك:

  • قم بفرز كلا الجدولين حسب المعرف الخاص بهما (باستخدام بعض الأشياء المفيدة مثل الفرز السريع).إذا تم فرزها بالفعل، فستفوز كثيرًا.
  • انتقل عبر كلا الجدولين مرة واحدة، متخطيًا أي فجوات في المعرفات في أي من الجدولين.المعرف المطابق يعني السجلات المكررة.

يتيح لك ذلك القيام بذلك في (وقت الفرز * 2 ) + مرور واحد، لذلك إذا كان تدوين O الكبير الخاص بي صحيحًا، فسيكون (مهما كان وقت الفرز) + O(m+n) وهو أمر جيد جدًا .
(مراجعة:هذا هو النهج الذي يصف ΤΖΩΤΖΙΟΥ )

2:نهج بديل، والذي قد يكون أكثر أو أقل كفاءة اعتمادًا على حجم بياناتك:

  • قم بالمرور عبر الجدول 1، ولكل صف، قم بإدراج معرفه (أو رمز التجزئة المحسوب، أو بعض المعرفات الفريدة الأخرى لهذا الصف) في القاموس (أو جدول التجزئة إذا كنت تفضل تسميته بذلك).
  • قم بالمرور عبر الجدول 2، ولكل صف، تحقق مما إذا كان المعرف (أو رمز التجزئة وما إلى ذلك) موجودًا في القاموس.أنت تستغل حقيقة أن القواميس لديها سرعة كبيرة - 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);

ويمكنك استخدام أساليب دمج وGetChanges على DataTable وللقيام بذلك:

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

والأجوبة تحمل حتى الآن أنك ببساطة تبحث عن مفاتيح أساسية مكررة. هذه مشكلة سهلة جدا - يمكنك استخدام طريقة دمج ()، على سبيل المثال

ولكن أنا أفهم سؤالك يعني أن كنت تبحث عن DataRows مكررة. (من وصفك للمشكلة، مع كل من الجداول التي يتم استيرادها من ملفات CSV، فما استقاموا لكم فاستقيموا حتى نفترض أن الصفوف الأصلي لم يكن لديهم قيم المفتاح الأساسي، والتي يتم تعيين أي المفاتيح الأساسية عن طريق ترقيم تلقائي أثناء الاستيراد.)

وتنفيذ السذاجة (لكل صف في A، مقارنة ItemArray مع أن كل صف في B) تسير بالفعل أن تكون مكلفة حسابيا.

وهناك طريقة أقل تكلفة بكثير للقيام بذلك هي مع خوارزمية التجزئة. لكل DataRow، سلسلة القيم سلسلة من أعمدته في سلسلة واحدة، ومن ثم استدعاء GetHashCode () على هذه السلسلة للحصول على القيمة الصحيحة. إنشاء Dictionary<int, DataRow> يحتوي على دخول، مرتبطا على رمز التجزئة لكل DataRow في DataTable وB. ثم، لكل DataRow في DataTable وA، حساب رمز التجزئة، ومعرفة ما إذا كانت الواردة في القاموس. إذا لم تكن كذلك، وتعلمون أن DataRow غير موجود في DataTable وB.

وهذا النهج اثنين من نقاط الضعف التي على حد سواء الخروج من حقيقة أن سلسلتين يمكن أن يكون غير متكافئ ولكن إنتاج نفس رمز التجزئة. إذا وجدت على التوالي في A الذي التجزئة في القاموس، ثم كنت بحاجة للتأكد من DataRow في القاموس للتحقق من أن صفين متساوية حقا.

وضعف الثاني هو أكثر خطورة: انه من غير المحتمل، ولكن من الممكن، أن اثنين من DataRows مختلفة في B يمكن تجزئة إلى نفس القيمة الأساسية. لهذا السبب، يجب القاموس أن يكون حقا Dictionary<int, List<DataRow>>، ويجب إجراء فحص وصفها في الفقرة السابقة ضد بعضها DataRow في القائمة.

ويستغرق قدرا كبيرا من العمل للحصول على هذا العمل، ولكن انها O (م + ن) الخوارزمية، الذي أعتقد أنه ستكون جيدة كما يحصل.

ولمعلوماتك فقط:

وبصفة عامة عن الخوارزميات، مقارنة مجموعتين من للفرز (كما هويات وعادة ما تكون) ليست 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 ومقارنة المعلومات (قد يكون هذا هو النهج الخاطئ.أفترض أنه يمكنني إنشاء كائن يحمله ويقارن الفرق ثم يتكرر من خلاله).

البيانات المتوفرة لدي عن كل تقرير هي كما يلي:

سلسلة - اسم المهمة السلسلة - اسم الإجراء int - ActionId (يمكن أن يكون معرف الإجراء في سجلات متعددة حيث يمكن لإجراء واحد استدعاء العديد من التقارير ، أيملف XML).سلسلة - ملف XML يسمى String - اسم التقرير

سأحاول فكرة الدمج المقدمة من MusiGenesis (شكرًا).(إعادة قراءة بعض المنشورات لست متأكدًا مما إذا كانت عملية الدمج ستنجح أم لا، ولكنها تستحق المحاولة لأنني لم أسمع عنها من قبل، لذلك هناك شيء جديد يجب تعلمه).

تبدو فكرة HashCode مثيرة للاهتمام أيضًا.

شكرا على كل النصائح.

ولقد وجدت طريقة سهلة لحل هذه المشكلة. على عكس "إلا طريقة" الأجوبة السابقة، وأنا استخدم باستثناء طريقة مرتين. هذا يقول لك ليس فقط ما تم حذف الصفوف ولكن ماذا صفوف أضيفت. إذا كنت تستخدم واحد فقط إلا طريقة - وسوف اقول لكم فقط فارق واحد وليس على حد سواء. يتم اختبار هذا الرمز ويعمل. أنظر أدناه

//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

لاحظ أنه إذا الجداول الخاصة بك لم يكن لديك 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