sql - 如何使用 PostgreSQL 计算分层定价

标签 sql postgresql window-functions

我正在尝试计算某些住宿的分级费率。假设我们有一个特性的每周、半周和每天的费率。

period_name | nights | rate
-------------------------------------
WEEK        | 7      | 100
HALFWEEK    | 3      | 50
DAY         | 1      | 25

我如何使用总夜数查询此数据并分割符合条件的时间段(从最长到最短)?一些示例结果

10 晚

我们将 10 分成(7 天)+(3 天)。 7 天将按周费率 (100)。 3 天将按半周费率 (50)。在这里它符合 (1 WEEK @ 100) + (1 HALFWEEK @ 50)

period_name | nights | rate | num | subtotal
----------------------------------------------
WEEK        | 7      | 100  | 1   | 100
HALFWEEK    | 3      | 50   | 1   | 50

4 晚

我们将 4 分成(3 天)+(1 天)。 3 天将按半周费率 (50)。 1 天将按 DAY 费率 (25) 计算。在这里它符合 (1 HALFWEEK @ 50) + (1 DAY @ 25)

period_name | nights | rate | num | subtotal
----------------------------------------------
HALFWEEK    | 3      | 50   | 1   | 50
DAY         | 1      | 25   | 1   | 25

16 晚

我们将 16 分解为(14 天)+(2 天)。 14 天将采用周率(乘以 2),(100 * 2)。 2 天将按天费率 (2 x 25)。在这里它符合 (2 WEEK @ 100) + (2 DAY @ 25)

period_name | nights | rate | num | subtotal
----------------------------------------------
WEEK        | 7      | 100  | 2   | 200
DAY         | 1      | 25   | 2   | 50

我考虑过使用 lag窗口函数,但现在确定我将如何跟踪上一期间已经应用的天数。

最佳答案

您可以使用 CTE RECURSIVE 查询来执行此操作。

http://sqlfiddle.com/#!17/0ac709/1

等级表(可动态扩展):

id  name       days  rate  
--  ---------  ----  ----  
1   WEEK       7     100   
2   DAYS       1     25    
3   HALF_WEEK  3     50    
4   MONTH      30    200    

天数据:

id  num  
--  ---  
1   10   
2   31   
3   30   
4   19   
5   14   
6   108  
7   3    
8   5    
9   1    
10  2    
11  7

结果:

num_id  num  days                                             total_price  
------  ---  -----------------------------------------------  -----------  
1       10   {"MONTH: 0","WEEK: 1","HALF_WEEK: 1","DAYS: 0"}  150          
2       31   {"MONTH: 1","WEEK: 0","HALF_WEEK: 0","DAYS: 1"}  225          
3       30   {"MONTH: 1","WEEK: 0","HALF_WEEK: 0","DAYS: 0"}  200          
4       19   {"MONTH: 0","WEEK: 2","HALF_WEEK: 1","DAYS: 2"}  300          
5       14   {"MONTH: 0","WEEK: 2","HALF_WEEK: 0","DAYS: 0"}  200          
6       108  {"MONTH: 3","WEEK: 2","HALF_WEEK: 1","DAYS: 1"}  875          
7       3    {"MONTH: 0","WEEK: 0","HALF_WEEK: 1","DAYS: 0"}  50           
8       5    {"MONTH: 0","WEEK: 0","HALF_WEEK: 1","DAYS: 2"}  100          
9       1    {"MONTH: 0","WEEK: 0","HALF_WEEK: 0","DAYS: 1"}  25           
10      2    {"MONTH: 0","WEEK: 0","HALF_WEEK: 0","DAYS: 2"}  50           
11      7    {"MONTH: 0","WEEK: 1","HALF_WEEK: 0","DAYS: 0"}  100          

想法:

首先我使用这个查询来计算一个值 (19) 的结果:

SELECT 
    days / 7 as WEEKS,
    days % 7 / 3 as HALF_WEEKS,
    days % 7 % 3 / 1 as DAYS 
FROM
    (SELECT 19 as days) s  

在这里您可以看到以整数除法终止的模块运算的递归结构。因为应该需要一个更通用的版本,所以我考虑了一个递归版本。使用 PostgreSQL WITH RECURSIVE 子句,这是可能的

https://www.postgresql.org/docs/current/static/queries-with.html

这就是最后的查询

WITH RECURSIVE days_per_tier(row_no, name, days, rate, counts, mods, num_id, num) AS (
    SELECT 
        row_no, 
        name, 
        days, 
        rate,
        num.num / days, 
        num.num % days, 
        num.id, 
        num.num
    FROM (
        SELECT 
            *, 
            row_number() over (order by days DESC) as row_no             -- C
        FROM 
            testdata.tiers) tiers,                      -- A
            (SELECT id, num FROM testdata.numbers) num  -- B
    WHERE row_no = 1

    UNION

    SELECT 
        days_per_tier.row_no + 1,
        tiers.name, 
        tiers.days,
        tiers.rate,
        mods / tiers.days,                                      -- D
        mods % tiers.days,                                      -- E
        days_per_tier.num_id,
        days_per_tier.num                               
    FROM 
        days_per_tier,  
        (SELECT 
            *, 
            row_number() over (order by days DESC) as row_no 
            FROM testdata.tiers) tiers 
    WHERE days_per_tier.row_no + 1 = tiers.row_no
 ) 
SELECT
    num_id,
    num,
    array_agg(name || ': ' || counts ORDER BY days DESC) as days, 
    sum(total_rate_per_tier) as total_price                    -- G
FROM (
    SELECT 
        *,
        rate * counts as total_rate_per_tier                   -- F
    FROM days_per_tier) s
GROUP BY num_id, num 
ORDER BY num_Id

WITH RECURSIVE 包含递归的起点 UNION 的递归部分。起点只是获得层级 (A) 和数字 (B)。为了根据天数对层进行排序,我添加了行数(C;仅当相应的 ID 与我的示例中的顺序不正确时才需要。如果您添加另一个层,可能会发生这种情况)。

递归部分取上一个SELECT结果(存储在days_per_tier中),计算下一个余数和整数除法(D,E)。所有其他列仅用于保存原始值(负责递归本身的递增行计数器除外)。

递归后,计数和比率相乘 (F),然后按生成总和 (G) 的原始编号 id 分组

编辑: 添加了速率函数和 sqlfiddle 链接。

关于sql - 如何使用 PostgreSQL 计算分层定价,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51699815/

相关文章:

apache-pig - 如何使用piggybank中的over函数

mysql - 查找不在连接中的记录——高效

mysql - 添加外键错误

SQL相关子查询事件序列

ruby-on-rails - Rails 4 总是为列添加空值

sql - 基于行的顺序分组

sql - 窗口函数,尝试按 created_at 从连接表中的列排序而不分组

php - 从 "mysql_query"的结果中计算行数

sql - PostgreSQL 更新日期范围

ruby-on-rails - rake 测试和 test_structure.sql