Calculating balance sheet with a single query





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ margin-bottom:0;
}







4















I have this schema for double entry bookkeeping system:



CREATE TABLE account (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
parent_id INTEGER REFERENCES account(id),

UNIQUE (name, parent_id)
);

CREATE TABLE journal (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
date DATE NOT NULL DEFAULT CURRENT_DATE
);

CREATE TABLE posting (
id SERIAL PRIMARY KEY,
account_id INTEGER NOT NULL REFERENCES account(id),
journal_id INTEGER NOT NULL REFERENCES journal(id),
amount NUMERIC (12, 2) NOT NULL,

-- debit and credit are described by positive and negative value, therefore
-- zero value is not allowed
CHECK (amount <> 0)
);


So, basically each transaction will be recorded in journal table and it's amount that is affecting account table will be recorded in posting table.



Since the structure of account is a hierarchical tree, the amount of each node must be calculated with all of it's descendant.



This is the best I can come up with:



WITH RECURSIVE
-- recursively build tree table, assigning path for each row
tree(id, parent_id, name, path) AS (
SELECT account.id AS id,
account.parent_id AS parent_id,
account.name AS name,
ARRAY[account.id] AS path -- path from root
FROM account
WHERE account.parent_id IS NULL

UNION ALL

SELECT account.id AS id,
account.parent_id AS parent_id,
account.name AS name,
tree.path || ARRAY[account.id] AS path
FROM account, tree
WHERE account.parent_id = tree.id
),
-- make a flat table. each account will appear as whenever it's child is
-- appeared
flatten AS (
SELECT unnest(tree.path) AS id,
tree.id AS account_id
FROM tree
ORDER BY unnest(tree.path), tree.id
)

SELECT tree.id AS id,
tree.name AS name,
coalesce(abs(sum(posting.amount)), 0) AS amount
FROM tree
LEFT OUTER JOIN flatten ON tree.id = flatten.id
LEFT OUTER JOIN posting ON posting.account_id = flatten.account_id
GROUP BY tree.id, tree.name
ORDER BY tree.id


My question is, Can this be improved? Will it perform well?



PS: Some data for testing code



INSERT INTO account VALUES (100, 'ASSET', NULL);
INSERT INTO account VALUES (101, 'Cash', 100);
INSERT INTO account VALUES (102, 'Bank', 100);
INSERT INTO account VALUES (200, 'LIABILITY', NULL);
INSERT INTO account VALUES (201, 'Loan', 200);
INSERT INTO account VALUES (300, 'EQUITY', NULL);
INSERT INTO account VALUES (301, 'Opening Balance', 300);

INSERT INTO journal VALUES (1, 'opening balance', '2018-12-05');
INSERT INTO posting VALUES (1, 101, 1, 1000000.00);
INSERT INTO posting VALUES (2, 301, 1, -1000000.00);

INSERT INTO journal VALUES (3, 'save to bank', '2018-12-05');
INSERT INTO posting VALUES (5, 102, 3, 1250000.00);
INSERT INTO posting VALUES (6, 101, 3, -1250000.00);

INSERT INTO journal VALUES (2, 'loan money', '2018-12-05');
INSERT INTO posting VALUES (3, 101, 2, 500000.00);
INSERT INTO posting VALUES (4, 201, 2, -500000.00);


The output (for running the query above) should be something like this:



 id  |      name       |   amount   
-----+-----------------+------------
100 | ASSET | 1500000.00
101 | Cash | 250000.00
102 | Bank | 1250000.00
200 | LIABILITY | 500000.00
201 | Loan | 500000.00
300 | EQUITY | 1000000.00
301 | Opening Balance | 1000000.00
(7 rows)









share|improve this question
















bumped to the homepage by Community 16 mins ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.











  • 1





    This is really well prepared, I wish all newcomers would do that. However, some sample data (e.g. as insert into) and the expected output would be helpful as well.

    – a_horse_with_no_name
    Dec 5 '18 at 11:00











  • Update with data for testing.

    – Mas Bagol
    Dec 5 '18 at 11:58


















