How to do a recursive sum of children in mysql?
-
07-03-2021 - |
Question
I've searched and tried for long hours, and can't seem to get what I need. I have a table of accounts as a tree -parents and children-, only leaf accounts can have transactions, any parent will NOT have debits and credits itself.
My fiddle of data is here
What I need is to show sum of "Debits of children" and sum of "Credits of children" in their parent's row, like this picture:
How can I do it in MySql?
Thank you
Solution
With your example, you can run
CREATE PROCEDURE procedure_name()
BEGIN
DECLARE finished INTEGER DEFAULT 0;
DECLARE _accoujntno INTEGER DEFAULT 0;
DECLARE _uacn varchar(191) DEFAULT "";
DEClARE curaccount
CURSOR FOR
Select account_no ,user_account_number FROM TBL1 WHERE length(user_account_number)-length(replace(user_account_number,'-',''))+1 = 2 order by user_account_number;
-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET finished = 1;
CREATE TEMPORARY TABLE credits(
user_account_number VARCHAR(191),
`Debit_Before_Period` DECIMAL(20,2),
`Credit_Before_Period` DECIMAL(20,2),
`Debit_In_Period` DECIMAL(20,2),
`Credit_In_Period` DECIMAL(20,2)
);
OPEN curaccount;
getaccount: LOOP
FETCH curaccount INTO _accoujntno,_uacn;
IF finished = 1 THEN
LEAVE getaccount;
END IF;
INSERT INTO credits SELECT _uacn ,SUM(`Debit Before Period`),SUM(`Credit Before Period`) ,SUM(`Debit In Period`) ,SUM(`Credit In Period`)
FROM TBL1
WHERE LEFT(user_account_number,7) = _uacn AND account_no <> _accoujntno;
END LOOP getaccount;
CLOSE curaccount;
SELECT * FROM credits;
END
The resulting temporary table credits you can use to run further analysis
simply add the procedure and CALL procedure_name()
In a query tab in phmyadmin wor mysql Workbench you need to add DELIMITER to the procedure, but that doesn't work in dbfiddle
It is like in the comment
CREATE PROCEDURE procedure_name()
BEGIN
DECLARE finished INTEGER DEFAULT 0;
DECLARE _accoujntno INTEGER DEFAULT 0;
DECLARE _uacn varchar(191) DEFAULT "";
DEClARE curaccount
CURSOR FOR
Select account_no ,user_account_number FROM TBL1 WHERE length(user_account_number)-length(replace(user_account_number,'-',''))+1 = 1 order by user_account_number;
-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET finished = 1;
CREATE TEMPORARY TABLE credits(
user_account_number VARCHAR(191),
`Debit_Before_Period` DECIMAL(20,2),
`Credit_Before_Period` DECIMAL(20,2),
`Debit_In_Period` DECIMAL(20,2),
`Credit_In_Period` DECIMAL(20,2)
);
OPEN curaccount;
getaccount: LOOP
FETCH curaccount INTO _accoujntno,_uacn;
IF finished = 1 THEN
LEAVE getaccount;
END IF;
INSERT INTO credits SELECT _uacn ,SUM(`Debit Before Period`),SUM(`Credit Before Period`) ,SUM(`Debit In Period`) ,SUM(`Credit In Period`)
FROM TBL1
WHERE LEFT(user_account_number,3) = _uacn;
END LOOP getaccount;
CLOSE curaccount;
SELECT * FROM credits;
END
OTHER TIPS
Based on the accepted answer from @nbk, I made a small change in the sql code to fit my case. I Hope this is useful for others:
-- HELPER SP
DELIMITER //
DROP PROCEDURE IF EXISTS sumChildrenAccounts//
CREATE PROCEDURE sumChildrenAccounts(
_Level SMALLINT
)
BEGIN
DECLARE finished INTEGER DEFAULT 0;
DECLARE _accoujntno INTEGER DEFAULT 0;
DECLARE _uacn varchar(191) DEFAULT "";
DEClARE curaccount CURSOR FOR
SELECT account_no ,user_account_number FROM VIEW2 WHERE length(user_account_number)-length(replace(user_account_number,'-',''))+1 = _Level ORDER BY user_account_number;
-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET finished = 1;
CREATE TEMPORARY TABLE TOTALS_BY_LEVEL(
user_account_number VARCHAR(191),
`Debit Before Period` DECIMAL(20,2),
`Credit Before Period` DECIMAL(20,2),
`Debit In Period` DECIMAL(20,2),
`Credit In Period` DECIMAL(20,2)
);
OPEN curaccount;
getaccount: LOOP
FETCH curaccount INTO _accoujntno,_uacn;
IF finished = 1 THEN
LEAVE getaccount;
END IF;
INSERT INTO TOTALS_BY_LEVEL SELECT _uacn ,SUM(`Debit Before Period`),SUM(`Credit Before Period`) ,SUM(`Debit In Period`) ,SUM(`Credit In Period`)
FROM VIEW2
WHERE LEFT(user_account_number, (_level -1 + (_level * 3))) = _uacn;
END LOOP getaccount;
CLOSE curaccount;
-- update parents' totals in the same source table (where parents had nulls actually)
UPDATE TOTALS_BY_LEVEL b
INNER JOIN VIEW2 a ON b.user_account_number = a.user_account_number
SET
a. `Debit Before Period` = b. `Debit Before Period` ,
a. `Credit Before Period` = b. `Credit Before Period`,
a. `Debit In Period` = b. `Debit In Period` ,
a. `Credit In Period` = b. `Credit In Period` ;
DROP TABLE IF EXISTS TOTALS_BY_LEVEL;
END//
DELIMITER ;
Then I called it in a loop inside the original stored procedure:
-- loop from the 1 until the deepest level of child accounts
SET @ml := 1;
SELECT max(level) FROM acc_accounts LIMIT 1 INTO @ml;
SET @x = 1;
WHILE @x <= @ml DO
call sumChildrenAccounts(@x);
SET @x := @x + 1;
END WHILE;