我有两个有季节性折扣的桌子。这两个表中的每个表均包含不重叠的日期范围,在该日期范围内适用的产品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)为非聚集索引。
答案 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)