it-swarm.com.de

Schreiben eines einfachen Bankschemas: Wie soll ich meine Guthaben mit dem Transaktionsverlauf synchronisieren?

Ich schreibe das Schema für eine einfache Bankdatenbank. Hier sind die grundlegenden Spezifikationen:

  • In der Datenbank werden Transaktionen für einen Benutzer und eine Währung gespeichert.
  • Jeder Benutzer hat ein Guthaben pro Währung, daher ist jedes Guthaben einfach die Summe aller Transaktionen gegen einen bestimmten Benutzer und eine bestimmte Währung.
  • Ein Saldo kann nicht negativ sein.

Die Bankanwendung kommuniziert mit ihrer Datenbank ausschließlich über gespeicherte Prozeduren.

Ich erwarte, dass diese Datenbank Hunderttausende neuer Transaktionen pro Tag akzeptiert und Abfragen in einer höheren Größenordnung ausgleicht. Um Guthaben sehr schnell bereitzustellen, muss ich sie voraggregieren. Gleichzeitig muss ich sicherstellen, dass ein Saldo niemals seiner Transaktionshistorie widerspricht.

Meine Optionen sind:

  1. Haben Sie eine separate balances Tabelle und führen Sie einen der folgenden Schritte aus:

    1. Wenden Sie Transaktionen sowohl auf die Tabellen transactions als auch balances an. Verwenden Sie die Logik TRANSACTION in meiner gespeicherten Prozedurebene, um sicherzustellen, dass Salden und Transaktionen immer synchron sind. (Unterstützt von Jack .)

    2. Wenden Sie Transaktionen auf die Tabelle transactions an und haben Sie einen Auslöser, der die Tabelle balances für mich mit dem Transaktionsbetrag aktualisiert.

    3. Wenden Sie Transaktionen auf die Tabelle balances an und haben Sie einen Auslöser, der mir einen neuen Eintrag in der Tabelle transactions mit dem Transaktionsbetrag hinzufügt.

    Ich muss mich auf sicherheitsbasierte Ansätze verlassen, um sicherzustellen, dass keine Änderungen außerhalb der gespeicherten Prozeduren vorgenommen werden können. Andernfalls könnte beispielsweise ein Prozess eine Transaktion direkt in die Tabelle transactions einfügen, und unter Schema 1.3 Wäre der relevante Saldo nicht synchron.

  2. Haben Sie eine balances indizierte Ansicht , die die Transaktionen entsprechend aggregiert. Die Speicher-Engine garantiert, dass die Salden mit ihren Transaktionen synchron bleiben. Daher muss ich mich nicht auf sicherheitsbasierte Ansätze verlassen, um dies zu gewährleisten. Andererseits kann ich nicht mehr erzwingen, dass Salden nicht mehr negativ sind, da Ansichten - selbst indizierte Ansichten - keine CHECK Einschränkungen haben können. (Unterstützt von Denny .)

  3. Haben Sie nur eine transactions -Tabelle , aber mit einer zusätzlichen Spalte, um den Saldo zu speichern, der unmittelbar nach der Ausführung dieser Transaktion wirksam wird. Somit enthält der letzte Transaktionsdatensatz für einen Benutzer und eine Währung auch deren aktuellen Kontostand. (Vorgeschlagen von Andrew ; Variante vorgeschlagen von garik .)

Als ich dieses Problem zum ersten Mal anging, las ich diesezwei Diskussionen und entschied mich für die Option 2. Als Referenz können Sie eine Bare-Bones-Implementierung davon sehen hier .

  • Haben Sie eine solche Datenbank mit einem hohen Lastprofil entworfen oder verwaltet? Was war Ihre Lösung für dieses Problem?

  • Glaubst du, ich habe die richtige Designwahl getroffen? Gibt es etwas, an das ich denken sollte?

    Ich weiß beispielsweise, dass Schemaänderungen an der Tabelle transactions erfordern, dass ich die Ansicht balances neu erstelle. Selbst wenn ich Transaktionen archiviere, um die Datenbank klein zu halten (z. B. indem ich sie an einen anderen Ort verschiebe und durch zusammenfassende Transaktionen ersetze), bedeutet die Notwendigkeit, die Ansicht von zig Millionen Transaktionen mit jedem Schema-Update neu zu erstellen, wahrscheinlich erheblich mehr Ausfallzeiten pro Bereitstellung.

  • Wie kann ich garantieren, dass kein Saldo negativ ist, wenn die indizierte Ansicht der richtige Weg ist?


