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


57

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 verfügt über einen Kontostand pro Währung. Jeder Kontostand ist also einfach die Summe aller Transaktionen mit einem bestimmten Benutzer und einer bestimmten 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 die Salden sehr schnell aufzufüllen, muss ich sie vorab aggregieren. Gleichzeitig muss ich garantieren, dass ein Kontostand niemals seiner Transaktionshistorie widerspricht.

Meine Optionen sind:

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

    1. Wenden Sie Transaktionen auf die Tabellen transactionsund an balances. Verwenden Sie TRANSACTIONLogik in meiner gespeicherten Prozedurebene, um sicherzustellen, dass Salden und Transaktionen immer synchron sind. (Unterstützt von Jack .)

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

    3. Wenden Sie Transaktionen auf die balancesTabelle an und haben Sie einen Auslöser, der transactionsmir einen neuen Eintrag in der Tabelle 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 transactionsTabelle einfügen, und 1.3der betreffende Saldo wäre nach dem Schema nicht synchron.

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

  3. Haben Sie nur eine transactionsTabelle, aber mit einer zusätzlichen Spalte, um den Saldo direkt nach der Ausführung dieser Transaktion zu speichern. Somit enthält der letzte Transaktionsdatensatz für einen Benutzer und eine Währung auch den aktuellen Kontostand. (Vorgeschlagen von Andrew ; von Garik vorgeschlagene Variante .)

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

  • 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 Wahl getroffen? Was sollte ich beachten?

    Ich weiß beispielsweise, dass Schemaänderungen an der transactionsTabelle eine Neuerstellung der balancesAnsicht erfordern . Selbst wenn ich Transaktionen archiviere, um die Datenbank klein zu halten (z. B. indem ich sie an einen anderen Ort verschiebe und durch Zusammenfassungstransaktionen ersetze), bedeutet die Neuerstellung der Ansicht aus zig Millionen Transaktionen mit jeder Schemaaktualisierung wahrscheinlich eine erheblich längere Ausfallzeit pro Bereitstellung.

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


Transaktionen archivieren:

Lassen Sie mich ein wenig auf die Archivierungstransaktionen und die "Zusammenfassungstransaktionen" eingehen, die ich oben erwähnt habe. Erstens ist in einem Hochlastsystem wie diesem eine regelmäßige Archivierung erforderlich. Ich möchte die Konsistenz zwischen Salden und ihren Transaktionsverläufen gewährleisten und gleichzeitig alte Transaktionen an einen anderen Ort verschieben. Dazu 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 ersetzt durch:

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

Auf diese Weise behält ein Saldo mit archivierten Transaktionen eine vollständige und konsistente Transaktionshistorie bei.


1
Wenn Sie Option 2 wählen (die meiner Meinung nach sauberer ist), schauen Sie unter pgcon.org/2008/schedule/attachments/… nach, wie Sie "materialisierte Ansichten" effizient implementieren können. Für Option 1 wäre Kapitel 11 von Haans und Koppelaars ' Angewandte Mathematik für Datenbankfachleute (machen Sie sich keine Gedanken über den Titel) hilfreich, um eine Vorstellung davon zu bekommen, wie "Übergangsbedingungen" effizient implementiert werden können. Der erste Link ist für PostgreSQL und der zweite für Oracle, aber die Techniken sollten für jedes vernünftige Datenbanksystem funktionieren.
jp

In der Theorie möchten Sie # 3 tun. Um einen "laufenden Saldo" zu erstellen, weisen Sie jeder Transaktion einen Saldo zu. Stellen Sie sicher, dass Sie die Transaktionen definitiv entweder mit einer Seriennummer (bevorzugt) oder einem Zeitstempel bestellen können. Sie sollten wirklich keinen laufenden Saldo "berechnen".
Pbreitenbach

Antworten:


15

