Обработка парной последовательности для сравнения таблиц БД - PullRequest
0 голосов
/ 13 апреля 2011

Рассмотрим следующий вариант использования:
Я хочу выполнить итерацию по таблицам 2 дБ параллельно и найти различия и пропуски / пропущенные записи в любой таблице.Предположим, что 1) pk таблицы является полем Int ID;2) таблицы читаются в порядке идентификации;3) записи могут отсутствовать в любой таблице (с соответствующими пробелами в последовательности).

Я бы хотел сделать это за один проход для каждого дБ - используя ленивое чтение.(Моя первоначальная версия этой программы использует объекты последовательности и средство чтения данных - к сожалению, делает несколько проходов для каждого дБ).

Я думал об использовании парной обработки последовательности и использовании Seq.skip в итерациях, чтобы попытатьсясинхронизировать обработку таблицыОднако, по-видимому, это очень медленно, так как I Seq.skip имеет большие издержки (создавая новые последовательности под капотом), поэтому это может быть проблемой с большой таблицей (скажем, 200 тыс. Записей).

Я представляю, что это общий шаблон проектирования (сравнивая одновременные потоки данных из разных источников), и мне интересны отзывы / комментарии / ссылки на похожие проекты.

Кто-нибудь хочет прокомментировать?

Ответы [ 2 ]

1 голос
/ 14 апреля 2011

Вот мой (полностью непроверенный) дубль по одному и тому же проходу по обеим таблицам:

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 с (по одному для каждой таблицы) и возвращает два int[] с, которые указывают идентификаторы, которые присутствовали только в их соответствующей таблице. Код предполагает, что поле идентификатора имеет тип int и имеет порядковый индекс 0.

Также обратите внимание, что в этом коде используется LazyList из F # PowerPack , поэтому вам необходимо получить его, если у вас его еще нет. Если вы ориентируетесь на .NET 4.0, тогда я настоятельно рекомендую получить исполняемые файлы .NET 4.0, которые я собрал и разместил здесь , поскольку двоичные файлы с сайта F # PowerPack предназначены только для .NET 2.0 и иногда Не подходит для VS2010 SP1 (см. эту ветку для получения дополнительной информации: Проблема с F # Powerpack. Метод не найден ).

1 голос
/ 13 апреля 2011

Когда вы используете последовательности, любая ленивая функция добавляет некоторые издержки на последовательность.Вызов 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])

Но я все же думаю, что вам следует отделить итерацию ваших данных от обработки.Написание функции высокого порядка для итерации - хорошая идея (в конце концов, не раздражает, если код функции итератора использует изменяемые перечислители).

...