Archivierung von Transaktionen:

Lassen Sie mich etwas näher auf die Archivierung von Transaktionen und die oben erwähnten "zusammenfassenden Transaktionen" eingehen. Erstens ist eine regelmäßige Archivierung in einem solchen Hochlastsystem eine Notwendigkeit. Ich möchte die Konsistenz zwischen Salden und ihren Transaktionshistorien aufrechterhalten und gleichzeitig zulassen, dass alte Transaktionen an einen anderen Ort verschoben werden. Zu diesem Zweck ersetze ich jeden Stapel archivierter Transaktionen durch eine Zusammenfassung ihrer Beträge pro Benutzer und Währung.

So zum Beispiel diese Liste von Transaktionen:

user_id    currency_id      amount    is_summary
------------------------------------------------
      3              1       10.60             0
      3              1      -55.00             0
      3              1      -12.12             0

wird archiviert und durch Folgendes ersetzt:

user_id    currency_id      amount    is_summary
------------------------------------------------
      3              1      -56.52             1

Auf diese Weise wird durch einen Saldo mit archivierten Transaktionen ein vollständiger und konsistenter Transaktionsverlauf aufrechterhalten.

60
Nick Chammas

Ich bin mit der Buchhaltung nicht vertraut, habe jedoch einige ähnliche Probleme in Umgebungen mit Inventartyp gelöst. Ich speichere laufende Summen in derselben Zeile wie die Transaktion. Ich verwende Einschränkungen, damit meine Daten auch bei hoher Parallelität niemals falsch sind. Ich habe damals 2009 die folgende Lösung geschrieben : :

Die Berechnung laufender Summen ist notorisch langsam, egal ob Sie dies mit einem Cursor oder mit einer dreieckigen Verknüpfung tun. Es ist sehr verlockend, laufende Summen zu denormalisieren und in einer Spalte zu speichern, insbesondere wenn Sie sie häufig auswählen. Wie üblich beim Denormalisieren müssen Sie jedoch die Integrität Ihrer denormalisierten Daten gewährleisten. Glücklicherweise können Sie die Integrität laufender Summen mit Einschränkungen gewährleisten. Solange alle Ihre Einschränkungen vertrauenswürdig sind, sind alle laufenden Summen korrekt. Auf diese Weise können Sie auch leicht sicherstellen, dass der aktuelle Kontostand (laufende Summen) niemals negativ ist. Die Durchsetzung durch andere Methoden kann ebenfalls sehr langsam sein. Das folgende Skript demonstriert die Technik.

CREATE TABLE Data.Inventory(InventoryID INT NOT NULL IDENTITY,
  ItemID INT NOT NULL,
  ChangeDate DATETIME NOT NULL,
  ChangeQty INT NOT NULL,
  TotalQty INT NOT NULL,
  PreviousChangeDate DATETIME NULL,
  PreviousTotalQty INT NULL,
  CONSTRAINT PK_Inventory PRIMARY KEY(ItemID, ChangeDate),
  CONSTRAINT UNQ_Inventory UNIQUE(ItemID, ChangeDate, TotalQty),
  CONSTRAINT UNQ_Inventory_Previous_Columns 
     UNIQUE(ItemID, PreviousChangeDate, PreviousTotalQty),
  CONSTRAINT FK_Inventory_Self FOREIGN KEY(ItemID, PreviousChangeDate, PreviousTotalQty)
    REFERENCES Data.Inventory(ItemID, ChangeDate, TotalQty),
  CONSTRAINT CHK_Inventory_Valid_TotalQty CHECK(
         TotalQty >= 0 
     AND (TotalQty = COALESCE(PreviousTotalQty, 0) + ChangeQty)
  ),
  CONSTRAINT CHK_Inventory_Valid_Dates_Sequence CHECK(PreviousChangeDate < ChangeDate),
  CONSTRAINT CHK_Inventory_Valid_Previous_Columns CHECK(
        (PreviousChangeDate IS NULL AND PreviousTotalQty IS NULL)
     OR (PreviousChangeDate IS NOT NULL AND PreviousTotalQty IS NOT NULL)
  )
);