4















I have this schema for double entry bookkeeping system:



CREATE TABLE account (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
parent_id INTEGER REFERENCES account(id),

UNIQUE (name, parent_id)
);

CREATE TABLE journal (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
date DATE NOT NULL DEFAULT CURRENT_DATE
);

CREATE TABLE posting (
id SERIAL PRIMARY KEY,
account_id INTEGER NOT NULL REFERENCES account(id),
journal_id INTEGER NOT NULL REFERENCES journal(id),
amount NUMERIC (12, 2) NOT NULL,

-- debit and credit are described by positive and negative value, therefore
-- zero value is not allowed
CHECK (amount <> 0)
);


So, basically each transaction will be recorded in journal table and it's amount that is affecting account table will be recorded in posting table.



Since the structure of account is a hierarchical tree, the amount of each node must be calculated with all of it's descendant.



This is the best I can come up with:



WITH RECURSIVE
-- recursively build tree table, assigning path for each row
tree(id, parent_id, name, path) AS (
SELECT account.id AS id,
account.parent_id AS parent_id,
account.name AS name,
ARRAY[account.id] AS path -- path from root
FROM account
WHERE account.parent_id IS NULL

UNION ALL

SELECT account.id AS id,
account.parent_id AS parent_id,
account.name AS name,
tree.path || ARRAY[account.id] AS path
FROM account, tree
WHERE account.parent_id = tree.id
),
-- make a flat table. each account will appear as whenever it's child is
-- appeared
flatten AS (
SELECT unnest(tree.path) AS id,
tree.id AS account_id
FROM tree
ORDER BY unnest(tree.path), tree.id
)

SELECT tree.id AS id,
tree.name AS name,
coalesce(abs(sum(posting.amount)), 0) AS amount
FROM tree
LEFT OUTER JOIN flatten ON tree.id = flatten.id
LEFT OUTER JOIN posting ON posting.account_id = flatten.account_id
GROUP BY tree.id, tree.name
ORDER BY tree.id


My question is, Can this be improved? Will it perform well?



PS: Some data for testing code



INSERT INTO account VALUES (100, 'ASSET', NULL);
INSERT INTO account VALUES (101, 'Cash', 100);
INSERT INTO account VALUES (102, 'Bank', 100);
INSERT INTO account VALUES (200, 'LIABILITY', NULL);
INSERT INTO account VALUES (201, 'Loan', 200);
INSERT INTO account VALUES (300, 'EQUITY', NULL);
INSERT INTO account VALUES (301, 'Opening Balance', 300);

INSERT INTO journal VALUES (1, 'opening balance', '2018-12-05');
INSERT INTO posting VALUES (1, 101, 1, 1000000.00);
INSERT INTO posting VALUES (2, 301, 1, -1000000.00);

INSERT INTO journal VALUES (3, 'save to bank', '2018-12-05');
INSERT INTO posting VALUES (5, 102, 3, 1250000.00);
INSERT INTO posting VALUES (6, 101, 3, -1250000.00);

INSERT INTO journal VALUES (2, 'loan money', '2018-12-05');
INSERT INTO posting VALUES (3, 101, 2, 500000.00);
INSERT INTO posting VALUES (4, 201, 2, -500000.00);


The output (for running the query above) should be something like this:



 id  |      name       |   amount   
-----+-----------------+------------
100 | ASSET | 1500000.00
101 | Cash | 250000.00
102 | Bank | 1250000.00
200 | LIABILITY | 500000.00
201 | Loan | 500000.00
300 | EQUITY | 1000000.00
301 | Opening Balance | 1000000.00
(7 rows)









share|improve this question
















bumped to the homepage by Community 16 mins ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.











  • 1





    This is really well prepared, I wish all newcomers would do that. However, some sample data (e.g. as insert into) and the expected output would be helpful as well.

    – a_horse_with_no_name
    Dec 5 '18 at 11:00











  • Update with data for testing.

    – Mas Bagol
    Dec 5 '18 at 11:58














4












4








4








I have this schema for double entry bookkeeping system:



