如何使用PostgreSQL计算分层定价

时间:2018-08-06 02:18:54

标签: sql postgresql window-functions

我正在尝试计算某些住宿的分级费用。假设我们有每周,半周和每天的房价。

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

我该如何查询总夜晚数,并弄清哪些时段符合条件(从最长到最短)?一些示例结果

10晚

我们将10分为(7天)+(3天)。这7天将以周速率(100)进行。这三天的价格为半周(50)。在这里,它有资格获得(1周@ 100)+(1半周@ 50)

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

4晚

我们将4分为(3天)+(1天)。这三天的价格为半周(50)。 1天将按DAY费率(25)。在这里它符合(1半周@ 50)+(1天@ 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天将以DAY费率(2 x 25)。在这里,它有资格获得(2周@ 100)+(2天@ 25)

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

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

2 个答案:

答案 0 :(得分:1)

您可以使用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分组

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

答案 1 :(得分:0)

这里您需要做的是首先触发一条SQL命令以检索所有条件并为您的业务逻辑写下功能。

例如。

  1. 我将在查询下面向数据库中触发。

    按晚上的夜晚顺序从table_name顺序中选择*

结果,我将获得按夜晚降序排列的数据,这意味着首先是7,然后是3,然后是1。

  1. 例如,我将编写一个函数来写下我的业务逻辑。

假设我需要找11天。

我将获取第一个记录7,并检查它为11。

if(11 > 7){// execute this if in a loop till it's greater then 7, same for 3 & 1
    days = 11-7;
    price += price_from_db;
    package += package_from_db;
}else{
   // goto fetch next record and check the above condition with next record.
}

注意:我写下一种算法,而不是特定于语言的代码。