-- beginning of inventory for item 1
INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)
VALUES(1, '20090101', 10, 10, NULL, NULL);

-- cannot begin the inventory for the second time for the same item 1
INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)
VALUES(1, '20090102', 10, 10, NULL, NULL);


Msg 2627, Level 14, State 1, Line 10

Violation of UNIQUE KEY constraint 'UNQ_Inventory_Previous_Columns'. 
Cannot insert duplicate key in object 'Data.Inventory'.

The statement has been terminated.


-- add more
DECLARE @ChangeQty INT;
SET @ChangeQty = 5;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20090103', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

SET @ChangeQty = 3;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20090104', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

SET @ChangeQty = -4;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20090105', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

-- try to violate chronological order
SET @ChangeQty = 5;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20081231', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

Msg 547, Level 16, State 0, Line 4

The INSERT statement conflicted with the CHECK constraint 
"CHK_Inventory_Valid_Dates_Sequence". 
The conflict occurred in database "Test", table "Data.Inventory".

The statement has been terminated.

SELECT ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty
FROM Data.Inventory ORDER BY ChangeDate;

ChangeDate              ChangeQty   TotalQty    PreviousChangeDate      PreviousTotalQty
----------------------- ----------- ----------- ----------------------- -----
2009-01-01 00:00:00.000 10          10          NULL                    NULL
2009-01-03 00:00:00.000 5           15          2009-01-01 00:00:00.000 10
2009-01-04 00:00:00.000 3           18          2009-01-03 00:00:00.000 15
2009-01-05 00:00:00.000 -4          14          2009-01-04 00:00:00.000 18


-- try to change a single row, all updates must fail
UPDATE Data.Inventory SET ChangeQty = ChangeQty + 2 WHERE InventoryID = 3;
UPDATE Data.Inventory SET TotalQty = TotalQty + 2 WHERE InventoryID = 3;

-- try to delete not the last row, all deletes must fail
DELETE FROM Data.Inventory WHERE InventoryID = 1;
DELETE FROM Data.Inventory WHERE InventoryID = 3;

-- the right way to update
DECLARE @IncreaseQty INT;

SET @IncreaseQty = 2;

UPDATE Data.Inventory 
SET 
     ChangeQty = ChangeQty 
   + CASE 
        WHEN ItemID = 1 AND ChangeDate = '20090103' 
        THEN @IncreaseQty 
        ELSE 0 
     END,
  TotalQty = TotalQty + @IncreaseQty,
  PreviousTotalQty = PreviousTotalQty + 
     CASE 
        WHEN ItemID = 1 AND ChangeDate = '20090103' 
        THEN 0 
        ELSE @IncreaseQty 
     END
WHERE ItemID = 1 AND ChangeDate >= '20090103';

SELECT ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty
FROM Data.Inventory ORDER BY ChangeDate;

ChangeDate              ChangeQty   TotalQty    PreviousChangeDate      PreviousTotalQty
----------------------- ----------- ----------- ----------------------- ----------------
2009-01-01 00:00:00.000 10          10          NULL                    NULL
2009-01-03 00:00:00.000 7           17          2009-01-01 00:00:00.000 10
2009-01-04 00:00:00.000 3           20          2009-01-03 00:00:00.000 17
2009-01-05 00:00:00.000 -4          16          2009-01-04 00:00:00.000 20
17
A-K