CREATE TABLE account (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
parent_id INTEGER REFERENCES account(id),

UNIQUE (name, parent_id)
);

CREATE TABLE journal (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
date DATE NOT NULL DEFAULT CURRENT_DATE
);

CREATE TABLE posting (
id SERIAL PRIMARY KEY,
account_id INTEGER NOT NULL REFERENCES account(id),
journal_id INTEGER NOT NULL REFERENCES journal(id),
amount NUMERIC (12, 2) NOT NULL,

-- debit and credit are described by positive and negative value, therefore
-- zero value is not allowed
CHECK (amount <> 0)
);


So, basically each transaction will be recorded in journal table and it's amount that is affecting account table will be recorded in posting table.



Since the structure of account is a hierarchical tree, the amount of each node must be calculated with all of it's descendant.



This is the best I can come up with:



WITH RECURSIVE
-- recursively build tree table, assigning path for each row
tree(id, parent_id, name, path) AS (
SELECT account.id AS id,
account.parent_id AS parent_id,
account.name AS name,
ARRAY[account.id] AS path -- path from root
FROM account
WHERE account.parent_id IS NULL

UNION ALL

SELECT account.id AS id,
account.parent_id AS parent_id,
account.name AS name,
tree.path || ARRAY[account.id] AS path
FROM account, tree
WHERE account.parent_id = tree.id
),
-- make a flat table. each account will appear as whenever it's child is
-- appeared
flatten AS (
SELECT unnest(tree.path) AS id,
tree.id AS account_id
FROM tree
ORDER BY unnest(tree.path), tree.id
)

SELECT tree.id AS id,
tree.name AS name,
coalesce(abs(sum(posting.amount)), 0) AS amount
FROM tree
LEFT OUTER JOIN flatten ON tree.id = flatten.id
LEFT OUTER JOIN posting ON posting.account_id = flatten.account_id
GROUP BY tree.id, tree.name
ORDER BY tree.id


My question is, Can this be improved? Will it perform well?



PS: Some data for testing code



INSERT INTO account VALUES (100, 'ASSET', NULL);
INSERT INTO account VALUES (101, 'Cash', 100);
INSERT INTO account VALUES (102, 'Bank', 100);
INSERT INTO account VALUES (200, 'LIABILITY', NULL);
INSERT INTO account VALUES (201, 'Loan', 200);
INSERT INTO account VALUES (300, 'EQUITY', NULL);
INSERT INTO account VALUES (301, 'Opening Balance', 300);

INSERT INTO journal VALUES (1, 'opening balance', '2018-12-05');
INSERT INTO posting VALUES (1, 101, 1, 1000000.00);
INSERT INTO posting VALUES (2, 301, 1, -1000000.00);

INSERT INTO journal VALUES (3, 'save to bank', '2018-12-05');
INSERT INTO posting VALUES (5, 102, 3, 1250000.00);
INSERT INTO posting VALUES (6, 101, 3, -1250000.00);

INSERT INTO journal VALUES (2, 'loan money', '2018-12-05');
INSERT INTO posting VALUES (3, 101, 2, 500000.00);
INSERT INTO posting VALUES (4, 201, 2, -500000.00);


The output (for running the query above) should be something like this:



 id  |      name       |   amount   
-----+-----------------+------------
100 | ASSET | 1500000.00
101 | Cash | 250000.00
102 | Bank | 1250000.00
200 | LIABILITY | 500000.00
201 | Loan | 500000.00
300 | EQUITY | 1000000.00
301 | Opening Balance | 1000000.00
(7 rows)









share|improve this question
















I have this schema for double entry bookkeeping system:



CREATE TABLE account (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
parent_id INTEGER REFERENCES account(id),

UNIQUE (name, parent_id)
);

CREATE TABLE journal (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
date DATE NOT NULL DEFAULT CURRENT_DATE
);

