У меня две таблицы с сезонными скидками. В каждой из этих двух таблиц есть непересекающиеся диапазоны дат, идентификатор продукта и скидка, которые применяются в этом диапазоне дат. Диапазоны дат из одной таблицы, однако, могут совпадать с диапазонами дат в другой таблице. Учитывая третью таблицу с идентификатором продукта и его ценой по умолчанию, цель состоит в том, чтобы эффективно рассчитать цены на сезонные даты для идентификатора продукта после применения скидок из обеих таблиц.
Скидки умножаются только в период их перекрытия, например, если первая скидка составляет 0,9 (10%) с 2019-07-01 по 2019-07-30, а вторая скидка составляет 0,8 с 2019-07-16 по 2019-08-15, это означает: скидка 0,9 с 2019 -07-01 до 2019-07-15, скидка 0,72 с 2019-07-16 по 2019-07-30 и скидка 0,8 с 2019-07-31 до 2019-08-15.
Мне удалось прийти к решению, сначала сгенерировав таблицу, в которой упорядочены все даты начала и окончания в обеих таблицах скидок, затем сгенерировав результирующую таблицу со всеми наименьшими непересекающимися интервалами, а затем для каждого интервала сгенерировав все цены, по умолчанию, цена с применением только скидки от первой таблицы (если применимо), цена с учетом только скидки от второй таблицы (если применимо), цена с обеими скидками (если это возможно) и затем минимальная цена эти четыре цены. Смотрите пример кода ниже.
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
Есть ли более эффективное решение, которого я не вижу?
У меня большой набор данных: ~ 20 тыс. Строк в таблице реальных цен на продукты и 100–200 тыс. Строк в обеих таблицах скидок.
Структура индексирования фактических таблиц следующая: идентификатор продукта является кластеризованным индексом в таблице цен на продукты, в то время как таблицы скидок имеют суррогатный столбец Id в качестве кластеризованного индекса (а также первичного ключа) и (product_id, start_date, end_date) как некластерный индекс.