需要根据另一列否定一列,作为总计的一部分
Need to negate a column based on another column, as part of an aggregate total
我在会计系统中生成损益报告时遇到问题。
每个普通日记帐分录都有一个金额、一个来源帐户和一个目标帐户。这是一个简化的架构和一些示例数据:
CREATE TABLE `sa_general_journal` (
`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`Date` timestamp NOT NULL DEFAULT current_timestamp(),
`Item` varchar(1024) NOT NULL DEFAULT '',
`Amount` decimal(9,2) NOT NULL DEFAULT 0.00,
`Source` int(10) unsigned NOT NULL,
`Destination` int(10) unsigned NOT NULL,
PRIMARY KEY (`ID`),
KEY `Date` (`Date`),
KEY `Source` (`Source`),
KEY `Destination` (`Destination`),
CONSTRAINT `sa_credit-account` FOREIGN KEY (`Destination`) REFERENCES `sa_accounts` (`ID`),
CONSTRAINT `sa_debit-account` FOREIGN KEY (`Source`) REFERENCES `sa_accounts` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=21561 DEFAULT CHARSET=utf8;
CREATE TABLE `sa_accounts` (
`ID` int(10) unsigned NOT NULL,
`Name` varchar(255) NOT NULL,
`Type` enum('Asset','Liability','Income','Expense'),
`Report` enum('BS','PL'), -- for "Balance Sheet" or "Profit & Loss"
PRIMARY KEY (`ID`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO sa_account (`ID`, `Name`, `Type`, `Report`)
VALUES (1009999, "Test chequing account", "Asset", "BS"),
(4059999, "Test Income account", "Income", "PL"),
(5059999, "Test Expense account", "Expense", "PL");
INSERT INTO sa_general_journal (`ID`, `Date`, `Item`, `Amount`, `Source`, `Destination`)
VALUES (NULL, "2020-05-03", "Test income transaction", 10.10, 4059999, 1009999),
(NULL, "2020-05-03", "Test expense transaction", 1.01, 1009999, 5059999);
这是存入支票账户的 10.10 美元收入和从支票账户中支出的 1.01 美元。
要获得损益表的余额,需要对 Source
列中每个出现的帐户的所有 Amount
条目求和,然后减去所有 Amount
个条目,其中该帐户位于 Destination
列。
预期结果为:
<table>
<th>ID</th><th>Name</th><th>Debits</th><th>Credits</th><th>Net</th></tr>
<tr><td>1009999</td><td>Test chequing account</td><td>-1.01</td><td>10.10</td><td>9.09</td></tr>
<tr><td>4059999</td><td>Test income transaction</td><td>-10.10</td><td><i>NULL</i></td><td>-10.10</td></tr>
<tr><td>5059999</td><td>Test expense transaction</td><td><i>NULL</i></td><td>1.01</td><td>1.01</td></tr>
</table>
我的第一个方法有点天真,查询 sa_general_journal
table 与 sa_accounts
table 连接,由 Source
和 Destination
列,如果 Destination
包含感兴趣的帐户,则使用 IF 函数取反 Amount
。我在使用准备好的语句查询个人帐户时成功地做到了这一点:
SELECT
DATE_FORMAT(exp.Date, '%Y') AS `Year`,
AVG(exp.Amount) AS `Avg`,
SUM(IF(Source = ?, 0 - exp.Amount, NULL)) AS `Debits`,
SUM(IF(Destination = ?, exp.Amount, NULL)) AS `Credits`,
SUM(IF(Source = ?, 0 - exp.Amount, exp.Amount)) AS `Net`
FROM sa_general_journal exp
LEFT JOIN sa_accounts Destination ON exp.Destination = Destination.ID
WHERE Destination = ?
OR Source = ?
GROUP BY `Year`
所有四个占位符都拥有相同的帐户 ID。
但是,当在没有 WHERE 子句的情况下按帐户 ID 进行分组以获取所有帐户余额的列表时,这会失败——用动态帐户 ID 代替占位符中的静态帐户 ID 似乎永远不会达到“0 - exp.Amount" 代码……呃!查询被选择为一个集合,而不是过程中的步骤。我明白了。
使 SUM 语句子查询可行,但速度慢得可怕,显然要对数万条记录中的每条记录执行三个子查询!
所以,我想我可以用几个常见的 Table 表达式分解子查询,但这似乎也不能正常工作,因为它似乎仍然只是简单地返回 SUM(Amount
), 不减去 Amount
s 即 Destination
s 而不是 Source
s.
WITH
source1 AS (SELECT
src.ID,
src.Name,
SUM(Amount) Amount
FROM sa_general_journal gj
LEFT JOIN sa_accounts src ON gj.`Source` = src.ID
WHERE src.Report = "PL" -- AND YEAR(`Date`) = 2019
GROUP BY src.ID),
destination1 AS (SELECT
dst.ID,
dst.Name,
SUM(0-Amount) Amount
FROM sa_general_journal gj
LEFT JOIN sa_accounts dst ON gj.`Destination` = dst.ID
WHERE dst.Report = "PL" -- AND YEAR(`Date`) = 2019
GROUP BY dst.ID)
SELECT ID, Name, sum(Amount)
FROM source1
UNION ALL
SELECT ID, Name, sum(Amount)
FROM destination1
GROUP BY ID
我猜我在做一些愚蠢的假设,或者做一些愚蠢的事情,所以任何建议都将不胜感激!
没有样本数据很难 100% 确定,但此查询应该会给出您想要的结果。它使用 UNION
查询将交易拆分为 Source
和 Destination
账户,然后使用条件聚合 SUM
每个账户的交易。
WITH CTE AS (
SELECT Source AS account, 'debits' AS type, -Amount AS Amount
FROM sa_general_journal
UNION ALL
SELECT Destination, 'credits', Amount
FROM sa_general_journal
)
SELECT acc.ID, acc.Name,
SUM(CASE WHEN CTE.type = 'debits' THEN Amount END) AS Debits,
SUM(CASE WHEN CTE.type = 'credits' THEN Amount END) AS Credits,
SUM(Amount) AS Net
FROM CTE
JOIN sa_accounts acc ON CTE.account = acc.ID
GROUP BY acc.ID, acc.Name
输出(对于您的样本数据):
ID Name Debits Credits Net
4059999 Test Income account -10.1 null -10.1
1009999 Test chequing account -1.01 10.1 9.09
5059999 Test Income account null 1.01 1.01
我在会计系统中生成损益报告时遇到问题。
每个普通日记帐分录都有一个金额、一个来源帐户和一个目标帐户。这是一个简化的架构和一些示例数据:
CREATE TABLE `sa_general_journal` (
`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`Date` timestamp NOT NULL DEFAULT current_timestamp(),
`Item` varchar(1024) NOT NULL DEFAULT '',
`Amount` decimal(9,2) NOT NULL DEFAULT 0.00,
`Source` int(10) unsigned NOT NULL,
`Destination` int(10) unsigned NOT NULL,
PRIMARY KEY (`ID`),
KEY `Date` (`Date`),
KEY `Source` (`Source`),
KEY `Destination` (`Destination`),
CONSTRAINT `sa_credit-account` FOREIGN KEY (`Destination`) REFERENCES `sa_accounts` (`ID`),
CONSTRAINT `sa_debit-account` FOREIGN KEY (`Source`) REFERENCES `sa_accounts` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=21561 DEFAULT CHARSET=utf8;
CREATE TABLE `sa_accounts` (
`ID` int(10) unsigned NOT NULL,
`Name` varchar(255) NOT NULL,
`Type` enum('Asset','Liability','Income','Expense'),
`Report` enum('BS','PL'), -- for "Balance Sheet" or "Profit & Loss"
PRIMARY KEY (`ID`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO sa_account (`ID`, `Name`, `Type`, `Report`)
VALUES (1009999, "Test chequing account", "Asset", "BS"),
(4059999, "Test Income account", "Income", "PL"),
(5059999, "Test Expense account", "Expense", "PL");
INSERT INTO sa_general_journal (`ID`, `Date`, `Item`, `Amount`, `Source`, `Destination`)
VALUES (NULL, "2020-05-03", "Test income transaction", 10.10, 4059999, 1009999),
(NULL, "2020-05-03", "Test expense transaction", 1.01, 1009999, 5059999);
这是存入支票账户的 10.10 美元收入和从支票账户中支出的 1.01 美元。
要获得损益表的余额,需要对 Source
列中每个出现的帐户的所有 Amount
条目求和,然后减去所有 Amount
个条目,其中该帐户位于 Destination
列。
预期结果为:
<table>
<th>ID</th><th>Name</th><th>Debits</th><th>Credits</th><th>Net</th></tr>
<tr><td>1009999</td><td>Test chequing account</td><td>-1.01</td><td>10.10</td><td>9.09</td></tr>
<tr><td>4059999</td><td>Test income transaction</td><td>-10.10</td><td><i>NULL</i></td><td>-10.10</td></tr>
<tr><td>5059999</td><td>Test expense transaction</td><td><i>NULL</i></td><td>1.01</td><td>1.01</td></tr>
</table>
我的第一个方法有点天真,查询 sa_general_journal
table 与 sa_accounts
table 连接,由 Source
和 Destination
列,如果 Destination
包含感兴趣的帐户,则使用 IF 函数取反 Amount
。我在使用准备好的语句查询个人帐户时成功地做到了这一点:
SELECT
DATE_FORMAT(exp.Date, '%Y') AS `Year`,
AVG(exp.Amount) AS `Avg`,
SUM(IF(Source = ?, 0 - exp.Amount, NULL)) AS `Debits`,
SUM(IF(Destination = ?, exp.Amount, NULL)) AS `Credits`,
SUM(IF(Source = ?, 0 - exp.Amount, exp.Amount)) AS `Net`
FROM sa_general_journal exp
LEFT JOIN sa_accounts Destination ON exp.Destination = Destination.ID
WHERE Destination = ?
OR Source = ?
GROUP BY `Year`
所有四个占位符都拥有相同的帐户 ID。
但是,当在没有 WHERE 子句的情况下按帐户 ID 进行分组以获取所有帐户余额的列表时,这会失败——用动态帐户 ID 代替占位符中的静态帐户 ID 似乎永远不会达到“0 - exp.Amount" 代码……呃!查询被选择为一个集合,而不是过程中的步骤。我明白了。
使 SUM 语句子查询可行,但速度慢得可怕,显然要对数万条记录中的每条记录执行三个子查询!
所以,我想我可以用几个常见的 Table 表达式分解子查询,但这似乎也不能正常工作,因为它似乎仍然只是简单地返回 SUM(Amount
), 不减去 Amount
s 即 Destination
s 而不是 Source
s.
WITH
source1 AS (SELECT
src.ID,
src.Name,
SUM(Amount) Amount
FROM sa_general_journal gj
LEFT JOIN sa_accounts src ON gj.`Source` = src.ID
WHERE src.Report = "PL" -- AND YEAR(`Date`) = 2019
GROUP BY src.ID),
destination1 AS (SELECT
dst.ID,
dst.Name,
SUM(0-Amount) Amount
FROM sa_general_journal gj
LEFT JOIN sa_accounts dst ON gj.`Destination` = dst.ID
WHERE dst.Report = "PL" -- AND YEAR(`Date`) = 2019
GROUP BY dst.ID)
SELECT ID, Name, sum(Amount)
FROM source1
UNION ALL
SELECT ID, Name, sum(Amount)
FROM destination1
GROUP BY ID
我猜我在做一些愚蠢的假设,或者做一些愚蠢的事情,所以任何建议都将不胜感激!
没有样本数据很难 100% 确定,但此查询应该会给出您想要的结果。它使用 UNION
查询将交易拆分为 Source
和 Destination
账户,然后使用条件聚合 SUM
每个账户的交易。
WITH CTE AS (
SELECT Source AS account, 'debits' AS type, -Amount AS Amount
FROM sa_general_journal
UNION ALL
SELECT Destination, 'credits', Amount
FROM sa_general_journal
)
SELECT acc.ID, acc.Name,
SUM(CASE WHEN CTE.type = 'debits' THEN Amount END) AS Debits,
SUM(CASE WHEN CTE.type = 'credits' THEN Amount END) AS Credits,
SUM(Amount) AS Net
FROM CTE
JOIN sa_accounts acc ON CTE.account = acc.ID
GROUP BY acc.ID, acc.Name
输出(对于您的样本数据):
ID Name Debits Credits Net
4059999 Test Income account -10.1 null -10.1
1009999 Test chequing account -1.01 10.1 9.09
5059999 Test Income account null 1.01 1.01