CREATE TABLE posting (
id SERIAL PRIMARY KEY,
account_id INTEGER NOT NULL REFERENCES account(id),
journal_id INTEGER NOT NULL REFERENCES journal(id),
amount NUMERIC (12, 2) NOT NULL,

-- debit and credit are described by positive and negative value, therefore
-- zero value is not allowed
CHECK (amount <> 0)
);


So, basically each transaction will be recorded in journal table and it's amount that is affecting account table will be recorded in posting table.



Since the structure of account is a hierarchical tree, the amount of each node must be calculated with all of it's descendant.



This is the best I can come up with:



WITH RECURSIVE
-- recursively build tree table, assigning path for each row
tree(id, parent_id, name, path) AS (
SELECT account.id AS id,
account.parent_id AS parent_id,
account.name AS name,
ARRAY[account.id] AS path -- path from root
FROM account
WHERE account.parent_id IS NULL

UNION ALL

SELECT account.id AS id,
account.parent_id AS parent_id,
account.name AS name,
tree.path || ARRAY[account.id] AS path
FROM account, tree
WHERE account.parent_id = tree.id
),
-- make a flat table. each account will appear as whenever it's child is
-- appeared
flatten AS (
SELECT unnest(tree.path) AS id,
tree.id AS account_id
FROM tree
ORDER BY unnest(tree.path), tree.id
)

SELECT tree.id AS id,
tree.name AS name,
coalesce(abs(sum(posting.amount)), 0) AS amount
FROM tree
LEFT OUTER JOIN flatten ON tree.id = flatten.id
LEFT OUTER JOIN posting ON posting.account_id = flatten.account_id
GROUP BY tree.id, tree.name
ORDER BY tree.id


My question is, Can this be improved? Will it perform well?



PS: Some data for testing code



INSERT INTO account VALUES (100, 'ASSET', NULL);
INSERT INTO account VALUES (101, 'Cash', 100);
INSERT INTO account VALUES (102, 'Bank', 100);
INSERT INTO account VALUES (200, 'LIABILITY', NULL);
INSERT INTO account VALUES (201, 'Loan', 200);
INSERT INTO account VALUES (300, 'EQUITY', NULL);
INSERT INTO account VALUES (301, 'Opening Balance', 300);

INSERT INTO journal VALUES (1, 'opening balance', '2018-12-05');
INSERT INTO posting VALUES (1, 101, 1, 1000000.00);
INSERT INTO posting VALUES (2, 301, 1, -1000000.00);

INSERT INTO journal VALUES (3, 'save to bank', '2018-12-05');
INSERT INTO posting VALUES (5, 102, 3, 1250000.00);
INSERT INTO posting VALUES (6, 101, 3, -1250000.00);

INSERT INTO journal VALUES (2, 'loan money', '2018-12-05');
INSERT INTO posting VALUES (3, 101, 2, 500000.00);
INSERT INTO posting VALUES (4, 201, 2, -500000.00);


The output (for running the query above) should be something like this:



 id  |      name       |   amount   
-----+-----------------+------------
100 | ASSET | 1500000.00
101 | Cash | 250000.00
102 | Bank | 1250000.00
200 | LIABILITY | 500000.00
201 | Loan | 500000.00
300 | EQUITY | 1000000.00
301 | Opening Balance | 1000000.00
(7 rows)






postgresql






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Dec 5 '18 at 11:58







Mas Bagol

















asked Dec 5 '18 at 10:33









Mas BagolMas Bagol

1213




1213





bumped to the homepage by Community 16 mins ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.







bumped to the homepage by Community 16 mins ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.










  • 1





    This is really well prepared, I wish all newcomers would do that. However, some sample data (e.g. as insert into) and the expected output would be helpful as well.

    – a_horse_with_no_name
    Dec 5 '18 at 11:00











  • Update with data for testing.

    – Mas Bagol
    Dec 5 '18 at 11:58














  • 1





    This is really well prepared, I wish all newcomers would do that. However, some sample data (e.g. as insert into) and the expected output would be helpful as well.

    – a_horse_with_no_name
    Dec 5 '18 at 11:00











  • Update with data for testing.

    – Mas Bagol
    Dec 5 '18 at 11:58








1




