复杂的嵌套聚合以获取订单总计
Posted
技术标签:
【中文标题】复杂的嵌套聚合以获取订单总计【英文标题】:Complex nested aggregations to get order totals 【发布时间】:2020-08-25 11:44:39 【问题描述】:我有一个系统来跟踪订单和相关支出。这是一个在 PostgreSQL 上运行的 Rails 应用程序。我的应用程序 99% 都通过普通的旧 Rails Active Record 调用等。这个很丑。
expenditures
表如下所示:
+----+----------+-----------+------------------------+
| id | category | parent_id | note |
+----+----------+-----------+------------------------+
| 1 | order | nil | order with no invoices |
+----+----------+-----------+------------------------+
| 2 | order | nil | order with invoices |
+----+----------+-----------+------------------------+
| 3 | invoice | 2 | invoice for order 2 |
+----+----------+-----------+------------------------+
| 4 | invoice | 2 | invoice for order 2 |
+----+----------+-----------+------------------------+
每个expenditure
有很多expenditure_items
并且可以订单可以是发票的父母。该表如下所示:
+----+----------------+-------------+-------+---------+
| id | expenditure_id | cbs_item_id | total | note |
+----+----------------+-------------+-------+---------+
| 1 | 1 | 1 | 5 | Fuit |
+----+----------------+-------------+-------+---------+
| 2 | 1 | 2 | 15 | Veggies |
+----+----------------+-------------+-------+---------+
| 3 | 2 | 1 | 123 | Fuit |
+----+----------------+-------------+-------+---------+
| 4 | 2 | 2 | 456 | Veggies |
+----+----------------+-------------+-------+---------+
| 5 | 3 | 1 | 34 | Fuit |
+----+----------------+-------------+-------+---------+
| 6 | 3 | 2 | 76 | Veggies |
+----+----------------+-------------+-------+---------+
| 7 | 4 | 1 | 26 | Fuit |
+----+----------------+-------------+-------+---------+
| 8 | 4 | 2 | 98 | Veggies |
+----+----------------+-------------+-------+---------+
我需要跟踪一些事情:
剩余的订单发票金额(很简单) 以上但为每个cbs_item_id
卷起(这是丑陋的部分)
cbs_item_id 基本上是一个会计代码,用于对所花费的钱等进行分类。我已经可视化了我的最终结果:
+-------------+----------------+-------------+---------------------------+-----------+
| cbs_item_id | expenditure_id | order_total | invoice_total | remaining |
+-------------+----------------+-------------+---------------------------+-----------+
| 1 | 1 | 5 | 0 | 5 |
+-------------+----------------+-------------+---------------------------+-----------+
| 1 | 2 | 123 | 60 | 63 |
+-------------+----------------+-------------+---------------------------+-----------+
| | | | Rollup for cbs_item_id: 1 | 68 |
+-------------+----------------+-------------+---------------------------+-----------+
| 2 | 1 | 15 | 0 | 15 |
+-------------+----------------+-------------+---------------------------+-----------+
| 2 | 2 | 456 | 174 | 282 |
+-------------+----------------+-------------+---------------------------+-----------+
| | | | Rollup for cbs_item_id: 2 | 297 |
+-------------+----------------+-------------+---------------------------+-----------+
order_total
是给定订单(类别 = '订单')的所有支出项目的total
总和。 invoice_total
是 parent_id = 支出.id 的所有支出项目的总和。剩余按差值计算(但不大于 0)。实际上,这里的想法是您下订单并订购 1000 美元和 750 美元的发票。我需要计算订单上剩下的 250 美元(剩余) - 分解为每个类别 (cbs_item_id
)。然后我需要汇总由cbs_item_id
分组的所有剩余值。
因此,对于每个cbs_item_id
,我需要按每个订单进行分组,找到订单的总额,找到针对订单开具发票的总额,然后减去两者(也不能为负数)。它必须以每个订单为基础 - 总体差异不会返回预期结果。
最终寻找这样的结果:
+-------------+-----------+
| cbs_item_id | remaining |
+-------------+-----------+
| 1 | 68 |
+-------------+-----------+
| 2 | 297 |
+-------------+-----------+
我猜这可能是 GROUP BY 和子查询甚至 CTE(对我来说是巫术)的组合。我的 SQL 技能不是很好,这远远高于我的工资等级。
这是上面数据的一个小提琴:
http://sqlfiddle.com/#!17/2fe3a
备用小提琴:
https://dbfiddle.uk/?rdbms=postgres_11&fiddle=e9528042874206477efbe0f0e86326fb
【问题讨论】:
【参考方案1】:此查询产生您正在寻找的结果:
SELECT cbs_item_id, sum(order_total - invoice_total) AS remaining
FROM (
SELECT cbs_item_id
, COALESCE(e.parent_id, e.id) AS expenditure_id -- ①
, COALESCE(sum(total) FILTER (WHERE e.category = 'order' ), 0) AS order_total -- ②
, COALESCE(sum(total) FILTER (WHERE e.category = 'invoice'), 0) AS invoice_total
FROM expenditures e
JOIN expenditure_items i ON i.expenditure_id = e.id
GROUP BY 1, 2 -- ③
) sub
GROUP BY 1
ORDER BY 1;
db小提琴here
① 请注意我如何假设expenditures.parent_id
是integer
和真正的NULL
而不是字符串'nil' 的更合理的表定义。这允许简单地使用COALESCE
。
②关于聚合FILTER
子句:
③ 使用ordinal numbers of an SELECT
list items 的短句法。示例:
我可以得到所有行剩余的总数还是需要将其包装到另一个子选择中?
GROUPING SETS
有一个非常简洁的选项:
...
GROUP BY GROUPING SETS ((1), ()) -- that's all :)
db小提琴here
相关:
Converting rows to columns【讨论】:
是的 - 它是一个真正的 NULL。这有帮助 - 我实际上可以按照这里的逻辑进行操作?。打算在我的实际应用中尝试这个 - 这看起来很简单。 附带问题 - 既然我看到了你的答案,我或许应该重新提出我的问题。我查询了 cbs_items 即 id = 1, name = 'fruit', id=2, name = 'veggies' 等。在最终界面中我想列出这些(包括没有支出的 cbs_items 等.). @DanTappin:刚开始一个新问题。这一次,以实际的最小表定义(CREATE TABLE
就像在小提琴中的语句)和你的 Postgres 版本作为引导。您可以随时链接到此链接以获取上下文,并在此处添加评论以链接回。
我想我真的想通了 - 从 cbs_items 中做了第一个 FROM 然后双左连接。 dbfiddle.uk/… 我将尝试以“Rails”方式执行此操作,以便更轻松地集成到我的应用程序中。 ?
第一次尝试这个:***.com/questions/61706844/…以上是关于复杂的嵌套聚合以获取订单总计的主要内容,如果未能解决你的问题,请参考以下文章
当总计为正数时将订单计为“订单”,但当总计为负数时计为“退货”