Transazioni, riferimenti e come applicare la contabilità in partita doppia? (PG)


8

La contabilità a doppia entrata è

un insieme di regole per la registrazione di informazioni finanziarie in un sistema di contabilità finanziaria in cui ogni transazione o evento modifica almeno due diversi conti contabili.

Un conto può essere "addebitato" o "accreditato" e la somma di tutti i crediti deve essere uguale alla somma di tutti i debiti.

Come lo implementereste in un database Postgres? Specifica del seguente 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'))
);

Nota: la tabella transazione_dettagli non specifica un conto di addebito / credito esplicito, poiché il sistema dovrebbe essere in grado di addebitare / accreditare più di un conto in una singola transazione.

Questo DDL crea il seguente requisito: Dopo che una transazione del database si è impegnata nella tabella transazioni_dettagli, deve addebitare e accreditare lo stesso importo per ciascuno transaction_id, ad esempio :

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;

È possibile implementarlo in un database PostgreSQL? Senza specificare tabelle aggiuntive per memorizzare gli stati di trigger.

Risposte:


5

Innanzitutto, questa è esattamente la domanda che avevo in mente quando ho chiesto i vincoli di modellazione sugli aggregati di sottogruppi? che è sicuramente il punto di partenza. Questa domanda è però più generale di così e quindi la mia risposta qui avrà un po 'più di informazioni sugli approcci pratici.

Probabilmente non vorrai farlo dichiaratamente in PostgreSQL. Le uniche soluzioni dichiarative possibili o rompono 1NF o sono estremamente complicate e quindi questo significa farlo imperativamente.

In LedgerSMB prevediamo di applicare questa imposizione in due fasi (entrambe rigorose).

  1. Tutte le voci di giornale entreranno tramite le procedure memorizzate. Queste procedure memorizzate accetteranno un elenco di elementi pubblicitari come un array e verificheranno che la somma sia uguale a 0. Il nostro modello nel db è che abbiamo una singola colonna di importo con numeri negativi che sono debiti e numeri positivi che sono crediti (se lo fossi ricominciare da capo, avrei numeri positivi come debiti e numeri negativi come crediti perché questo è solo un po 'più naturale, ma i motivi qui sono oscuri). Debiti e crediti vengono uniti in memoria e separati al momento del recupero dal livello di presentazione. Ciò semplifica notevolmente l'esecuzione dei totali.

  2. Useremo un trigger di vincolo differito che controllerà il commit in base ai campi di sistema nella tabella. Ciò significa che le righe inserite in una determinata transazione devono essere bilanciate, ma possiamo farlo oltre le righe stesse.


A proposito, se stai facendo molta contabilità in partita doppia, ti aspettiamo di riprogettare il nostro schema finanziario (su postgreSQL) entro il prossimo anno o giù di lì. Non so se saresti interessato a collaborare con un progetto open source ma ho pensato che avrei esteso un invito.
Chris Travers,

Sono in ritardo alla festa, ma potresti spiegare - "verificherà il commit in base ai campi di sistema sul tavolo"? Stai usando il campo di sistema xmin per scoprire quali righe sono state inserite? Sono di fronte a questa situazione esatta e questo è l'unico thread che si avvicina a una soluzione. Tuttavia, sono ignorante.
Codice Poeta

Si. Possiamo guardare le righe create dalla transazione e insistere sul fatto che la somma degli importi in esse sono 0. Ciò significa, fondamentalmente, controllare le colonne di sistema come xmin.
Chris Travers,

4

Un altro approccio è quello di adottare la posizione secondo cui è il trasferimento dell'importo finanziario che comprende un singolo record.

Quindi potresti avere la struttura:

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

Un vincolo di controllo può garantire che i conti di debito e credito siano diversi e che vi sia un solo importo da archiviare. Pertanto, vi è integrità garantita, che è ciò che il modello di dati dovrebbe fornire naturalmente.

Ho lavorato con sistemi che hanno adottato questo approccio con successo. Vi è un po 'meno efficienza nella ricerca di qualsiasi record su un determinato account, ma la tabella era più compatta e le query per un account in quanto solo il debito o solo il credito erano un po' più efficienti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.