1





This is really well prepared, I wish all newcomers would do that. However, some sample data (e.g. as insert into) and the expected output would be helpful as well.

– a_horse_with_no_name
Dec 5 '18 at 11:00





This is really well prepared, I wish all newcomers would do that. However, some sample data (e.g. as insert into) and the expected output would be helpful as well.

– a_horse_with_no_name
Dec 5 '18 at 11:00













Update with data for testing.

– Mas Bagol
Dec 5 '18 at 11:58





Update with data for testing.

– Mas Bagol
Dec 5 '18 at 11:58










1 Answer
1






active

oldest

votes


















0














If your account table doesn't change often, you could take advantage of Postgres Materialized Views.



CREATE MATERIALIZED VIEW account_tree 
AS
WITH RECURSIVE
-- recursively build tree table, assigning path for each row
tree
AS (
SELECT account.id AS id,
account.parent_id AS parent_id,
account.name AS name,
ARRAY[account.id] AS path
FROM account
WHERE account.parent_id IS NULL

UNION ALL

SELECT account.id AS id,
account.parent_id AS parent_id,
account.name AS name,
tree.path || ARRAY[account.id] AS path
FROM account, tree
WHERE account.parent_id = tree.id
)
SELECT
t.id,
t.account_id,
CASE WHEN t.parent_id = t.account_id
THEN a.name ELSE t.name END AS name
FROM
(SELECT id, parent_id, name, unnest(path) as account_id
FROM tree) t
JOIN
account a
ON a.id = t.parent_id;


Materialized views allows the use of index:



CREATE INDEX account_tree_ix
ON account_tree (id, account_id);


This view returns next result:




id | account_id | name
--: | ---------: | :--------------
101 | 100 | ASSET
101 | 101 | Cash
102 | 100 | ASSET
102 | 102 | Bank
201 | 200 | LIABILITY
201 | 201 | Loan
301 | 300 | EQUITY
301 | 301 | Opening Balance


Now you can use this view to obtain the balance sheet avoiding the recursive CTE:



SELECT    at.account_id, 
at.name,
coalesce(abs(sum(p.amount)), 0) AS amount
FROM account_tree at
LEFT JOIN posting p
ON p.account_id = at.id
GROUP BY at.account_id,
at.name
ORDER BY at.account_id;




account_id | name | amount
---------: | :-------------- | ---------:
100 | ASSET | 1500000.00
101 | Cash | 250000.00
102 | Bank | 1250000.00
200 | LIABILITY | 500000.00
201 | Loan | 500000.00
300 | EQUITY | 1000000.00
301 | Opening Balance | 1000000.00



db<>fiddle here






