Транзакции, ссылки и как обеспечить двойную бухгалтерию? (ПГ)

8

Двойная бухгалтерия

набор правил для записи финансовой информации в системе финансового учета, в которой каждая транзакция или событие изменяет, по крайней мере, две разные учетные записи номинальной книги.

Счет может быть «списан» или «зачислен», и сумма всех кредитов должна быть равна сумме всех дебетов.

Как бы вы реализовали это в базе данных Postgres? Указание следующего 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'))
);

Примечание. В таблице транзакции не указывается явный счет дебетования / кредитования, поскольку система должна иметь возможность дебетовать / кредитовать более одного счета в одной транзакции.

Этот DDL создает следующее требование: после того, как транзакция базы данных фиксируется в таблице Transactions_details, она должна дебетовать и кредитовать одну и ту же сумму для каждого transaction_id, например :

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;

Возможно ли реализовать это в базе данных PostgreSQL? Без указания дополнительных таблиц для хранения состояний триггера.

Кочиз Рухулессин
источник

Ответы:

5

Во-первых, это именно тот вопрос, который я имел в виду, когда задавал ограничения по моделированию для агрегатов подмножеств? это, безусловно, место для начала. Этот вопрос является более общим, чем этот, и поэтому мой ответ здесь будет иметь немного больше информации о практических подходах.

Вы, вероятно, не хотите делать это декларативно в PostgreSQL. Единственные возможные декларативные решения либо нарушают 1NF, либо являются чрезвычайно сложными, и это означает, что необходимо делать это настоятельно.

В LedgerSMB мы ожидаем, что это будет сделано в два этапа (оба из которых являются строгими).

  1. Все записи журнала будут поступать через хранимые процедуры. Эти хранимые процедуры примут список позиций в виде массива и проверит, что сумма равна 0. Наша модель в БД состоит в том, что у нас есть один столбец суммы с отрицательными числами, являющимися дебетами, и положительными числами, являющимися кредитами (если бы я был Начиная с начала, у меня были бы положительные числа как дебеты и отрицательные числа как кредиты, потому что это немного более естественно, но причины здесь неясны). Дебеты и кредиты объединяются в хранилище и разделяются при извлечении на уровне представления. Это значительно облегчает беговые итоги.

  2. Мы будем использовать отложенный триггер ограничения, который будет проверять фиксацию на основе системных полей таблицы. Это означает, что строки, введенные в данной транзакции, должны быть сбалансированы, но мы можем сделать это за пределами самих строк.

Крис Траверс
источник
Кстати, если вы ведете бухгалтерию с двойными записями, мы ожидаем реорганизации нашей финансовой схемы (на postgreSQL) в течение следующего года или около того. Я не знаю, было бы вам интересно сотрудничать с проектом с открытым исходным кодом, но я подумал, что я бы направил приглашение.
Крис Треверс
Я опаздываю на вечеринку, но не могли бы вы объяснить - «проверять коммит на основе системных полей таблицы»? Вы используете системное поле xmin, чтобы узнать, какие строки были вставлены? Я столкнулся с этой конкретной ситуацией, и это единственная тема, которая приближается к решению. Однако я в неведении.
Кодовый поэт
Да. Мы можем посмотреть на строки, созданные транзакцией, и настаивать на том, что сумма сумм в них равна 0. Это означает, в основном, проверку системных столбцов, таких как xmin.
Крис Трэверс
4

Другой подход заключается в принятии позиции, согласно которой передача финансовой суммы состоит из одной записи.

Таким образом, вы можете иметь структуру:

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

Ограничение проверки может гарантировать, что дебетовые и кредитные счета различаются, и для хранения имеется только одна сумма. Таким образом, существует гарантированная целостность, которую модель данных должна обеспечивать естественным образом.

Я работал с системами, которые приняли этот подход успешно. Запросы для любых записей по конкретному аккаунту несколько менее эффективны, но таблица была более компактной, и запросы для аккаунта только для дебета или только для кредита были немного более эффективными.

Дэвид Олдридж
источник