比较内存中的2个无序记录集

时间:2018-02-02 12:55:22

标签: c# sql algorithm linq ado.net

下面是我的应用程序数据库表,其中包含存储在表中的SQL查询: QueryStorage

Id           Query           ConnectionString       Rdbms
1            select...        Data Source           Sql Server
2            select...        Data Source           Oracle

上表中的SQL查询是通过Web服务更新的,我们不允许在查询之上更新,但我们可以在查询之外添加一些内容:

查询存储在表格中: select id as LinkedColumn, Amount as CompareColumn from Source

来自我的c#app的调查查询: select Q.LinkedColumn, Q.CompareColumn from (stored sql query) as Q

我想比较下面的2个无序列表:

QueryStorage表记录为Id = 1(Sql server)执行的查询如下所示:

select Id as LinkedColumn,CompareColumn from Source

列表1:

LinkedColumn     CompareColumn
1                100
2                200
3                300
4                400
5                500
6                600
7                700
8                800
9                900
10               1000

QueryStorage 表记录中为Id = 2(Oracle)执行的查询如下所示:

select Id as LinkedColumn,CompareColumn from Target

列表2:

LinkedColumn       CompareColumn
10                 10
9                  20
8                  30
7                  40
6                  50
5                  60
4                  70
3                  80
2                  90
1                  5

我想加入LinkedColumn from source to target,然后对CompareColumn进行比较,这应该会给我以下输出:

SrcLinkedColumn      SrcCompareColumn     TgtLinkedColumn    TgtCompareColumn
    1                       100           1                  5
    2                       200           2                  90               

逻辑:

var data = (from s in List1.AsEnumerable()
             join t in List2.AsEnumerable() on s.Field<string>("LinkedColumn") equals t.Field<string>("LinkedColumn")
             where s.Field<decimal>("CompareColumn") != t.Field<decimal>("CompareColumn")
                        select new
                        {
                            srcLinkedcol = s.Field<string>("LinkedColumn"),
                            srcCompareCol = s.Field<decimal>("CompareColumn"),
                            tgtLinkedCol = t.Field<string>("LinkedColumn"),
                            tgtCompareCol = t.Field<decimal>("CompareColumn")
                        }).ToList();

从源到目标的数百万条记录我想与之比较,因此我们现在面临的out of memory exception大问题是将所有数据加载到内存中,然后与上面的linq查询进行比较。

我有两种解决方案,如下所示:

1)打开2个有序数据读取器。

优点:

    - No memory exception
    - Fast as there will be 1 to 1 comparision of LinkedColumn for List1 and 
      List2 records.

缺点:

    - Order by is require on LinkedColumns and as i have no control over 
      query as because it is dumped by webservice in QueryStorage table so 
      user is explicitly require to submit query with order by on 
      LinkedColumn.
    - Wont work if order by on Linkedcolumn is not present.
    - Order by query have performance overhead so because of this user may not include order by on LinkedColumn in query.                               

2)通过调整查询并添加OffSet and FetchRowNext来比较块的大块记录。这就是我在思考算法的方法:

enter image description here

但我仍然觉得使用第二种方法我可以获得内存异常问题,因为在源和目标数据不匹配的某些步骤中,我将它们存储在缓冲区(数据表或列表等)中以进行下一个块比较。

任何人都可以指导一下这应该是什么样的好算法或者更好的方法来解决这个问题吗?

注意:我不想使用 LinkedServer SSIS

7 个答案:

答案 0 :(得分:5)

您的第二个解决方案似乎是尝试重新发明External merge sort。如果您的数据不适合内存,这是一种有效的方法,但是当您的数据集适合内存时,它是过度的。

2400万行,每个(8字节)两个数字只有~200MB的内存。 两个数据集是400MB。现代桌面硬件上没有任何内容。

将两个数据集加载到内存中。在简单的数组中。在内存中排序数组。 通过扫描两个已排序的数组来查找差异。 你不需要花哨的LINQ。

这是伪代码。我们有Array1Array2。维护两个包含每个数组当前索引的变量:Idx1Idx2。沿阵列移动索引以查找LinkedColumn匹配的位置。

Idx1 = 0; Idx2 = 0;
while (true)
{
    // scan arrays until both indexes point to the same LinkedColumn
    // here we assume that both arrays are sorted by LinkedColumn
    // and that values in LinkedColumn are unique

    while (Idx1 < Array1.Length && Idx2 < Array2.Length &&
        Array1[Idx1].LinkedColumn < Array2[Idx2].LinkedColumn)
    {
        // move along Array1
        Idx1++;
    }

    while (Idx1 < Array1.Length && Idx2 < Array2.Length &&
        Array1[Idx1].LinkedColumn > Array2[Idx2].LinkedColumn)
    {
        // move along Array2
        Idx2++;
    }

    // at this point both indexes point to the same LinkedColumn
    // or one or both of the arrays are over

    if (Idx1 >= Array1.Length || Idx2 >= Array2.Length)
    {
        break;
    }

    // compare the values
    if (Array1[Idx1].CompareColumn != Array2[Idx2].CompareColumn)
    {
        // TODO: output/save/print the difference
    }

    Idx1++; Idx2++;
}

或者

