web-dev-qa-db-fra.com

Transactions, références et comment appliquer la comptabilité à double entrée? (Pg)

La comptabilité à double entrée est

un ensemble de règles pour enregistrer des informations financières dans un système de comptabilité financière dans laquelle chaque transaction ou événement change au moins deux comptes de niveau nominal différents.

Un compte peut être "débité" ou "crédité", et la somme de tous les crédits doit être égale à la somme de tous les débits.

Comment implémenteriez-vous cela dans une base de données Postgres? Spécification de la DDL suivante:

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'))
);

Remarque: la table transaction_détails ne spécifie pas un compte de débit/crédit explicite, car le système devrait pouvoir débiter/créditer plusieurs comptes dans une seule transaction.

Ce DDL crée l'exigence suivante: après une transaction de base de données commet sur la table transactions_Détails, il doit débiter et créditer le même montant pour chaque transaction_id, E.g:

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;

Est-il possible de mettre en œuvre cela dans une base de données PostgreSQL? Sans spécifier des tables supplémentaires pour stocker des états de déclenchement.

8

Tout d'abord, c'est exactement la question que j'avais à l'esprit lorsque j'ai demandé modélisation de contraintes sur les agrégats de sous-ensembles? Ce qui est certainement le lieu de démarrage. Cette question est plus générale que cela et que ma réponse aura donc un peu plus d'informations sur les approches pratiques.

Vous ne voulez probablement pas faire cela déclara dans PostgreSQL. Les seules solutions déclaratives possibles éventuellement rompent 1nf ou sont extrêmement compliquées et cela signifie donc le faire impérativement.

Dans LEDGERSMB Nous prévoyons faire cette application en deux étapes (toutes deux strictes).

  1. Toutes les entrées de journal seront effectuées via des procédures stockées. Ces procédures stockées accepteront une liste d'éléments de ligne comme une matrice et vérifieront que la somme est égale à 0. Notre modèle dans la DB est que nous avons une colonne de quantité unique avec des nombres négatifs étant des débits et des nombres positifs étant crédits (si j'étais À partir de, j'aurais des chiffres positifs comme des débits et des nombres négatifs en tant que crédits car c'est un peu plus naturel, mais les raisons ici sont obscures). Les débits et les crédits sont fusionnés sur le stockage et séparés à la récupération par la couche de présentation. Cela facilite beaucoup les totaux de fonctionnement.

  2. Nous utiliserons un déclencheur de contrainte différé qui vérifiera de commettre basé sur les champs système de la table. Cela signifie que les lignes entrées dans une transaction donnée doivent équilibrer, mais nous pouvons le faire au-delà des lignes elles-mêmes.

5
Chris Travers

Une autre approche consiste à adopter la position que c'est le transfert du montant financier qui comprend un seul enregistrement.

Ainsi, vous pourriez avoir la structure:

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);

Une contrainte de contrôle peut garantir que les comptes de débit et de crédit sont différents, et il n'ya qu'un montant à stocker. Ainsi, il existe une intégrité garantie, ce que le modèle de données devrait fournir naturellement.

J'ai travaillé avec des systèmes qui ont adopté cette approche avec succès. Il existe un peu moins d'efficacité pour interrogé pour tous les enregistrements contre un compte particulier, mais la table était plus compacte et des requêtes pour un compte que le débit uniquement ou que le crédit que le crédit n'était qu'un peu plus efficace.

3
David Aldridge