Ein etwas anderer Ansatz (ähnlich Ihrer 2. Option) besteht darin, nur die Transaktionstabelle mit der Definition von:

CREATE TABLE Transaction (
      UserID              INT
    , CurrencyID          INT 
    , TransactionDate     DATETIME  
    , OpeningBalance      MONEY
    , TransactionAmount   MONEY
);

Möglicherweise möchten Sie auch eine Transaktions-ID/Bestellung, damit Sie zwei Transaktionen mit demselben Datum bearbeiten und Ihre Abrufabfrage verbessern können.

Um den aktuellen Kontostand zu erhalten, müssen Sie lediglich den letzten Datensatz abrufen.

Methoden zum Abrufen des letzten Datensatzes :

/* For a single User/Currency */
Select TOP 1 *
FROM dbo.Transaction
WHERE UserID = 3 and CurrencyID = 1
ORDER By TransactionDate desc

/* For multiple records ie: to put into a view (which you might want to index) */
SELECT
    C.*
FROM
    (SELECT 
        *, 
        ROW_NUMBER() OVER (
           PARTITION BY UserID, CurrencyID 
           ORDER BY TransactionDate DESC
        ) AS rnBalance 
    FROM Transaction) C
WHERE
    C.rnBalance = 1
ORDER BY
    C.UserID, C.CurrencyID

Nachteile:

  • Wenn Sie eine Transaktion außerhalb der Reihenfolge einfügen (dh um ein Problem/einen falschen Startsaldo zu korrigieren), müssen Sie möglicherweise Aktualisierungen für alle nachfolgenden Transaktionen kaskadieren.
  • Transaktionen für den Benutzer/die Währung müssten serialisiert werden, um ein genaues Gleichgewicht aufrechtzuerhalten.

    -- Example of getting the current balance and locking the 
    -- last record for that User/Currency.
    -- This lock will be freed after the Stored Procedure completes.
    SELECT TOP 1 @OldBalance = OpeningBalance + TransactionAmount  
    FROM dbo.Transaction with (rowlock, xlock)   
    WHERE UserID = 3 and CurrencyID = 1  
    ORDER By TransactionDate DESC;
    

Vorteile:

  • Sie müssen nicht mehr zwei separate Tabellen pflegen ...
  • Sie können den Kontostand leicht validieren, und wenn der Kontostand nicht mehr synchron ist, können Sie genau feststellen, wann er aus dem Gleichgewicht geraten ist, wenn der Transaktionsverlauf selbstdokumentierend wird.

Bearbeiten: Einige Beispielabfragen zum Abrufen des aktuellen Kontostands und zum Hervorheben von con (Danke @Jack Douglas)

15

Es ist eine Geschäftsregel, Kunden nicht zu erlauben, ein Guthaben von weniger als 0 zu haben (was sich schnell ändern würde, da die Gebühren für Dinge wie Überziehungszahlungen das meiste Geld von Banken verdienen). Sie sollten dies in der Anwendungsverarbeitung behandeln, wenn Zeilen in den Transaktionsverlauf eingefügt werden. Vor allem, weil einige Kunden möglicherweise einen Überziehungsschutz haben und einige Gebühren erheben und andere die Eingabe negativer Beträge nicht zulassen.

Bisher gefällt mir, wohin Sie damit gehen, aber wenn dies für ein tatsächliches Projekt (nicht für eine Schule) ist, müssen verdammt viele Gedanken in Geschäftsregeln usw. gesteckt werden. Sobald Sie ein Bankensystem eingerichtet haben und beim Laufen gibt es nicht viel Raum für eine Neugestaltung, da es sehr spezifische Gesetze gibt, nach denen Menschen Zugang zu ihrem Geld haben.

14
mrdenny

Nachdem ich diese beiden Diskussionen gelesen hatte, entschied ich mich für Option 2

Nachdem ich auch diese Diskussionen gelesen habe, bin ich mir nicht sicher, warum Sie sich für die DRI Lösung entschieden haben, und zwar über die sinnvollste der anderen Optionen, die Sie skizzieren:

