【问题标题】:Pairwise Sequence Processing to compare db tables成对序列处理以比较数据库表
【发布时间】:2011-04-13 17:45:30
【问题描述】:

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

我想在每个 db 上一次执行此操作 - 使用惰性读取。 (我的这个程序的初始版本使用序列对象和数据读取器 - 不幸的是对每个数据库进行了多次传递)。

我曾考虑使用成对序列处理并在迭代中使用 Seq.skip 来尝试保持表处理同步。但是显然这非常慢,因为我 Seq.skip 的开销很高(在引擎盖下创建新序列),所以这可能是一个大表(比如 200k 记录)的问题。

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

有人愿意发表评论吗?

【问题讨论】:

  • 你能分享一个代码示例,以便我们改进它吗?
  • 您意识到最快的方法是在数据库内部?通过消除通过网络传输所有数据的需要,您通过算法改进获得的任何速度节省都将相形见绌。
  • @hova:我描述的场景过于简化,可能会产生误导。此处的实际用例由不同数据库中的相似表组成,这些表需要在数据迁移项目期间进行比较(例如,从 Access Db 转换为 Postgres 或 MySQl)。您还可以扩展此场景以处理来自各种外部(可能是远程)数据源(例如 Excel 文件、csv 文件、日志文件、XML 等)的数据。所以这里的目标是解决更一般的情况。
  • 您是在寻找 id 的差距、列值的差异,还是两者兼而有之?
  • 以上所有 - 假设您的工作是验证桌面数据库和 Oracle、Sql Server 之间的数据转换项目,您需要了解所有差异 - 迭代/序列处理只是这里的冰山一角。

标签: f# stream iteration f#-data lazy-sequences


【解决方案1】:

这是我(完全未经测试)的看法,对两张桌子都做了一次:

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 ()

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

另请注意,此代码使用来自F# PowerPack 的LazyList,因此如果您还没有它,则需要获取它。如果您的目标是 .NET 4.0,那么我强烈建议您获取我构建并托管 here 的 .NET 4.0 二进制文件,因为来自 F# PowerPack 站点的二进制文件仅针对 .NET 2.0,有时不适合使用VS2010 SP1(有关更多信息,请参阅此线程:Problem with F# Powerpack. Method not found error)。

【讨论】:

    【解决方案2】:

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

    您可以使用Seq.zip 或Seq.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])
    

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

    【讨论】:

    • 我不认为 map 和 zip fns 可以在这里工作,因为我的测试序列的长度未知(并且可能)不相等,并且还可能包含间隙。但是,您的示例代码看起来像是一个好的开始 - thx
    • 示例代码肯定会让 StackOverflow 有 20 万条记录?
    • 不,编译器能够优化这些递归调用并使用简单的循环编译函数(我已经检查了生成的 IL)。如果您不信任编译器,请随意使用 while 循环,这里的转换非常容易。
    • IMO,使用枚举器直接强制使用命令式风格,这几乎违背了首先使用函数式语言的目的。将seqs 强制转换为LazyLists 至少允许模式匹配和函数式编程风格。
    • @ildjarn:所有的 Seq 库都是这样写的,这不是一件坏事。我认为这样的函数应该是通用的(高阶函数)和可重用的,因此逻辑可以与可变枚举器分开。我的回答是为迭代定义一个新的原语。你是对的,LazyList 可能是一个替代方案。
    猜你喜欢
    • 2014-07-26
    • 1970-01-01
    • 2019-03-25
    • 2013-07-11
    • 2019-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多