成对序列处理以比较数据库表



考虑以下用例:
我想并行地遍历2个数据库表,并在其中的任何一个表中找到差异和间隙/缺失记录。假设表的1(pk是Int ID字段;2( 按ID顺序读取表格;3( 任何一个表中都可能缺少记录(具有相应的序列间隙(。

我想在每个数据库上一次性完成这项工作——使用惰性读取。(我的这个程序的初始版本使用序列对象和数据读取器——不幸的是,在每个数据库上进行多次传递(。

我已经考虑过使用成对序列处理,并在迭代中使用Seq.skip来尝试保持表处理的同步。然而,显然这是非常慢的,因为I Seq.skip有很高的开销(在引擎盖下创建新序列(,所以这可能是一个大表的问题(比如20万个recs(。

我认为这是一种常见的设计模式(比较来自不同来源的并发数据流(,并且对类似项目的反馈/评论/链接感兴趣。

有人想发表评论吗?

这是我的(完全未经测试的(观点,对两个表进行一次遍历:

let findDifferences readerA readerB =
    let idsA, idsB =
        let getIds (reader:System.Data.Common.DbDataReader) =
            reader |> LazyList.unfold (fun reader ->
                if reader.Read ()
                then Some (reader.GetInt32 0, reader)
                else None)
        getIds readerA, getIds readerB
    let onlyInA, onlyInB = ResizeArray<_>(), ResizeArray<_>()
    let rec impl a b =
        let inline handleOnlyInA idA as' = onlyInA.Add idA; impl as' b
        let inline handleOnlyInB idB bs' = onlyInB.Add idB; impl a bs'
        match a, b with
        | LazyList.Cons (idA, as'), LazyList.Cons (idB, bs') ->
                if   idA < idB then handleOnlyInA idA as'
                elif idA > idB then handleOnlyInB idB bs'
                else impl as' bs'
        | LazyList.Nil, LazyList.Nil  -> () // termination condition
        | LazyList.Cons (idA, as'), _ -> handleOnlyInA idA as'
        | _, LazyList.Cons (idB, bs') -> handleOnlyInB idB bs'
    impl idsA idsB
    onlyInA.ToArray (), onlyInB.ToArray ()

这需要两个DataReader(每个表一个(,并返回两个指示仅存在于各自表中的ID的int[]。该代码假定ID字段的类型为int,并且位于序号索引0处。

还要注意的是,这段代码使用了F#PowerPack中的LazyList,所以如果你还没有它,你就需要获得它。如果你的目标是.NET 4.0,那么我强烈建议你获得我在这里构建和托管的.NET 4.0二进制文件,因为F#PowerPack站点的二进制文件只针对.NET 2.0,有时与VS2010 SP1配合不好(有关更多信息,请参阅此线程:F#PowerPack有问题。找不到方法错误(。

使用序列时,任何惰性函数都会在序列上增加一些开销。在同一序列中调用Seq.skip数千次显然会很慢。

您可以使用Seq.zipSeq.map2一次处理两个序列:

> Seq.map2 (+) [1..3] [10..12];;
val it : seq<int> = seq [11; 13; 15]

如果Seq模块还不够,您可能需要编写自己的函数。我不确定我是否理解你想做什么,但这个示例函数可能会对你有所帮助:

let fct (s1: seq<_>) (s2: seq<_>) =
    use e1 = s1.GetEnumerator()
    use e2 = s2.GetEnumerator()
    let rec walk () =
        // do some stuff with the element of both sequences
        printfn "%d %d" e1.Current e2.Current
        if cond1 then // move in both sequences
            if e1.MoveNext() && e2.MoveNext() then walk ()
            else () // end of a sequence
        elif cond2 then // move to the next element of s1
            if e1.MoveNext() then walk()
            else () // end of s1
        elif cond3 then // move to the next element of s2
            if e2.MoveNext() then walk ()
            else () // end of s2
    // we need at least one element in each sequence
    if e1.MoveNext() && e2.MoveNext() then walk()

编辑:

前面的函数旨在扩展Seq模块的功能,您可能希望将其作为一个高阶函数。正如ildjarn所说,使用LazyList可以产生更干净的代码:

let rec merge (l1: LazyList<_>) (l2: LazyList<_>) =
    match l1, l2 with
    | LazyList.Cons(h1, t1), LazyList.Cons(h2, t2) ->
        if h1 <= h2 then LazyList.cons h1 (merge t1 l2)
        else LazyList.cons h2 (merge l1 t2)
    | LazyList.Nil, l2 -> l2
    | _ -> l1
merge (LazyList.ofSeq [1; 4; 5; 7]) (LazyList.ofSeq [1; 2; 3; 6; 8; 9])

但我仍然认为您应该将数据的迭代与处理分开。编写一个高阶函数进行迭代是个好主意(最后,如果迭代器函数代码使用可变枚举器,这并不令人讨厌(。

相关内容

  • 没有找到相关文章

最新更新