Wenden Sie Transaktionen sowohl auf die Transaktions- als auch auf die Salden-Tabelle an. Verwenden Sie die TRANSACTION-Logik in meiner Schicht für gespeicherte Prozeduren, um sicherzustellen, dass Salden und Transaktionen immer synchron sind.

Diese Art von Lösung bietet immense praktische Vorteile, wenn Sie den Luxus haben, alleZugriffe auf die Daten über Ihre Transaktions-API einzuschränken. Sie verlieren den sehr wichtigen Vorteil von DRI, nämlich dass die Integrität durch die Datenbank garantiert wird. In jedem Modell mit ausreichender Komplexität gibt es jedoch einige Geschäftsregeln, die von DRI nicht durchgesetzt werden können .

Ich würde empfehlen, DRI nach Möglichkeit zu verwenden, um Geschäftsregeln durchzusetzen, ohne Ihr Modell zu stark zu verbiegen, um dies zu ermöglichen:

Selbst wenn ich Transaktionen archiviere (z. B. indem ich sie an einen anderen Ort verschiebe und durch zusammenfassende Transaktionen ersetze)

Sobald Sie überlegen, Ihr Modell so zu verschmutzen, bewegen Sie sich in den Bereich, in dem der Nutzen von DRI durch die von Ihnen eingeführten Schwierigkeiten aufgewogen wird. Stellen Sie sich zum Beispiel vor, dass ein Fehler in Ihrem Archivierungsprozess theoretisch dazu führen könnte, dass Ihre goldene Regel (die immergleich der Summe der Transaktionen ausgleicht) zu leise brechen mit einer DRI-Lösung .

Hier ist eine Zusammenfassung der Vorteile des Transaktionsansatzes, wie ich sie sehe:

  • Wir sollten das sowieso tun, wenn es überhaupt möglich ist. Unabhängig davon, welche Lösung Sie für dieses spezielle Problem wählen, erhalten Sie mehr Designflexibilität und Kontrolle über Ihre Daten. Jeder Zugriff wird dann in Bezug auf die Geschäftslogik und nicht nur in Bezug auf die Datenbanklogik "transaktional".
  • Sie können Ihr Modell ordentlich halten
  • Sie können einen viel größeren Bereich und eine viel größere Komplexität von Geschäftsregeln "durchsetzen" (wobei zu beachten ist, dass das Konzept der "Durchsetzung" lockerer ist als bei DRI).
  • Sie können DRI weiterhin verwenden, wo immer dies praktikabel ist, um dem Modell eine robustere zugrunde liegende Integrität zu verleihen - und dies kann als Überprüfung Ihrer Transaktionslogik dienen
  • Die meisten Leistungsprobleme, die Sie beunruhigen, werden verschwinden
  • Die Einführung neuer Anforderungen kann viel einfacher sein - zum Beispiel: Komplexe Regeln für umstrittene Transaktionen können Sie später von einem reinen DRI-Ansatz abbringen, was viel verschwendeten Aufwand bedeutet
  • Das Partitionieren oder Archivieren historischer Daten wird viel weniger riskant und schmerzhaft

--bearbeiten

Um die Archivierung zu ermöglichen, ohne Komplexität oder Risiko hinzuzufügen, können Sie Zusammenfassungszeilen in einer separaten Übersichtstabelle aufbewahren, die kontinuierlich generiert wird (Ausleihe von @Andrew und @Garik).

Wenn die Zusammenfassungen beispielsweise monatlich sind:

  • jedes Mal, wenn eine Transaktion (über Ihre API) erfolgt, erfolgt eine entsprechende Aktualisierung oder Einfügung in die Übersichtstabelle
  • die Übersichtstabelle ist niearchiviert, aber das Archivieren von Transaktionen wird so einfach wie das Löschen (oder Löschen von Partitionen?).
  • jede Zeile in der Übersichtstabelle enthält "Eröffnungssaldo" und "Betrag".
  • prüfbeschränkungen wie 'Eröffnungssaldo' + 'Betrag'> 0 und 'Eröffnungssaldo'> 0 können auf die Übersichtstabelle angewendet werden
  • zusammenfassungszeilen könnten in einen monatlichen Stapel eingefügt werden, um das Sperren der letzten Zusammenfassungszeile zu vereinfachen (es würde immer eine Zeile für den aktuellen Monat geben).

