将一个数字分成多个波段

Divide a number into multiple bands

我一直在处理将一个指标拆分成几个波段的问题。为了给您一些背景信息,让我们以这个例子为例,每个客户都有一定数量的订单。现在,一个客户可以订购 n 种产品。让我们根据订单数量给客户一定的折扣。折扣是基于分层模型提供的。为了简单起见,我省略了多个产品类别。以下是 table 的一些示例。

订单table

Customer    |   order_no
----------------------------
Customer1   |   400
Customer2   |   1200
Customer3   |   40
Customer4   |   2000
Customer5   |   700

分层定价table

Tier   | lower_th | higer_th | price |
--------------------------------------
Tier1  | 0        | 250      | 50    |
TIer2  | 251      | 500      | 45    |
Tier3  | 501      | 1000     | 40    |
TIer4  | 1001     | 10000    |  30   |

示例 1:我希望能够向客户 1 收取 250 件订单 50 美元的费用,并向总共 400 件产品中的其余 150 件收取 45 美元。

示例 2:我希望能够向 Customer5 收取 250 笔订单 50 美元,另外 250 笔订单收取 45 美元,其余 200 笔订单 40 美元,共计 700 件商品。

如何在 PostgreSQL 中实现这一点?对于 Customer1,我的输出需要如下所示。拆分订单总数并将其加入定价层以获得相应数量的最佳方法是什么?

Customer  | order_no | charges |
--------------------------------
Customer1 | 250      | 50      |
Customer1 | 150      | 45      |

您可以将层级视为间隔。

两个区间[a1, b1][a2, b2]相交时

a1 <= b2 AND b1 >= a2

订单数是另一个始终从 1 开始的区间。

您的两个间隔是:等级 [lower_th, higer_th] 和订单 [1, order_no]

查询是使用此交集表达式的简单连接:

SELECT *
    ,CASE WHEN O.order_no > T.higer_th
    THEN T.higer_th - T.lower_th + 1 -- full tier
    ELSE O.order_no - T.lower_th + 1
    END AS SplitOrderNumbers
FROM
    Orders AS O
    INNER JOIN Tiers AS T
        -- ON 1 <= T.higer_th AND O.order_no >= T.lower_th
        ON O.order_no >= T.lower_th
ORDER BY
    O.Customer
    ,T.lower_th
;

你真的不需要 1 <= T.higer_th 部分,因为它总是为真,所以表达式变得简单 O.order_no >= T.lower_th.

此外,通常最好将间隔存储为 [closed; open)。它通常简化算术,类似于为什么大多数编程语言的数组索引从 0 而不是 1 开始。你的间隔似乎是 [closed; closed]。在这种情况下,您需要将 lower_th 设置为 1,而不是 0,并在计算中包含 +1

通过对样本数据的这种调整,此查询产生以下结果:

+-----------+----------+-------+----------+----------+-------+-------------------+
| Customer  | order_no | Tier  | lower_th | higer_th | price | SplitOrderNumbers |
+-----------+----------+-------+----------+----------+-------+-------------------+
| Customer1 |      400 | Tier1 |        1 |      250 | 50.00 |               250 |
| Customer1 |      400 | Tier2 |      251 |      500 | 45.00 |               150 |
| Customer2 |     1200 | Tier1 |        1 |      250 | 50.00 |               250 |
| Customer2 |     1200 | Tier2 |      251 |      500 | 45.00 |               250 |
| Customer2 |     1200 | Tier3 |      501 |     1000 | 40.00 |               500 |
| Customer2 |     1200 | Tier4 |     1001 |    10000 | 30.00 |               200 |
| Customer3 |       40 | Tier1 |        1 |      250 | 50.00 |                40 |
| Customer4 |     2000 | Tier1 |        1 |      250 | 50.00 |               250 |
| Customer4 |     2000 | Tier2 |      251 |      500 | 45.00 |               250 |
| Customer4 |     2000 | Tier3 |      501 |     1000 | 40.00 |               500 |
| Customer4 |     2000 | Tier4 |     1001 |    10000 | 30.00 |              1000 |
| Customer5 |      700 | Tier1 |        1 |      250 | 50.00 |               250 |
| Customer5 |      700 | Tier2 |      251 |      500 | 45.00 |               250 |
| Customer5 |      700 | Tier3 |      501 |     1000 | 40.00 |               200 |
+-----------+----------+-------+----------+----------+-------+-------------------+

对于定价数据,我会使用这样的 table 来简化数据维护

create table pricing_data
(
   high_limit int, 
   price numeric
);

视图将使用 window 函数为您提供所需的时间间隔:

create view pricing as
  select coalesce(lag(high_limit) over (order by high_limit), 0) as last_limit,
         high_limit, price
    from pricing_data;

这简化了价格等级的细分:

select o.customer, 
       least(o.order_no - p.last_limit, p.high_limit - p.last_limit) as order_no,
       p.price as charges
  from orders o
  join pricing p on p.last_limit < o.order_no
 order by o.customer, p.price desc
;

结果:

customer    order_no    charges
Customer1    250            50
Customer1    150            45
Customer2    250            50
Customer2    250            45
Customer2    500            40
Customer2    200            30
Customer3     40            50
Customer4    250            50
Customer4    250            45
Customer4    500            40
Customer4   1000            30
Customer5    250            50
Customer5    250            45
Customer5    200            40
14 rows

Fiddle here.