Ich bin mit der Buchhaltung nicht vertraut, habe jedoch einige ähnliche Probleme in Umgebungen mit Bestandsaufnahmen 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 im Jahr 2009 folgende Lösung geschrieben :

Das Berechnen von laufenden Summen ist notorisch langsam, egal ob Sie sie mit einem Cursor oder mit einer Dreiecksverknüpfung ausführen. Es ist sehr verlockend zu denormalisieren, laufende Summen in einer Spalte zu speichern, besonders wenn Sie diese häufig auswählen. Beim Denormalisieren müssen Sie jedoch wie gewohnt die Integrität Ihrer denormalisierten Daten gewährleisten. Glücklicherweise können Sie die Integrität von laufenden Summen mit Einschränkungen garantieren. Solange alle Ihre Einschränkungen vertrauenswürdig sind, sind alle laufenden Summen korrekt. Auch auf diese Weise können Sie auf einfache Weise sicherstellen, dass der aktuelle Kontostand (laufende Summen) niemals negativ ist - die Durchsetzung durch andere Methoden kann auch 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

14

Kunden ein Guthaben von weniger als 0 zu verweigern, ist eine Geschäftsregel (die sich schnell ändern würde, da Banken mit Gebühren für Überziehungskredite den größten Teil ihres Geldes verdienen). Sie möchten dies in der Anwendungsverarbeitung behandeln, wenn Zeilen in den Transaktionsverlauf eingefügt werden. Zumal einige Kunden möglicherweise einen Überziehungsschutz haben und andere Gebühren verlangen und andere die Eingabe negativer Beträge nicht zulassen.

