给定两个日期范围的折扣表和产品价格,请计算日期范围的最终价格

时间:2019-06-05 22:42:47

标签: sql sql-server tsql

我有两个有季节性折扣的桌子。这两个表中的每个表均包含不重叠的日期范围,在该日期范围内适用的产品ID和折扣。但是,一个表中的日期范围可能与另一表中的日期范围重叠。给定第三个具有产品ID及其默认价格的表格,目标是在应用了两个表格的折扣后,有效地计算产品ID的季节性价格变化价格。

折扣仅在重叠时段内相乘,例如如果从2019-07-01到2019-07-30的第一折扣为0.9(10%),而从2019-07-16到2019-08-15的第二折扣为0.8,则表示:从2019开始的0.9折扣-07-01至2019-07-15,从2019-07-16至2019-07-30享有0.72的折扣,以及从2019-07-31至2019-08-15享有0.8的折扣。

我设法解决了这个问题,首先生成一个表,该表在两个折扣表中都包含所有开始日期和结束日期的有序信息,然后生成所有最小不相交间隔的结果表,然后针对每个间隔,生成所有价格,默认值,仅应用了第一个表的折扣的价格(如果有的话),仅应用了第二个表的折扣的价格(如果有的话),同时应用了两个折扣的价格(如果可能的话)和最小值这四个价格。请参见下面的示例代码。

    declare @pricesDefault table (product_id int, price decimal)
    insert into @pricesDefault 
    values 
    (1, 100), 
    (2, 120), 
    (3, 200),
    (4, 50)

    declare @discountTypeA table (product_id int, modifier decimal(4,2), startdate datetime, enddate datetime)
    insert into @discountTypeA
    values
    (1, 0.75, '2019-06-06', '2019-07-06'),
    (1, 0.95, '2019-08-06', '2019-08-20'),
    (1, 0.92, '2019-05-06', '2019-06-05'),
    (2, 0.75, '2019-06-08', '2019-07-19'),
    (2, 0.95, '2019-07-20', '2019-09-20'),
    (3, 0.92, '2019-05-06', '2019-06-05')

    declare @discountTypeB table (product_id int, modifier decimal(4,2), startdate datetime, enddate datetime)
    insert into @discountTypeB
    values
    (1, 0.85, '2019-06-20', '2019-07-03'),
    (1, 0.65, '2019-08-10', '2019-08-29'),
    (1, 0.65, '2019-09-10', '2019-09-27'),
    (3, 0.75, '2019-05-08', '2019-05-19'),
    (2, 0.95, '2019-05-20', '2019-05-21'),
    (3, 0.92, '2019-09-06', '2019-09-09')                                            
    declare @pricingPeriod table(product_id int, discountedPrice decimal, startdate datetime, enddate datetime);

    with allDates(product_id, dt) as
    (select distinct product_id, dta.startdate from @discountTypeA dta
    union all
    select distinct product_id, dta.enddate from @discountTypeA dta
    union all
    select distinct product_id, dtb.startdate from @discountTypeB dtb
    union all
    select distinct product_id, dtb.enddate from @discountTypeB dtb
    ),
    allproductDatesWithId as
    (select product_id, dt, row_number() over (partition by product_id order by dt asc) 'Id'
    from allDates),
    sched as
    (select pd.product_id, apw1.dt startdate, apw2.dt enddate
    from @pricesDefault pd
    join allproductDatesWithId apw1 on apw1.product_id = pd.product_id
    join allproductDatesWithId apw2 on apw2.product_id = pd.product_id and apw2.Id= apw1.Id+1
    ),
    discountAppliedTypeA as(
    select sc.product_id, sc.startdate, sc.enddate,
    min(case when sc.startdate >= dta.startdate and dta.enddate >= sc.enddate then  pd.price * dta.modifier else pd.price end ) 'price'
    from sched sc
    join @pricesDefault pd on pd.product_id = sc.product_id
    left join @discountTypeA dta on sc.product_id = dta.product_id
    group by sc.product_id, sc.startdate , sc.enddate ),

    discountAppliedTypeB as(
    select daat.product_id, daat.startdate, daat.enddate,
    min(case when daat.startdate >= dta.startdate and dta.enddate >= daat.enddate then daat.price * dta.modifier else daat.price end ) 'price'
    from discountAppliedTypeA daat
    left join @discountTypeB dta on daat.product_id = dta.product_id
    group by daat.product_id, daat.startdate , daat.enddate )

    select * from discountAppliedTypeB
    order by product_id, startdate

计算所有可能价格的最小值是不必要的开销。我想生成一个结果价格,并将其作为最终价格。