Nick.

Die Hauptidee besteht darin, Saldo- und Transaktionsdatensätze in derselben Tabelle zu speichern. Es ist historisch passiert, dachte ich. In diesem Fall können wir also das Gleichgewicht erreichen, indem wir nur den letzten zusammenfassenden Datensatz suchen.

 id   user_id    currency_id      amount    is_summary (or record_type)
----------------------------------------------------
  1       3              1       10.60             0
  2       3              1       10.60             1    -- summary after transaction 1
  3       3              1      -55.00             0
  4       3              1      -44.40             1    -- summary after transactions 1 and 3
  5       3              1      -12.12             0
  6       3              1      -56.52             1    -- summary after transactions 1, 3 and 5 

Eine bessere Variante ist die Verringerung der Anzahl der Zusammenfassungsdatensätze. Wir können am Ende (und/oder am Anfang) des Tages einen Saldodatensatz haben. Wie Sie wissen, hat jede Bank operational day zum Öffnen und Schließen, um einige Zusammenfassungsvorgänge für diesen Tag auszuführen. Es ermöglicht uns die einfache Berechnung von Zinsen unter Verwendung der täglichen Bilanzbilanz, zum Beispiel:

user_id    currency_id      amount    is_summary    oper_date
--------------------------------------------------------------
      3              1       10.60             0    01/01/2011 
      3              1      -55.00             0    01/01/2011
      3              1      -44.40             1    01/01/2011 -- summary at the end of day (01/01/2011)
      3              1      -12.12             0    01/02/2011
      3              1      -56.52             1    01/02/2011 -- summary at the end of day (01/02/2011)

Glück.

6
garik

Basierend auf Ihren Anforderungen erscheint Option 1 am besten. Obwohl ich mein Design hätte, nur Einfügungen in die Transaktionstabelle zuzulassen. Und haben Sie den Auslöser für die Transaktionstabelle, um die Echtzeit-Bilanztabelle zu aktualisieren. Sie können Datenbankberechtigungen verwenden, um den Zugriff auf diese Tabellen zu steuern.

Bei diesem Ansatz wird garantiert, dass der Echtzeitsaldo mit der Transaktionstabelle synchronisiert ist. Dabei spielt es keine Rolle, ob gespeicherte Prozeduren oder psql oder jdbc verwendet werden. Bei Bedarf können Sie Ihren Negativsaldo überprüfen lassen. Leistung wird kein Problem sein. Um die Echtzeitbilanz zu erhalten, handelt es sich um eine Singleton-Abfrage.

Die Archivierung hat keinen Einfluss auf diesen Ansatz. Sie können eine wöchentliche, monatliche und jährliche Übersichtstabelle erstellen, auch wenn dies beispielsweise für Berichte erforderlich ist.

4
Elan Fisoc

In Oracle können Sie dazu nur die Transaktionstabelle mit einer schnell aktualisierbaren materialisierten Ansicht verwenden, die die Aggregation zur Bildung des Saldos durchführt. Sie definieren den Auslöser in der Materialisierten Ansicht. Wenn die materialisierte Ansicht mit 'ON COMMIT' definiert ist, wird das Hinzufügen/Ändern von Daten in den Basistabellen effektiv verhindert. Der Trigger erkennt die [in] gültigen Daten und löst eine Ausnahme aus, bei der die Transaktion zurückgesetzt wird. Ein schönes Beispiel finden Sie hier http://www.sqlsnippets.com/en/topic-12896.html

Ich kenne SQL Server nicht, aber vielleicht hat es eine ähnliche Option?

3
ik_zelf