Transaktionen, Referenzen und wie kann die doppelte Buchführung erzwungen werden? (PG)

8

Doppelte Buchführung ist

eine Reihe von Regeln für die Erfassung von Finanzinformationen in einem Finanzbuchhaltungssystem, in dem jede Transaktion oder jedes Ereignis mindestens zwei verschiedene nominale Sachkonten ändert.

Ein Konto kann "belastet" oder "gutgeschrieben" werden, und die Summe aller Gutschriften muss gleich der Summe aller Belastungen sein.

Wie würden Sie dies in einer Postgres-Datenbank implementieren? Angabe der folgenden DDL:

CREATE TABLE accounts(
    account_id serial NOT NULL PRIMARY KEY,
    account_name varchar(64) NOT NULL
);


CREATE TABLE transactions(
    transaction_id serial NOT NULL PRIMARY KEY,
    transaction_date date NOT NULL
);


CREATE TABLE transactions_details(
    id serial8 NOT NULL PRIMARY KEY,
    transaction_id integer NOT NULL 
        REFERENCES transactions (transaction_id)
        ON UPDATE CASCADE
        ON DELETE CASCADE
        DEFERRABLE INITIALLY DEFERRED,
    account_id integer NOT NULL
        REFERENCES accounts (account_id)
        ON UPDATE CASCADE
        ON DELETE RESTRICT
        NOT DEFERRABLE INITIALLY IMMEDIATE,
    amount decimal(19,6) NOT NULL,
    flag varchar(1) NOT NULL CHECK (flag IN ('C','D'))
);

Hinweis: In der Tabelle transaction_details wird kein explizites Debit- / Kreditkonto angegeben, da das System in der Lage sein sollte, mehr als ein Konto in einer einzelnen Transaktion zu belasten / gutzuschreiben.

Diese DDL schafft die folgende Anforderung: Nachdem eine Datenbanktransaktion für die Tabelle transaction_details festgeschrieben wurde, muss sie für jeden den gleichen Betrag belasten und gutschreiben transaction_id, z .

INSERT INTO accounts VALUES (100, 'Accounts receivable');
INSERT INTO accounts VALUES (200, 'Revenue');

INSERT INTO transactions VALUES (1, CURRENT_DATE);

-- The following must succeed
BEGIN;
    INSERT INTO transactions_details VALUES (DEFAULT, 1, 100, '1000'::decimal, 'D');
    INSERT INTO transactions_details VALUES (DEFAULT, 1, 200, '1000'::decimal, 'C');
COMMIT;


-- But this must raise some error
BEGIN;
    INSERT INTO transactions_details VALUES (DEFAULT, 1, 100, '1000'::decimal, 'D');
    INSERT INTO transactions_details VALUES (DEFAULT, 1, 200, '500'::decimal, 'C');
COMMIT;

Ist es möglich, dies in einer PostgreSQL-Datenbank zu implementieren? Ohne Angabe zusätzlicher Tabellen zum Speichern von Triggerzuständen.

Cochise Ruhulessin
quelle

Antworten:

5

Erstens ist dies genau die Frage, an die ich gedacht habe, als ich Modellierungsbeschränkungen für Teilmengenaggregate gestellt habe. Das ist sicherlich der Ort, um zu beginnen. Diese Frage ist jedoch allgemeiner und meine Antwort hier enthält daher etwas mehr Informationen zu praktischen Ansätzen.

Sie möchten dies wahrscheinlich nicht deklarativ in PostgreSQL tun. Die einzig möglichen deklarativen Lösungen brechen entweder 1NF oder sind extrem kompliziert. Dies bedeutet, dass dies unbedingt erforderlich ist.

In LedgerSMB erwarten wir, dass diese Durchsetzung in zwei Schritten erfolgt (beide sind streng).

  1. Alle Journaleinträge werden über gespeicherte Prozeduren eingegeben. Diese gespeicherten Prozeduren akzeptieren eine Liste von Werbebuchungen als Array und prüfen, ob die Summe gleich 0 ist. Unser Modell in der Datenbank ist, dass wir eine einzelne Betragsspalte haben, wobei negative Zahlen Belastungen und positive Zahlen Gutschriften sind (wenn ich es war) von vorne anfangen, ich hätte positive Zahlen als Lastschriften und negative Zahlen als Gutschriften, weil dies nur ein bisschen natürlicher ist, aber die Gründe hier sind unklar). Belastungen und Gutschriften werden beim Speichern zusammengeführt und beim Abrufen durch die Präsentationsschicht getrennt. Dies erleichtert das Ausführen von Summen erheblich.

  2. Wir werden einen verzögerten Einschränkungsauslöser verwenden, der das Festschreiben basierend auf den Systemfeldern in der Tabelle überprüft. Dies bedeutet, dass die in einer bestimmten Transaktion eingegebenen Zeilen ausgeglichen sein müssen, aber wir können dies über die Zeilen selbst hinaus tun.

Chris Travers
quelle
Übrigens, wenn Sie viel Doppelbuchhaltung betreiben, erwarten wir, dass wir unser Finanzschema (auf postgreSQL) innerhalb des nächsten Jahres oder so überarbeiten. Ich weiß nicht, ob Sie an einer Zusammenarbeit mit einem Open-Source-Projekt interessiert wären, aber ich dachte, ich würde eine Einladung verlängern.
Chris Travers
Ich bin zu spät zur Party, aber können Sie bitte erklären - "wird das Commit anhand der Systemfelder in der Tabelle überprüfen"? Verwenden Sie das Systemfeld xmin, um herauszufinden, welche Zeilen eingefügt wurden? Ich bin mit genau dieser Situation konfrontiert und dies ist der einzige Thread, der einer Lösung nahe kommt. Ich bin jedoch unwissend.
Code Poet
Ja. Wir können uns die durch die Transaktion erstellten Zeilen ansehen und darauf bestehen, dass die Summe der darin enthaltenen Beträge 0 ist. Das bedeutet im Grunde, Systemspalten wie xmin zu überprüfen.
Chris Travers
4

Ein anderer Ansatz besteht darin, die Position einzunehmen, dass die Übertragung des Finanzbetrags einen einzelnen Datensatz umfasst.

So könnten Sie die Struktur haben:

create table ... (
  id                integer,
  debit_account_id  not null REFERENCES accounts (account_id),
  credit_account_id not null REFERENCES accounts (account_id),
  amount            numeric not null);

Eine Prüfbeschränkung kann sicherstellen, dass die Debit- und Kreditkonten unterschiedlich sind und nur ein Betrag gespeichert werden muss. Somit ist die Integrität garantiert, was das Datenmodell natürlich bieten sollte.

Ich habe mit Systemen gearbeitet, die diesen Ansatz erfolgreich übernommen haben. Es ist etwas weniger effizient, Datensätze für ein bestimmtes Konto abzufragen, aber die Tabelle war kompakter und Abfragen für ein Konto, da nur die Lastschrift oder nur das Guthaben etwas effizienter waren.

David Aldridge
quelle