这是结果集:

    product_id  start_date              end_date                final_price
    1           2019-05-06 00:00:00.000 2019-06-05 00:00:00.000 92.0000
    1           2019-06-05 00:00:00.000 2019-06-06 00:00:00.000 100.0000
    1           2019-06-06 00:00:00.000 2019-06-20 00:00:00.000 75.0000
    1           2019-06-20 00:00:00.000 2019-07-03 00:00:00.000 63.7500
    1           2019-07-03 00:00:00.000 2019-07-06 00:00:00.000 75.0000
    1           2019-07-06 00:00:00.000 2019-08-06 00:00:00.000 100.0000
    1           2019-08-06 00:00:00.000 2019-08-10 00:00:00.000 95.0000
    1           2019-08-10 00:00:00.000 2019-08-20 00:00:00.000 61.7500
    1           2019-08-20 00:00:00.000 2019-08-29 00:00:00.000 65.0000
    1           2019-08-29 00:00:00.000 2019-09-10 00:00:00.000 100.0000
    1           2019-09-10 00:00:00.000 2019-09-27 00:00:00.000 65.0000
    2           2019-05-20 00:00:00.000 2019-05-21 00:00:00.000 114.0000
    2           2019-05-21 00:00:00.000 2019-06-08 00:00:00.000 120.0000
    2           2019-06-08 00:00:00.000 2019-07-19 00:00:00.000 90.0000
    2           2019-07-19 00:00:00.000 2019-07-20 00:00:00.000 120.0000
    2           2019-07-20 00:00:00.000 2019-09-20 00:00:00.000 114.0000
    3           2019-05-06 00:00:00.000 2019-05-08 00:00:00.000 184.0000
    3           2019-05-08 00:00:00.000 2019-05-19 00:00:00.000 138.0000
    3           2019-05-19 00:00:00.000 2019-06-05 00:00:00.000 184.0000
    3           2019-06-05 00:00:00.000 2019-09-06 00:00:00.000 200.0000
    3           2019-09-06 00:00:00.000 2019-09-09 00:00:00.000 184.0000

此解决方案是否有更有效的解决方案?

我在真实产品价格表中有一个约2万行的大型数据集,在两个折扣表中都有10万至20万行。

实际表的索引结构如下:产品ID是产品价格表中的聚集索引,而折扣表具有ID替代列作为聚集索引(以及主键),而(product_id,start_date,end_date)为非聚集索引。

2 个答案:

答案 0 :(得分:0)

您可以使用union生成日期。然后输入该日期有效的所有折扣,然后计算总计。

这看起来像:

with prices as (
      select a.product_id, v.dte
      from @discountTypeA a cross apply
           (values (a.startdate), (a.enddate)) v(dte)
      union  -- on purpose to remove duplicates
      select b.product_id, v.dte
      from @discountTypeB b cross apply
           (values (b.startdate), (b.enddate)) v(dte)
     ),
     p as (
      select p.*, 1-a.modifier as a_discount, 1-b.modifier as b_discount, pd.price
      from prices p left join
           @pricesDefault pd
           on pd.product_id = p.product_id left join
           @discountTypeA a
           on p.product_id = a.product_id and
              p.dte >= a.startdate and p.dte < a.enddate left join
           @discountTypeb b
           on p.product_id = b.product_id and
              p.dte >= b.startdate and p.dte < b.enddate
     )
select p.product_id, price * (1 - coalesce(a_discount, 0)) * (1 - coalesce(b_discount, 0)) as price, a_discount, b_discount,
       dte as startdate, lead(dte) over (partition by product_id order by dte) as enddate
from p
order by product_id, dte;

Here是db <>小提琴。

答案 1 :(得分:0)

这里是一个计算每个日期价格的版本。然后,您可以直接使用它,也可以使用SO上的许多解决方案之一来确定日期范围。

在此示例中,我对日期限制进行了硬编码,但是如果愿意,您可以轻松地从表中读取它们。

我尚未对此进行任何性能测试,但请尝试一下。如果您有正确的索引,这样做会更加简单。

;with dates as (
    select convert(datetime,'2019-05-06') as d
    union all
    select d+1 from dates where d<'2019-09-27'
)  
select pricesDefault.product_id, d, pricesDefault.price as baseprice,
    discountA.modifier as dA,
    discountB.modifier as dB,
    pricesDefault.price*isnull(discountA.modifier,1)*isnull(discountB.modifier,1) as finalprice
from @pricesDefault pricesDefault
cross join dates 
left join @discountTypeA discountA on discountA.product_id=pricesDefault.product_id and d between discountA.startdate and discountA.enddate
left join @discountTypeB discountB on discountB.product_id=pricesDefault.product_id and d between discountB.startdate and discountB.enddate
order by pricesDefault.product_id, d
Option (MaxRecursion 1000)