Soweit gefällt mir, wohin Sie gehen, aber wenn dies für ein tatsächliches Projekt (nicht für die 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, wonach Menschen Zugang zu ihrem Geld haben.


1
Ich kann sehen, warum die Saldenbeschränkung eigentlich eine Geschäftsregel sein sollte. Die Datenbank stellt lediglich einen Transaktionsservice bereit, und der Benutzer kann selbst entscheiden, was damit geschehen soll.
Nick Chammas

Was halten Sie von Jacks Kommentaren, dass die Verwendung der beiden Tabellen den Entwicklern mehr Flexibilität beim Ändern oder Implementieren von Geschäftslogik bietet? Haben Sie auch direkte Erfahrungen mit indizierten Ansichten, die diese Bedenken bestätigen oder in Frage stellen ?
Nick Chammas

1
Ich würde nicht sagen, dass die Implementierung von Geschäftslogik durch zwei Tabellen Flexibilität bietet. Dies gibt Ihnen mehr Flexibilität bei der Datenarchivierung. Als Bank (zumindest in den USA) haben Sie Gesetze, die festlegen, wie viele Daten Sie aufbewahren müssen. Sie sollten testen, wie die Leistung mit der Ansicht im Vordergrund aussieht, und berücksichtigen, dass Sie bei einer indizierten Ansicht das Schema der zugrunde liegenden Tabellen nicht ändern können. Nur eine andere Sache, über die man nachdenken muss.
Mrdenny

Alle in diesem Artikel genannten Elemente sind für die Verwendung einer indizierten Ansicht von Belang.
Mrdenny

1
Zur Verdeutlichung: IMO bietet eine Transaktions-API mehr Flexibilität beim Implementieren von Geschäftslogik (ohne zwei Tabellen). In diesem Fall würde ich auch zwei Tabellen befürworten (zumindest in Anbetracht der Informationen, die wir bisher haben), da mit dem Ansatz der indizierten Sicht Kompromisse vorgeschlagen wurden (z. B. kann DRI dann nicht verwendet werden, um ein Geschäft mit einem Saldo von> 0 zu erzwingen Regel)
Jack Douglas

13

Ein etwas anderer Ansatz (ähnlich wie bei Ihrer zweiten Option) besteht darin, nur die Transaktionstabelle mit einer 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 Abfrage verbessern können.

Um das aktuelle Guthaben zu erhalten, müssen Sie lediglich den letzten Datensatz abrufen.

Methoden, um den letzten Datensatz abzurufen :

/* 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 angegebenen Reihenfolge einfügen (z. B. um ein Problem zu beheben oder einen falschen Startsaldo), 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 einen genauen Saldo zu erhalten.

    -- 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 Saldo auf einfache Weise validieren, und wenn der Saldo nicht mehr synchron ist, können Sie genau feststellen, wann er aus dem Gleichgewicht geraten ist, da der Transaktionsverlauf sich selbst dokumentiert.

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


3
Es SELECT TOP (1) ... ORDER BY TransactionDate DESCwird sehr schwierig sein, das so zu implementieren, dass SQL Server die Transaktionstabelle nicht ständig durchsucht. Alex Kuznetsov hat hier eine Lösung für ein ähnliches Designproblem veröffentlicht, die diese Antwort perfekt ergänzt.
Nick Chammas

2
+1 Ich benutze einen ähnlichen Ansatz. Übrigens müssen wir sehr vorsichtig sein und sicherstellen, dass unser Code bei gleichzeitiger Auslastung korrekt funktioniert.
AK

12

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

Nachdem Sie diese Diskussionen ebenfalls gelesen haben, bin ich mir nicht sicher, warum Sie sich für die DRI- Lösung entschieden haben, anstatt der sinnvollsten 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 enorme praktische Vorteile, wenn Sie den Luxus haben, den gesamten Zugriff auf die Daten über Ihre Transaktions-API einzuschränken . Sie verlieren den sehr wichtigen Vorteil von DRI: Die Integrität wird durch die Datenbank garantiert. In jedem Modell mit ausreichender Komplexität gibt es jedoch einige Geschäftsregeln, die von DRI nicht erzwungen werden können .

Ich würde empfehlen, DRI zu verwenden, wenn dies möglich ist, um Geschäftsregeln durchzusetzen, ohne Ihr Modell zu stark zu verbiegen:

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

Sobald Sie erwägen, Ihr Modell auf diese Weise zu verschmutzen, bewegen Sie sich vermutlich in einem Bereich, in dem der Nutzen von DRI durch die von Ihnen eingeführten Schwierigkeiten überwiegt. Stellen Sie sich zum Beispiel vor, dass ein Fehler in Ihrem Archivierungsprozess theoretisch dazu führen könnte, dass Ihre goldene Regel (bei der der Saldo immer gleich der Summe der Transaktionen ist) stillschweigend mit einer DRI-Lösung bricht .

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

  • Wir sollten dies sowieso tun, wenn es überhaupt möglich ist. Unabhängig davon, welche Lösung Sie für dieses spezielle Problem wählen, bietet es Ihnen mehr Flexibilität beim Entwurf und Kontrolle über Ihre Daten. Jeder Zugriff wird dann in Bezug auf die Geschäftslogik "transaktional" und nicht nur in Bezug auf die Datenbanklogik.
  • Sie können Ihr Modell ordentlich halten
  • Sie können eine viel breitere Palette und Komplexität von Geschäftsregeln "erzwingen" (wobei zu beachten ist, dass das Konzept "erzwingen" weniger wichtig ist als bei DRI).
  • Sie können DRI weiterhin verwenden, wo immer dies sinnvoll ist, um dem Modell eine stabilere zugrunde liegende Integrität zu verleihen. Dies kann als Überprüfung Ihrer Transaktionslogik dienen
  • Die meisten Leistungsprobleme, die Sie stören, werden verschwinden
  • Das Einführen neuer Anforderungen kann viel einfacher sein - zum Beispiel: Komplexe Regeln für umstrittene Transaktionen können Sie von einem reinen DRI-Ansatz abhalten, was eine Menge unnötigen Aufwand bedeutet
  • Das Partitionieren oder Archivieren von historischen Daten ist weniger riskant und schmerzhaft

--bearbeiten

Um die Archivierung zu ermöglichen, ohne die Komplexität oder das Risiko zu erhöhen, können Sie Zusammenfassungstabellen in einer separaten Zusammenfassungstabelle speichern, die fortlaufend generiert wird (Ausleihe von @Andrew und @Garik).

Zum Beispiel, wenn die Zusammenfassungen monatlich sind:

  • Bei jeder Transaktion (über Ihre API) wird eine entsprechende Aktualisierung durchgeführt oder in die Übersichtstabelle eingefügt
  • Die Übersichtstabelle wird nie archiviert, aber Archivierungstransaktionen werden so einfach wie ein Löschen (oder Löschen einer Partition?)
  • Jede Zeile in der Übersichtstabelle enthält "Anfangsbestand" und "Betrag".
  • Prüfbeschränkungen wie 'Anfangsbestand' + 'Betrag'> 0 und 'Anfangsbestand'> 0 können auf die Übersichtstabelle angewendet werden
  • Zusammenfassungszeilen können in einem 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).

In Bezug auf Ihre Bearbeitung: Sie schlagen vor, diese Übersichtstabelle neben der Hauptbilanztabelle zu haben? Wird die Salden-Tabelle dann effektiv zu einer Übersichtstabelle, die nur die Datensätze für den aktuellen Monat enthält (da beide dieselbe Art von Daten speichern)? Wenn ich es richtig verstanden habe, warum ersetzen Sie dann nicht einfach die Salden-Tabelle durch die entsprechende Partition in der Übersichtstabelle?
Nick Chammas

Tut mir leid, dass Sie Recht haben, das ist unklar - ich wollte auf die Salden-Tabelle verzichten, da es immer eine wichtige Suche in der Zusammenfassungstabelle wäre, um den aktuellen Saldo zu erhalten (nicht wahr mit Andrews Vorschlag AFAIK). Der Vorteil ist, dass die Berechnung von Salden aus früheren Zeiten einfacher wird und es einen klareren Prüfpfad für Salden gibt, wenn sie schief gehen.
Jack Douglas

6

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 das Gleichgewicht nur durch Auffinden des letzten Zusammenfassungsdatensatzes erreichen.

 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 besteht darin, die Anzahl der Zusammenfassungsdatensätze zu verringern. Wir können einen Saldodatensatz am Ende (und / oder Anfang) des Tages haben. Wie Sie wissen, muss jede Bank operational daysie öffnen und schließen, um einige Zusammenfassungsoperationen für diesen Tag durchzuführen. Es ermöglicht uns die einfache Berechnung von Zinsen, indem wir den täglichen Saldodatensatz verwenden, 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.


4

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

Bei diesem Ansatz ist garantiert, dass das Echtzeitguthaben mit der Transaktionstabelle synchron ist. Dabei spielt es keine Rolle, ob gespeicherte Prozeduren oder psql oder jdbc verwendet werden. Sie können Ihren negativen Kontostand bei Bedarf überprüfen lassen. Leistung wird kein Problem sein. Um die Echtzeitbilanz zu erhalten, handelt es sich um eine Einzelabfrage.

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


3

In Oracle können Sie dazu nur die Transaktionstabelle mit einer schnell aktualisierbaren Materialized View verwenden, die die Aggregation zur Bildung des Saldos vornimmt. Sie definieren den Auslöser für die materialisierte 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 ist hier http://www.sqlsnippets.com/en/topic-12896.html

Ich kenne sqlserver nicht, aber vielleicht gibt es eine ähnliche Option?


2
Materialisierte Ansichten in Oracle ähneln der "indizierten Ansicht" von SQL Server, werden jedoch automatisch aktualisiert und nicht auf eine explizit verwaltete Art und Weise, wie z. B. das "ON COMMIT" -Verhalten von Oracle. Siehe social.msdn.microsoft.com/Forums/fi-FI/transactsql/thread/… und techembassy.blogspot.com/2007/01/…
GregW
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.