您可以将所选数据库中的两个数据集转储到两个表T1T2中,在两个表中的LinkedColumn上创建唯一索引并运行此查询:

SELECT
     T1.LinkedColumn  AS SrcLinkedColumn
    ,T1.CompareColumn AS SrcCompareColumn
    ,T2.LinkedColumn  AS DstLinkedColumn
    ,T2.CompareColumn AS DstCompareColumn
FROM
    T1 INNER JOIN T2 ON T1.LinkedColumn = T2.LinkedColumn
WHERE
    T1.CompareColumn <> T2.CompareColumn
ORDER BY
    T1.LinkedColumn
;

上面的伪代码执行与DBMS服务器为此查询执行的相同的合并连接。

答案 1 :(得分:2)

如果我理解正确,您可以通过C#之类的某些内容获取DataReader中的表格数据。如果您存储从Web服务获取的记录,然后执行一些查询,如@VladimirBaranov所述,它需要太长的时间来存储,这是不合理的。

我认为在这种情况下比较的更好的解决方案是Binary Search。它的时间顺序是O(log n),它的存储顺序是O(1)。所以它不需要额外的内存。

您可以使用如下所示的二进制搜索:

1-排序较小的表格。

时间顺序:如果此表包含n个元素,则执行时间平均为O(nlogn)more information)。

内存顺序:O(1),因为C#中的sort方法是In-place

2-通过二进制搜索比较另一个表记录和已排序表。

时间顺序:如果另一个表有m条记录,则执行时间为O(m)*O(logn) = O(mlogn)。 (more information

内存顺序:它不需要额外的内存。

总而言之,我感谢你可能是你问题的最佳解决方案,但我认为这个解决方案是一个很好的解决方案,它有O(mlogn)执行时间,O(1)内存顺序,它只在C#中实现,不需要保存数据库中的记录。

答案 2 :(得分:1)

由于内存碎片,你可能会内存不足。

  • 确保您使用有效的方式来读取数据,如DataReader。
  • 为了避免碎片,在开始插入数组之前,获取记录的计数并使用此计数初始化List 1数组。如果您无法获得计数,则可以增加列表的增长因子,或者构建一个包含原始查询的查询作为子查询,如下所示: SELECT COUNT(*)FROM([你的原始查询])
  • 构建包含字段或2维数组的struct数组,这不需要每行的引用。
  • 此外,您可以以目标格式存储原始(列表1)数据。这样,内存中只有一个数组。

这是伪代码:

  • 获取List1的行数:
  • 创建一个2维数组来保存list1
  • 的数据
  • 从List1获取数据并将这些记录添加到您的数组
  • 首先处理您的datareader
  • 使用就地排序对list1数据进行排序(这样您就可以使用二进制搜索来查找list1行了。)
  • 从List2获取数据
  • List2的Foreach行使用二进制搜索在list1中查找相应的记录:将List2的比较值添加到匹配的行(请记住,您的数组已包含3列)。您只需要设置list2的比较值。
  • 通过删除没有匹配的行来清除结果。我建议你使用相同的数组进行清理。使用两个索引,您可以扫描阵列并在阵列中向上移动记录。

答案 3 :(得分:0)

对不起,我只能给你一个想法:

约束:

  • 目标有数百万条记录
  • Source有数百万条记录
  • 内存有限

这个想法:

  1. 至少使用2台机器
  2. 一个作为[Source],另一个作为[Target]具有匹配服务
  3. 通过调用服务将每个n(块)记录从[Source]发送到[Target]机器,让它与记录匹配,然后将结果发送回[Source]
  4. 奖金:

    1. 使用多台[Target]机器,让[Source]机器在异步和负载均衡的场景中使用它们
    2. 您可能会找到一些类似(或高级)的解决方案,例如:

      • [目标]和[来源]可以使用共享内存(例如缓存服务器)来存储结果
      • 在它们之间使用真正的负载均衡器层,以处理对多个[Target]计算机的多个请求
      • 使用队列/消息传递层
      • 甚至使用具有多个[Target]机器的多个[Source]

      对不起,但(至少对我来说)这个案子太大了,仅使用1台机器

      我希望至少这可以给你一个想法

答案 4 :(得分:0)

看起来LinkedColumn在每个来源上包含唯一值,我会将两个数据加载到本地数据库的两个表中,在LinkedColumn上创建索引并执行加入。

或者你可以在一个简单的文件中加载这两个数据,包括每行的源标识符,如下所示(不包括标题):

LinkedColumn   Identifier   CompareColumn
1              S            100
2              S            200
3              S            300
                   ...
4              O            70
3              O            80
2              O            90

S代表SQL Server,O代表Oracle。对文件进行排序(也许您可以运行操作系统排序或其他外部排序)。逐行读取排序文件。必须存储第一行,以便将其LinkedColumn与匹配的第二行进行比较,从而收集它们或存储第二行并删除第一行以查找第三行的匹配,依此类推。< / p>

希望它有所帮助。

答案 5 :(得分:0)

删除匹配的元素怎么样?类似的东西:

List1.RemoveAll(x => List2.Any(y => y.LinkedColumn == x.LinkedColumn && y.CompareColumn == x.CompareColumn))

答案 6 :(得分:0)

将此作业留给SQL Server。

让它处理WHERE CLAUSE

Codable