Montag, Februar 18, 2013

MERGE im SQL Server

Zu den Dingen, die ich in der Vergangenheit in Hinblick auf den SQL Server häufiger gelobt habe, gehört das extrem flexible und mächtige MERGE-Statement, mit dem man z.B. die Ergänzung einer Slowly Changing Dimension Typ 2 in einer einzigen Operation zusammenfassen kann (was in Oracle mehrere SQL-Schritte erfordert, da es dort unter anderem nicht die Unterscheidung zwischen NOT MATCHED BY SOURCE und NOT MATCHED BY TARGET gibt). Leider scheint diese Komplexität aber auf Kosten der Stabilität zu gehen, wie Aaron Bertrand dieser Tage festgestellt hat: Im vorgestellten Fall führen MERGEs auf indexed views (also jenen Objekten, die Oracle und andere als Materialized Views bezeichnen) unter bestimmten – und alles andere als exotischen – Bedingungen zu einem „wrong result“ bug (einem Fehlerfall, der in relationalen Datenbanken deutlich häufiger vorkommt, als man annehmen sollte). Und offenbar ist das nur einer von relativ vielen Bugs, die beim SQL Server im Zusammenhang mit MERGE auftreten (und die zumindest zum Teil mit den Problemen zu tun haben, die der SQL Server immer noch mit der Sicherstellung der Lesekonsistenz hat) und deshalb kommt der Herr Bertrand zu einem recht unerfreulichen Fazit:
Personally, I think the syntax is great (albeit daunting to learn), but every time an issue comes up, it erodes my confidence in the practicality of replacing existing DML with the new construct.
With that in mind, not to be Chicken Little, but I would not feel comfortable recommending anyone to use MERGE unless they implement extremely comprehensive testing. Some of these issues are also present with standard UPSERT methodologies, but the problems are more obvious there. MERGE, merely through its single-statement nature, makes you want to believe in magic. Maybe someday it will deliver, but right now I know it’s not going to be able to saw a person in half without some serious help.
Offen gesagt halte ich “extremely comprehensive testing“ für ziemlich viel verlangt, wenn man mit Grundfunktionalitäten der Datenbank arbeitet … - aber ich spare mir den Kommentar, das so was (bzw. Ähnliches) mit Oracle nicht passieren könnte (obwohl mir dort eine grundsätzliche Instabilität des MERGE-Befehls nicht bekannt ist).

3 Kommentare:

  1. Anzumerken waere noch, dass man das NOT-MATCHED-BY-SOURCE-Verhalten bei Oracle simulieren kann, indem man eine CTE als Quelle fuer das MERGE nimmt und darin die Existenz ueberprueft. Nicht ganz so offensichtlich/explizit, funktioniert aber.

    AntwortenLöschen
  2. Hallo Jochen,
    meinst Du eine vorangehende Ergänzung der eigentlich fehlenden Sätze in der Source-Menge, wie sie z.B. hier beschrieben wird: http://stackoverflow.com/questions/10539627/when-doing-a-merge-in-oracle-sql-how-can-i-update-rows-that-arent-matched-in-t ? Das funktioniert sicher, aber ich würde auf Anhieb vermuten, dass dabei mehr Arbeit anfällt, als eigentlich erforderlich wäre, weil die Bestimmung des Deltas doppelt erfolgt (im Source-Block und im eigentlichen Merge) - aber ich kann mich täuschen: wäre interessant, mal einen Blick auf den Ausführungsplan zu werfen.

    Gruß

    Martin

    AntwortenLöschen
  3. Ja genau, so aehnlich wie es da bei Stackoverflow beschrieben ist wuerde ich das machen. Beispielcode:

    merge into (select * from old_data where is_current = 1) t
    using (
    with new as (
    select keycol1
    , keycol2
    , valuecol1
    , ...
    , 1 as is_current
    from new_data
    )
    , deleted as (
    select keycol1
    , keycol2
    , valuecol1
    , ...
    , 0 as is_current
    from old_data
    where is_current = 1
    and not exists(
    select 1
    from new_data
    where new_data.keycol1 = old_data.keycol1
    and new_data.keycol2 = old_data.keycol2
    )
    )
    select keycol1, keycol2, valuecol1, ..., is_current from new
    union all
    select keycol1, keycol2, valuecol1, ..., is_current from deleted
    ) s
    on (
    s.keycol1 = t.keycol1 and
    s.keycol2 = t.keycol2
    )
    when matched then update set
    ...
    when not matched then insert
    ...
    ;

    Wobei hier Aenderungen anhand der Key-Spalten einfach nur aktualisiert werden. Fuer eine vollstaendige Historisierung muesste man noch unterscheiden, ob sich bei den neuen Daten etwas an den Value-Spalten geaendert hat und dann ggf. einen neuen Satz einfuegen sowie den alten Satz begrenzen.

    AntwortenLöschen