0

Table contains data about hotel rooms.

We can see the same Room available for different date periods - at different Prices. AccmdMenTypeCode - is the Code that shows how many people can stay in this type of room.

A person wants to stay in the room from 2025-01-05 to 2025-04-01 (87 days). I already calculated OverlapDaysCount field that shows days count for every date period between Start and End. Now we need to have all the cases when we can accomodate the person in the room for 87 days. And calculate the Price for the Room.

Id HotelCode AccomodationPeriodBegin AccomodationPeriodEnd RoomType AccmdMenTypeCode Price OverlapDaysCount
1 MONTE 2025-01-10 2025-03-10 DGV 01010003 99 60
2 MONTE 2025-01-10 2025-03-10 DGV 01010003 99 60
3 MONTE 2025-03-06 2025-05-14 DGV 01010003 106 27
4 MONTE 2025-03-06 2025-05-14 DGV 01010003 106 27
5 MONTE 2024-07-01 2025-07-15 DGV 01010003 109 87
6 MONTE 2024-07-01 2025-07-15 DGV 01010003 109 87
7 MONTE 2025-01-10 2025-03-10 DGV 01010312 99 60
8 MONTE 2025-01-10 2025-03-10 DGV 01010312 99 60
9 MONTE 2025-03-06 2025-05-14 DGV 01010312 106 27
10 MONTE 2025-03-06 2025-05-14 DGV 01010312 106 27
11 MONTE 2024-07-01 2025-07-15 DGV 01010312 109 87
12 MONTE 2024-07-01 2025-07-15 DGV 01010312 109 87

For example, he can stay for 60 days (till 2025-03-10) in the Room with Id = 1 at Price 99, then in the Room with Id = 3 for 27 days at Price 106.

In total, Price will be (60 * 99) + (27 * 106) = 8,802

OR he can stay in the Room with Id = 5 all the 87 days, but with Price of 109 - in total it will be 87 * 109 = 9,483

We need to offer him all the possible options so that he can choose. Expected request result would be something like this

HotelCode RoomType AccmdMenTypeCode TotalPrice OverlapDaysCount
MONTE DGV 01010003 8802 87
MONTE DGV 01010003 9483 87
MONTE DGV 01010312 9483 87

Here is the table with the data. I try very hard, but it doesn't work either. I'm sure that I need to change my approach.

Any ideas?

15
  • I have reformatted your fiddle to verify that the data has been properly inserted. dbfiddle.uk/k9-RRwpW Commented Jan 15 at 21:15
  • We need to break your problem into manageable steps. I suggest we being by excluding rows that have an end date before 2025-01-05 and a start date after 2025-04-01. Oh, I see you did not include any out of range data. you should do that to properly test your solution. dbfiddle.uk/_i0irmFd Commented Jan 15 at 21:17
  • Please tag the RDBMS you are using. Commented Jan 15 at 21:20
  • 1
    All of your data is from the same hotel. You are using a hotel join in your query. rc.HotelCode = br.HotelCode. You should Include example rows where HotelCode <> 'MONTE' to verify your solution Commented Jan 15 at 21:29
  • @DaleK, I'm using MS SQL. Anyway, Fiddle is enough to find the solution. All that remains is to find it ) Commented Jan 16 at 12:31

2 Answers 2

0

Query parts

  1. CTE suitable - select all rows, intersecting period (dateFrom,dateTo) (2025-01-05->2025-04-01 in example) and rename column for for brevity and convenience.
  2. Recursive query anchor part: take all rows, covering the beginning of the period (fromDate). Main output is (iFrom,iTo) - period, intersection of (fromDate,endDate) and(periodBegin, periodEnd)
  3. Recursion on join anchor and added rows from cte suitable with intersection (iFrom,iTo) and (periodBegin,periodEnd).
  4. Output is (list of room ids),(list of days in appropriate room),(list of price for this room) and total sum.

If recursive query output row (iFrom,iTo)= (fromDate,toDate) - this the desired result.

Fiddle with additional test data and details

In fact, there are many more possible combinations. After all, we can take periods with an offset of 1 day. I took only boundary cases.
See example. We have two segments (123456) and (4567). We need to place such a segment (3456) in them.

A   123456
      ||||
B      4567
Possible combinations:
A(3456)B()
A(3)B(456)
A(34)B(56)
A(345)B(6)

A(3)B(4)A(5)B(6)
A(34)B(5)A(6)
... and so on
A will take this case
A(3456)    - first result
A(3)B(456) - second result

See example:

Id HotelCode Accomodation PeriodBegin Accomodation PeriodEnd RoomType AccmdMenTypeCode Price OverlapDaysCount
1 MONTE 2025-01-10 2025-03-10 DGV 01010003 99 60
3 MONTE 2025-03-06 2025-05-14 DGV 01010003 106 27
5 MONTE 2024-07-01 2025-07-15 DGV 01010003 109 87
7 MONTE 2025-01-10 2025-03-10 DGV 01010312 99 60
9 MONTE 2025-03-06 2025-05-14 DGV 01010312 106 27
11 MONTE 2024-07-01 2025-07-15 DGV 01010312 109 87
with suitables as(
select Id,HotelCode,AccomodationPeriodBegin PeriodBegin
        ,AccomodationPeriodEnd  PeriodEnd
        ,dateFrom,dateTo
        ,RoomType,Price,AccmdMenTypeCode TypeCode
        ,datediff(day,dateFrom,dateTo)+1 totDays
from HotelAccoMmodation h
cross join (select cast('2025-01-05' as date) dateFrom
     , cast('2025-04-01' as date) dateTo)p
where RoomType in('DGV') -- AccmdMenTypeCod RoomType
  and AccmdMenTypeCode in ('01010003','01010312') -- suitable AccmdMenTypeCode
  and ( (AccomodationPeriodBegin<=dateTo and AccomodationPeriodEnd>=dateFrom)
       or (AccomodationPeriodBegin<=dateTo and AccomodationPeriodEnd>=dateFrom)
     )
)

The actual recursive query. I will use cross apply just to simplify calculations and expressions. We save the days, prices, and amounts in paths one step later in the recursive query, because in the next step we can reduce the number of days in the previous step.

,r as(
  select 1 lvl,Id root,hotelcode,Id,cast(concat('',Id) as varchar(1000)) pathIds
    ,dateFrom iFrom
    ,case when periodEnd>=dateTo then dateTo else periodEnd end iTo
    ,case when periodEnd>=dateTo then 0 
     else datediff(day,periodEnd,dateTo) end restDays
    ,price,periodBegin,periodEnd,dateFrom,dateTo
    ,ciTo,ddif
    ,cast(concat('','') as varchar(1000)) pathDays,cDays
    ,cast(concat('',price) as varchar(1000)) pathPrice
    ,cDays*price as total
  from suitables t
  cross apply( values(
       ( case when t.periodEnd>=t.dateTo then t.dateTo else t.periodEnd end )
     ,( datediff(day,t.dateFrom
           ,case when t.periodEnd>=t.dateTo then t.dateTo else t.periodEnd end) +1)
     ,(0)
         ))c(ciTo,cDays,ddif)
  where periodBegin<=dateFrom and periodEnd>=dateFrom
  union all
  select lvl+1 lvl,r.root,r.hotelcode,t.Id,cast(concat(pathIds,':',t.Id) as varchar(1000)) path
    ,t.periodBegin iFrom
    ,c.ciTo iTo
    ,case when t.periodEnd>=r.dateTo then 0 
     else datediff(day,c.ciTo,r.dateTo) 
     end restDays
    ,t.price,t.periodBegin,t.periodEnd,r.dateFrom,r.dateTo
    ,c.ciTo,c.ddif
    ,cast(concat(pathDays,':',r.cDays-c.ddif) as varchar(1000)) pathDays,c.cDays
    ,cast(concat(pathPrice,':',t.price) as varchar(1000)) pathPrice
    ,r.total -r.price*c.ddif+ c.cDays*t.price total
  from r
  inner join suitables  t on r.hotelcode=t.hotelcode
   and ( 
            t.periodBegin<=iTo and t.periodEnd>=iTo
        and t.periodBegin>r.iFrom
    )
  cross apply( values(
       ( case when t.periodEnd>=r.dateTo then r.dateTo else t.periodEnd end )
     ,( datediff(day,t.periodBegin
           ,case when t.periodEnd>=r.dateTo then r.dateTo else t.periodEnd end) +1)
     ,( datediff(day,t.periodBegin,r.iTo)+1 ) 
         ))c(ciTo,cDays,ddif)
  where lvl<7
)
select lvl, root, Id, pathIds, concat(pathDays,':',cDays) pathDays,pathPrice,total
from r 
where iTo=dateTo
order by pathIds,lvl

lvl root Id pathIds pathDays pathPrice total
1 11 11 11 :87 109 9483
2 11 3 11:3 :60:27 109:106 9402
2 11 9 11:9 :60:27 109:106 9402
1 5 5 5 :87 109 9483
2 5 3 5:3 :60:27 109:106 9402
2 5 9 5:9 :60:27 109:106 9402

There, for example row

lvl root Id pathIds pathDays pathPrice total
2 11 3 11:3 :60:27 109:106 9402

roomId 11 days 60 price 109
roomId 3 days 27 price 106
total 9402.

More details:

with suitables as(
select Id,HotelCode,AccomodationPeriodBegin PeriodBegin
        ,AccomodationPeriodEnd  PeriodEnd
        ,dateFrom,dateTo
        ,RoomType,Price,AccmdMenTypeCode TypeCode
        ,datediff(day,dateFrom,dateTo)+1 totDays
from HotelAccoMmodation h
cross join (select cast('2025-01-05' as date) dateFrom
     , cast('2025-04-01' as date) dateTo)p
where RoomType in('DGV') -- AccmdMenTypeCod RoomType
  and AccmdMenTypeCode in ('01010003','01010312') -- suitable AccmdMenTypeCode
  and ( (AccomodationPeriodBegin<=dateTo and AccomodationPeriodEnd>=dateFrom)
       or (AccomodationPeriodBegin<=dateTo and AccomodationPeriodEnd>=dateFrom)
     )
)
,r as(
  select 1 lvl,Id root,Id,cast(concat('',Id) as varchar(1000)) pathIds
    ,dateFrom iFrom
    ,case when periodEnd>=dateTo then dateTo else periodEnd end iTo
    ,case when periodEnd>=dateTo then 0 
     else datediff(day,periodEnd,dateTo) end restDays
    ,price,periodBegin,periodEnd,dateFrom,dateTo
    ,ciTo,ddif
    ,cast(concat('','') as varchar(1000)) pathDays,cDays
    ,cast(concat('',price) as varchar(1000)) pathPrice
    ,cDays*price as total
  from suitables t
  cross apply( values(
       ( case when t.periodEnd>=t.dateTo then t.dateTo else t.periodEnd end )
     ,( datediff(day,t.dateFrom
           ,case when t.periodEnd>=t.dateTo then t.dateTo else t.periodEnd end) +1)
     ,(0)
         ))c(ciTo,cDays,ddif)
  where periodBegin<=dateFrom and periodEnd>=dateFrom
  union all
  select lvl+1 lvl,r.root,t.Id,cast(concat(pathIds,':',t.Id) as varchar(1000)) path
    ,t.periodBegin iFrom
    ,c.ciTo iTo
    ,case when t.periodEnd>=r.dateTo then 0 
     else datediff(day,c.ciTo,r.dateTo) 
     end restDays
    ,t.price,t.periodBegin,t.periodEnd,r.dateFrom,r.dateTo
    ,c.ciTo,c.ddif
    ,cast(concat(pathDays,':',r.cDays-c.ddif) as varchar(1000)) pathDays,c.cDays
    ,cast(concat(pathPrice,':',t.price) as varchar(1000)) pathPrice
    ,r.total -r.price*c.ddif+ c.cDays*t.price total
  from r
  inner join suitables  t on 
    ( 
            t.periodBegin<=iTo and t.periodEnd>=iTo
        and t.periodBegin>r.iFrom
    )
  cross apply( values(
       ( case when t.periodEnd>=r.dateTo then r.dateTo else t.periodEnd end )
     ,( datediff(day,t.periodBegin
           ,case when t.periodEnd>=r.dateTo then r.dateTo else t.periodEnd end) +1)
     ,( datediff(day,t.periodBegin,r.iTo)+1 ) 
         ))c(ciTo,cDays,ddif)
  where lvl<7
)
-- select * from r
select *
from r 
-- where iTo=dateTo
order by pathIds,lvl

lvl root Id pathIds iFrom iTo restDays price periodBegin periodEnd dateFrom dateTo ciTo ddif pathDays cDays pathPrice total
1 11 11 11 2025-01-05 2025-04-01 0 109 2024-07-01 2025-07-15 2025-01-05 2025-04-01 2025-04-01 0 87 109 9483
2 11 3 11:3 2025-03-06 2025-04-01 0 106 2025-03-06 2025-05-14 2025-01-05 2025-04-01 2025-04-01 27 :60 27 109:106 9402
2 11 9 11:9 2025-03-06 2025-04-01 0 106 2025-03-06 2025-05-14 2025-01-05 2025-04-01 2025-04-01 27 :60 27 109:106 9402
1 5 5 5 2025-01-05 2025-04-01 0 109 2024-07-01 2025-07-15 2025-01-05 2025-04-01 2025-04-01 0 87 109 9483
2 5 3 5:3 2025-03-06 2025-04-01 0 106 2025-03-06 2025-05-14 2025-01-05 2025-04-01 2025-04-01 27 :60 27 109:106 9402
2 5 9 5:9 2025-03-06 2025-04-01 0 106 2025-03-06 2025-05-14 2025-01-05 2025-04-01 2025-04-01 27 :60 27 109:106 9402

fiddle

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much! Seems data in DB isn't correct and we need investigations to correct it. As I wrote in another comment, I fixed it in fiddle and got working requests.
0

I think I've figured out the conditions. It's necessary that HotelCode, RoomType and the range (AccomodationPeriodBegin - AccomodationPeriodEnd) do not have duplicate AccmdMenTypeCode-s. Otherwise it makes confusion for users and difficulties when retrieving data from the DB.

Also, as @Dave.Gugg rightly pointed out, there were inaccuracies with the test data related to the date range.

I already fixed test data and changed requests to DB.

1 - detailed version with GroupIds

2 - simplier version

I'd appreciate your feedback

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.