share|improve this answer


























    Your Answer








    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "182"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: false,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f224184%2fcalculating-balance-sheet-with-a-single-query%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    0














    If your account table doesn't change often, you could take advantage of Postgres Materialized Views.



    CREATE MATERIALIZED VIEW account_tree 
    AS
    WITH RECURSIVE
    -- recursively build tree table, assigning path for each row
    tree
    AS (
    SELECT account.id AS id,
    account.parent_id AS parent_id,
    account.name AS name,
    ARRAY[account.id] AS path
    FROM account
    WHERE account.parent_id IS NULL

    UNION ALL

    SELECT account.id AS id,
    account.parent_id AS parent_id,
    account.name AS name,
    tree.path || ARRAY[account.id] AS path
    FROM account, tree
    WHERE account.parent_id = tree.id
    )
    SELECT
    t.id,
    t.account_id,
    CASE WHEN t.parent_id = t.account_id
    THEN a.name ELSE t.name END AS name
    FROM
    (SELECT id, parent_id, name, unnest(path) as account_id
    FROM tree) t
    JOIN
    account a
    ON a.id = t.parent_id;


    Materialized views allows the use of index:



    CREATE INDEX account_tree_ix
    ON account_tree (id, account_id);


    This view returns next result:




    id | account_id | name
    --: | ---------: | :--------------
    101 | 100 | ASSET
    101 | 101 | Cash
    102 | 100 | ASSET
    102 | 102 | Bank
    201 | 200 | LIABILITY
    201 | 201 | Loan
    301 | 300 | EQUITY
    301 | 301 | Opening Balance


    Now you can use this view to obtain the balance sheet avoiding the recursive CTE:



    SELECT    at.account_id, 
    at.name,
    coalesce(abs(sum(p.amount)), 0) AS amount
    FROM account_tree at
    LEFT JOIN posting p
    ON p.account_id = at.id
    GROUP BY at.account_id,
    at.name
    ORDER BY at.account_id;




    account_id | name | amount
    ---------: | :-------------- | ---------:
    100 | ASSET | 1500000.00
    101 | Cash | 250000.00
    102 | Bank | 1250000.00
    200 | LIABILITY | 500000.00
    201 | Loan | 500000.00
    300 | EQUITY | 1000000.00
    301 | Opening Balance | 1000000.00



    db<>fiddle here






    share|improve this answer






























      0














      If your account table doesn't change often, you could take advantage of Postgres Materialized Views.



      CREATE MATERIALIZED VIEW account_tree 
      AS
      WITH RECURSIVE
      -- recursively build tree table, assigning path for each row
      tree
      AS (
      SELECT account.id AS id,
      account.parent_id AS parent_id,
      account.name AS name,
      ARRAY[account.id] AS path
      FROM account
      WHERE account.parent_id IS NULL

      UNION ALL

      SELECT account.id AS id,
      account.parent_id AS parent_id,
      account.name AS name,
      tree.path || ARRAY[account.id] AS path
      FROM account, tree
      WHERE account.parent_id = tree.id
      )
      SELECT
      t.id,
      t.account_id,
      CASE WHEN t.parent_id = t.account_id
      THEN a.name ELSE t.name END AS name
      FROM
      (SELECT id, parent_id, name, unnest(path) as account_id
      FROM tree) t
      JOIN
      account a
      ON a.id = t.parent_id;


      Materialized views allows the use of index:



      CREATE INDEX account_tree_ix
      ON account_tree (id, account_id);


      This view returns next result:




      id | account_id | name
      --: | ---------: | :--------------
      101 | 100 | ASSET
      101 | 101 | Cash
      102 | 100 | ASSET
      102 | 102 | Bank
      201 | 200 | LIABILITY
      201 | 201 | Loan
      301 | 300 | EQUITY
      301 | 301 | Opening Balance


      Now you can use this view to obtain the balance sheet avoiding the recursive CTE:



      SELECT    at.account_id, 
      at.name,
      coalesce(abs(sum(p.amount)), 0) AS amount
      FROM account_tree at
      LEFT JOIN posting p
      ON p.account_id = at.id
      GROUP BY at.account_id,
      at.name
      ORDER BY at.account_id;




      account_id | name | amount
      ---------: | :-------------- | ---------:
      100 | ASSET | 1500000.00
      101 | Cash | 250000.00
      102 | Bank | 1250000.00
      200 | LIABILITY | 500000.00
      201 | Loan | 500000.00
      300 | EQUITY | 1000000.00
      301 | Opening Balance | 1000000.00



      db<>fiddle here






      share|improve this answer




























        0












        0








        0







        If your account table doesn't change often, you could take advantage of Postgres Materialized Views.



        CREATE MATERIALIZED VIEW account_tree 
        AS
        WITH RECURSIVE
        -- recursively build tree table, assigning path for each row
        tree
        AS (
        SELECT account.id AS id,
        account.parent_id AS parent_id,
        account.name AS name,
        ARRAY[account.id] AS path
        FROM account
        WHERE account.parent_id IS NULL

        UNION ALL

        SELECT account.id AS id,
        account.parent_id AS parent_id,
        account.name AS name,
        tree.path || ARRAY[account.id] AS path
        FROM account, tree
        WHERE account.parent_id = tree.id
        )
        SELECT
        t.id,
        t.account_id,
        CASE WHEN t.parent_id = t.account_id
        THEN a.name ELSE t.name END AS name
        FROM
        (SELECT id, parent_id, name, unnest(path) as account_id
        FROM tree) t
        JOIN
        account a
        ON a.id = t.parent_id;


        Materialized views allows the use of index:



        CREATE INDEX account_tree_ix
        ON account_tree (id, account_id);


        This view returns next result:




        id | account_id | name
        --: | ---------: | :--------------
        101 | 100 | ASSET
        101 | 101 | Cash
        102 | 100 | ASSET
        102 | 102 | Bank
        201 | 200 | LIABILITY
        201 | 201 | Loan
        301 | 300 | EQUITY
        301 | 301 | Opening Balance


        Now you can use this view to obtain the balance sheet avoiding the recursive CTE:



        SELECT    at.account_id, 
        at.name,
        coalesce(abs(sum(p.amount)), 0) AS amount
        FROM account_tree at
        LEFT JOIN posting p
        ON p.account_id = at.id
        GROUP BY at.account_id,
        at.name
        ORDER BY at.account_id;




        account_id | name | amount
        ---------: | :-------------- | ---------:
        100 | ASSET | 1500000.00
        101 | Cash | 250000.00
        102 | Bank | 1250000.00
        200 | LIABILITY | 500000.00
        201 | Loan | 500000.00
        300 | EQUITY | 1000000.00
        301 | Opening Balance | 1000000.00



        db<>fiddle here






        share|improve this answer















        If your account table doesn't change often, you could take advantage of Postgres Materialized Views.



        CREATE MATERIALIZED VIEW account_tree 
        AS
        WITH RECURSIVE
        -- recursively build tree table, assigning path for each row
        tree
        AS (
        SELECT account.id AS id,
        account.parent_id AS parent_id,
        account.name AS name,
        ARRAY[account.id] AS path
        FROM account
        WHERE account.parent_id IS NULL

        UNION ALL

        SELECT account.id AS id,
        account.parent_id AS parent_id,
        account.name AS name,
        tree.path || ARRAY[account.id] AS path
        FROM account, tree
        WHERE account.parent_id = tree.id
        )
        SELECT
        t.id,
        t.account_id,
        CASE WHEN t.parent_id = t.account_id
        THEN a.name ELSE t.name END AS name
        FROM
        (SELECT id, parent_id, name, unnest(path) as account_id
        FROM tree) t
        JOIN
        account a
        ON a.id = t.parent_id;


        Materialized views allows the use of index:



        CREATE INDEX account_tree_ix
        ON account_tree (id, account_id);


        This view returns next result:




        id | account_id | name
        --: | ---------: | :--------------
        101 | 100 | ASSET
        101 | 101 | Cash
        102 | 100 | ASSET
        102 | 102 | Bank
        201 | 200 | LIABILITY
        201 | 201 | Loan
        301 | 300 | EQUITY
        301 | 301 | Opening Balance


        Now you can use this view to obtain the balance sheet avoiding the recursive CTE:



        SELECT    at.account_id, 
        at.name,
        coalesce(abs(sum(p.amount)), 0) AS amount
        FROM account_tree at
        LEFT JOIN posting p
        ON p.account_id = at.id
        GROUP BY at.account_id,
        at.name
        ORDER BY at.account_id;




        account_id | name | amount
        ---------: | :-------------- | ---------:
        100 | ASSET | 1500000.00
        101 | Cash | 250000.00
        102 | Bank | 1250000.00
        200 | LIABILITY | 500000.00
        201 | Loan | 500000.00
        300 | EQUITY | 1000000.00
        301 | Opening Balance | 1000000.00



        db<>fiddle here







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Dec 17 '18 at 7:51

























        answered Dec 16 '18 at 0:42









        McNetsMcNets

        16.4k42261




        16.4k42261






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Database Administrators Stack Exchange!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f224184%2fcalculating-balance-sheet-with-a-single-query%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            SQL Server 17 - Attemping to backup to remote NAS but Access is denied

            Always On Availability groups resolving state after failover - Remote harden of transaction...

            Restoring from pg_dump with foreign key constraints