На основе двух таблиц дисконтированных дат и цены продукта рассчитайте итоговую цену ранжированной даты - PullRequest
0 голосов
/ 06 июня 2019

У меня две таблицы с сезонными скидками. В каждой из этих двух таблиц есть непересекающиеся диапазоны дат, идентификатор продукта и скидка, которые применяются в этом диапазоне дат. Диапазоны дат из одной таблицы, однако, могут совпадать с диапазонами дат в другой таблице. Учитывая третью таблицу с идентификатором продукта и его ценой по умолчанию, цель состоит в том, чтобы эффективно рассчитать цены на сезонные даты для идентификатора продукта после применения скидок из обеих таблиц.

Скидки умножаются только в период их перекрытия, например, если первая скидка составляет 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) как некластерный индекс.

Ответы [ 2 ]

0 голосов
/ 06 июня 2019

Вот версия, которая определяет цену для каждой даты.Затем вы можете либо использовать это напрямую, либо использовать одно из многих решений в 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)
0 голосов
/ 06 июня 2019

Вы можете сгенерировать даты, используя 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;

Здесь - это дб <> скрипка.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...