Обработка на последователност по двойки за сравняване на db таблици

Помислете за следния случай на употреба:
Искам да обходя паралелно 2 db таблици и да намеря разлики и пропуски/липсващи записи във всяка таблица. Да приемем, че 1) pk на таблицата е Int ID поле; 2) таблиците се четат в ID ред; 3) може да липсват записи от която и да е таблица (със съответните празнини в последователността).

Бих искал да направя това с едно преминаване през всяка db - използвайки мързеливи четения. (Моята първоначална версия на тази програма използва последователни обекти и четец на данни - за съжаление прави многократни преминавания през всяка db).

Мислех да използвам обработка на последователност по двойки и да използвам Seq.skip в итерациите, за да се опитам да поддържам обработката на таблицата в синхрон. Въпреки това очевидно това е много бавно, тъй като I Seq.skip има голямо натоварване (създаване на нови последователности под капака), така че това може да е проблем с голяма маса (да речем 200k rec).

Предполагам, че това е общ модел на проектиране (сравнете едновременни потоци от данни от различни източници) и се интересувам от обратна връзка/коментари/връзки към подобни проекти.

Някой иска ли да коментира?


person BrendanC    schedule 13.04.2011    source източник
comment
Бихте ли споделили примерен код, за да можем да го подобрим?   -  person Laurent    schedule 13.04.2011
comment
Разбирате ли, че най-бързият начин да направите това е в базата данни? Всяко спестяване на скорост, което получавате с алгоритмични подобрения, ще бъде НАМАЛЕНО, като премахне необходимостта от прехвърляне на ВСИЧКИ данни по мрежата.   -  person hova    schedule 14.04.2011
comment
@hova: Сценарият, който описах, беше прекалено опростен и вероятно подвеждащ. Случаят на използване в реалния свят тук се състои от подобни таблици в различни бази данни, които трябва да бъдат сравнени по време на проект за мигриране на данни (напр. конвертиране от Access Db в Postgres или MySQl). Можете също така да разширите този сценарий, за да обработвате данни от различни външни (евентуално отдалечени) източници на данни (напр. Excel файлове, csv файлове, регистрационни файлове, XML и т.н. Така че целта тук е да се реши по-общият случай.   -  person BrendanC    schedule 14.04.2011
comment
Търсите пропуски в идентификаторите, разлики в стойностите на колоните или и двете?   -  person Robert Jeppesen    schedule 14.04.2011
comment
Всичко по-горе - представете си, че работата ви е била да валидирате проект за преобразуване на данни между десктоп db и Oracle, Sql сървър и трябва да знаете за всички несъответствия - обработката на итерация/последователност е само върхът на айсберга тук.   -  person BrendanC    schedule 14.04.2011


Отговори (2)


Ето моето (напълно непроверено) представяне, което прави едно минаване през двете маси:

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 полето е от тип int и е на пореден индекс 0.

Също така имайте предвид, че този код използва LazyList от F# PowerPack, така че ще трябва да го получите, ако не вече го нямам. Ако се насочвате към .NET 4.0, тогава силно препоръчвам да вземете двоичните файлове на .NET 4.0, които създадох и хоствах тук, тъй като двоичните файлове от сайта на F# PowerPack са насочени само към .NET 2.0 и понякога не работят добре с VS2010 SP1 (вижте тази тема за повече информация: Проблем с F# Powerpack. Методът не е намерен грешка).

person ildjarn    schedule 13.04.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])

Но все пак смятам, че трябва да отделите итерацията на вашите данни от обработката. Писането на функция от висок ред за итериране е добра идея (в крайна сметка не е досадно, ако кодът на функцията на итератора използва променливи изброители).

person Laurent    schedule 13.04.2011
comment
Не мисля, че картата и zip fns ще работят тук, тъй като моите тестови последователности са с неизвестна (и вероятно) неравна дължина и могат също да съдържат пропуски. Вашият примерен код обаче изглежда като добро начало - благодаря - person BrendanC; 14.04.2011
comment
Примерният код със сигурност ще взриви StackOverflow с 200k записа? - person Robert Jeppesen; 14.04.2011
comment
Не, компилаторът е в състояние да оптимизира тези рекурсивни извиквания и да компилира функцията с обикновен цикъл (проверих генерирания IL). Ако нямате доверие на компилатора, не се колебайте да използвате цикъл while, трансформацията е много лесна за изпълнение тук. - person Laurent; 14.04.2011
comment
IMO, работата с преброители директно налага императивен стил, който почти проваля целта на работата на функционален език на първо място. Принуждаването на seqs в LazyLists поне позволява съвпадение на шаблони и стил на функционално програмиране. - person ildjarn; 14.04.2011
comment
@ildjarn: Цялата библиотека Seq е написана по този начин, това не може да е лошо нещо. Мисля, че такава функция трябва да се направи обща (функция от висок ред) и многократно използваема, така че логиката да може да бъде отделна от променливите изброители. Моят отговор беше за дефиниране на нов примитив за итерация. Прав си, че LazyList може да бъде алтернатива. - person Laurent; 14.04.2011
comment
@Laurent : Точно така, и модулът Seq съществува, така че никой друг да не трябва да издържа да пише код в този стил :-P По никакъв начин не отричам отговора, просто мисля, че това е странно ниво на абстракция за F# код когато съществуват лесни алтернативи :-] - person ildjarn; 14.04.2011