复杂的嵌套聚合以获取订单总计

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_idinteger 和真正的NULL 而不是字符串'nil' 的更合理的表定义。这允许简单地使用COALESCE

②关于聚合FILTER子句:

Aggregate columns with additional (distinct) filters

③ 使用ordinal numbers of an SELECT list items 的短句法。示例:

Select first row in each GROUP BY group?

我可以得到所有行剩余的总数还是需要将其包装到另一个子选择中?

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/…

以上是关于复杂的嵌套聚合以获取订单总计的主要内容,如果未能解决你的问题,请参考以下文章

当总计为正数时将订单计为“订单”,但当总计为负数时计为“退货”

化工行业的订单分配(考虑批号)

如何获取客户的用户 ID 以及他们使用聚合或窗口函数下的每个订单?

SQL怎么查询订单?好的话给分多!!!

如何在laravel中按特定月份获取用户订单数据?

关于多重嵌套的JSON数据解析