Думаю, вам нужно будет использовать recursive common table expression.
EDIT: Я также добавил процедуру, описанную ниже, вместо использования рекурсивного общего табличного выражения. Я рекомендую использовать этот процедурный подход, так как я думаю, что может быть несколько сценариев данных, которые, по-видимому, не учитывают рекурсивный запрос CTE, который я включил.
Ниже приведен правильный ответ на предоставленные вами сценарии, но вы, вероятно, захотите придумать некоторые дополнительные сложные сценарии и посмотреть, есть ли какие-либо ошибки.
Например, у меня возникает ощущение, что этот запрос может сломаться, если у вас несколько предыдущих заказов, перекрывающих более поздний порядок.
with CurrentOrders (UserId, SubscriptionDays, StartDate, EndDate) as
(
select
userid,
sum(subscriptiondays),
min(orderdate),
dateadd(day, sum(subscriptiondays), min(orderdate))
from #orders
where
#orders.orderdate <= @currentdate
-- start with the latest order(s)
and not exists (
select 1
from #orders o2
where
o2.userid = #orders.userid
and o2.orderdate <= @currentdate
and o2.orderdate > #orders.orderdate
)
group by
userid
union all
select
#orders.userid,
#orders.subscriptiondays,
#orders.orderdate,
dateadd(day, #orders.subscriptiondays, #orders.orderdate)
from #orders
-- join any overlapping orders
inner join CurrentOrders on
#orders.userid = CurrentOrders.UserId
and #orders.orderdate < CurrentOrders.StartDate
and dateadd(day, #orders.subscriptiondays, #orders.orderdate) > CurrentOrders.StartDate
)
select
UserId,
sum(SubscriptionDays) as TotalSubscriptionDays,
min(StartDate),
sum(SubscriptionDays) - datediff(day, min(StartDate), @currentdate) as RemainingSubscriptionDays
from CurrentOrders
group by
UserId
;
Филипп упомянул о проблеме ограничения рекурсии на общие табличные выражения. Ниже приведена процедурная альтернатива, использующая переменную таблицы и цикл while, которые, как я полагаю, выполняют одно и то же.
Хотя я проверял, что этот альтернативный код действительно работает, по крайней мере, для предоставленных данных примера, я был бы рад услышать комментарии кого-либо к этому подходу. Хорошая идея? Плохая идея? Любые проблемы, о которых нужно знать?
declare @ModifiedRows int
declare @CurrentOrders table
(
UserId int not null,
SubscriptionDays int not null,
StartDate date not null,
EndDate date not null
)
insert into @CurrentOrders
select
userid,
sum(subscriptiondays),
min(orderdate),
min(dateadd(day, subscriptiondays, orderdate))
from #orders
where
#orders.orderdate <= @currentdate
-- start with the latest order(s)
and not exists (
select 1
from #orders o2
where
o2.userid = #orders.userid
and o2.orderdate <= @currentdate
-- there does not exist any other order that surpasses it
and dateadd(day, o2.subscriptiondays, o2.orderdate) > dateadd(day, #orders.subscriptiondays, #orders.orderdate)
)
group by
userid
set @ModifiedRows = @@ROWCOUNT
-- perform an extra update here in case there are any additional orders that were made after the start date but before the specified @currentdate
update co set
co.SubscriptionDays = co.SubscriptionDays + #orders.subscriptiondays
from @CurrentOrders co
inner join #orders on
#orders.userid = co.UserId
and #orders.orderdate <= @currentdate
and #orders.orderdate >= co.StartDate
and dateadd(day, #orders.subscriptiondays, #orders.orderdate) < co.EndDate
-- Keep attempting to update rows as long as rows were updated on the previous attempt
while(@ModifiedRows > 0)
begin
update co set
SubscriptionDays = co.SubscriptionDays + overlap.subscriptiondays,
StartDate = overlap.orderdate
from @CurrentOrders co
-- join any overlapping orders
inner join (
select
#orders.userid,
sum(#orders.subscriptiondays) as subscriptiondays,
min(orderdate) as orderdate
from #orders
inner join @CurrentOrders co2 on
#orders.userid = co2.UserId
and #orders.orderdate < co2.StartDate
and dateadd(day, #orders.subscriptiondays, #orders.orderdate) > co2.StartDate
group by
#orders.userid
) overlap on
overlap.userid = co.UserId
set @ModifiedRows = @@ROWCOUNT
end
select
UserId,
sum(SubscriptionDays) as TotalSubscriptionDays,
min(StartDate),
sum(SubscriptionDays) - datediff(day, min(StartDate), @currentdate) as RemainingSubscriptionDays
from @CurrentOrders
group by
UserId
EDIT2: Я сделал некоторые изменения в код выше, чтобы рассмотреть различные специальные случаи, такие, как если только случится быть на два порядка для пользователя, что оба конца в тот же день.
Например, изменение данных настроек в следующие причинах проблем с исходным кодом, который я в настоящее время скорректированный:
insert into #orders
select 1, 2, 10, '2011-01-01'
union
select 2, 1, 10, '2011-01-10'
union
select 3, 1, 10, '2011-01-15'
union
select 4, 2, 6, '2011-01-15'
union
select 5, 2, 4, '2011-01-17'
EDIT3: Я сделал некоторые дополнительные настройки для решения других специальных случаев.В частности, предыдущий код побежал в проблемы со следующими данными настройки, которые я в настоящее время исправлено:
insert into #orders
select 1, 2, 10, '2011-01-01'
union
select 2, 1, 6, '2011-01-10'
union
select 3, 1, 10, '2011-01-15'
union
select 4, 2, 10, '2011-01-15'
union
select 5, 1, 4, '2011-01-12'
Для claify, вы хотите, чтобы разница в днях между вашим параметром и даты заказа? – Maess
Спасибо за предоставление кода настройки, который работает! –