Hotel Room Rates for different seasons - mysql

I have a database (MySQL) with a table containing date ranges (as startdate and enddate) and a rate field. The date range implies different seasons (low, high etc.). The scenario is such that a person checks in the hotel and his duration of stay is in two seasons. A sample data is like below:
SeasonName SartDate EndDate Rate
Low 01-01-2007 30-04-2007 100.00
High 01-05-2007 31-08-2007 150.00
Peak 01-09-2007 31-12-2007 200.00
The client's Check In Date is 29-04-2007 and Check Out Date is 03-05-2007. I need to calculate the exact number of nights for each season and also calculate the total amount.
The IDE is VB6. Any help will be extremely appreciated.
Thanks
Tom
Thanks for the response. I need the SQL to extract the information. As for the date validity, lets assume the rate applies till midnight (00:00). Hope i have clarified.
Tom

Having worked in a hotel and written the reservation system, hourly time is irrelevant as far
as billing goes. Everything is always charged by night. (Unless you plan to run a place that charges by the hour! ;-)) Check-in and check-out are operational considerations.
Do not use stored procuedures if you actually want to write a real reservation system.
It defeats the purpose of having a database.
Also, writing out dates like this is 2007-04-29 is really great way because not every one is from the same place and this is an international standard. Also notice, if you were to turn this into a string it will still be sorted correctly!
You need make a calandar table as MySQL does not have in built in functions to do it.
This procedure will build up dates for you.
drop table if exists calendar;
create table calendar
(
date_ date primary key
);
drop procedure fill_calendar;
delimiter $$
create procedure fill_calendar(start_date date, end_date date)
begin
declare date_ date;
set date_=start_date;
while date_ < end_date do
insert into calendar values(date_);
set date_ = adddate(date_, interval 1 day);
end while;
end $$
delimiter ;
call fill_calendar('2007-1-1', '2007-12-31');
from: http://www.ehow.com/how_7571744_mysql-calendar-tutorial.html
drop table if exists rates;
create table rates
(
season varchar(100) primary key,
start_date date references calendar(date_),
end_date date references calendar(date_),
rate float
);
insert into rates values ('Low', '2007-01-01', '2007-04-30', 100.00);
insert into rates values ('High', '2007-05-01', '2007-08-31', 150.00);
insert into rates values ('Peak', '2007-09-01', '2007-12-21', 200.00);
select * from rates;
season start_date end_date rate
Low 2007-01-01 2007-04-30 100
High 2007-05-01 2007-08-31 150
Peak 2007-09-01 2007-12-21 200
I'm going to ignore the dates you have given in your question and the assume the client is not travelling backwards in time.
select
date_, rate
from calendar
join rates
on date_ >= start_date and date_ <= end_date
where date_ between '2007-04-29' and '2007-5-01'
;
date_ rate
2007-04-29 100
2007-04-30 100
2007-05-01 150
select
sum(rate)
from calendar
join rates
on date_ >= start_date and date_ <= end_date
where date_ between '2007-04-29' and '2007-5-01'
sum(rate)
350
And, as you can see the sql is quite concise and readable without resorting to functions or procedures. This will be able to scale properly and handle more complex questions. Also, it enables referential checking to be used since the data is table based.

I used the DATEDIFF function to know the number of days between 2 dates. But since this function returns the number of days excluding the last day (e.g. DATEDIFF(d, '2007-04-29', '2007-04-30') return 1 instead of 2), I used a rate table like this:
SeasonName StartDate EndDate Rate
Low 01-01-2007 01-05-2007 100.00
High 01-05-2007 01-09-2007 150.00
Peak 01-09-2007 01-01-2008 200.00
This is the query I used. The inner of the two SELECTs calculates an effective end date for the customer's stay according to the season (either the end date of the season or it's date prior to it's check out date).
I used 02-05-2007 as the customer's stay end date instead of 03-05-2007 since usually a customer does does not pay for the day he checks out.
SELECT SeasonName, DATEDIFF(d, '2007-04-29', EffectiveEndDate) as NumberOfDays, Rate
FROM (
SELECT SeasonName,
CASE
WHEN EndDate < '2007-05-02' THEN EndDate
ELSE '2007-05-02'
END AS EffectiveEndDate,
Rate
FROM HotelRate
WHERE (StartDate <= '2007-04-29' and EndDate > '2007-04-29')
or (StartDate <= '2007-05-02' and EndDate > '2007-05-02')
) as SubSelect
This gives me this result:
SeasonName NumberOfDays Rate
Low 2 100.00
High 3 150.00
I hope it helps :)

Disclaimer: This is not the most efficient one, but it's the more clear, and if you have the price list cached in an array, the performance hit will be meaningless.
Dim numberOfDays As Integer, i As Integer
Dim CheckInDate As Date, CheckOutDate As Date, CurrDate As Date
Dim TotalRate As Currency
TotalRate = 0
CheckInDate = DateSerial(2007, 4, 29)
CheckOutDate = DateSerial(2007, 5, 3)
''// -1 asumming the last day is checkout day
numberOfDays = DateDiff("d", CheckInDate, CheckOutDate) - 1
For i = 0 To numberOfDays
CurrDate = DateAdd("d", i, CheckInDate)
TotalRate = TotalRate + CalculateRateForDay(CurrDate)
Next

Use an auxiliary calendar table:
SELECT S1.client_ID, H1.Rate, C1.dt
FROM HotelRates AS H1
INNER JOIN Calendar AS C1
ON C1.dt BETWEEN H1.SartDate AND H1.EndDate
INNER JOIN Stays AS S1
ON C1.dt >= check_in_date AND
C1.dt < check_out_date;

Related

How to query available item leases based on a date range in MySQL?

We have a business that rents out international phone numbers to customers when traveling. When a customer makes an order We want to display to the customer the available phone numbers for his booking dates based on his start_date and end_date and numbers which is not occupied yet.
Since these phone numbers are rented out, I need to select from the table ONLY those numbers that are not rented out yet for dates that would interfere with the current customers dates.
I also don't want to rent out any phone number prior to 7 days after its end date. Meaning, If a customer booked a phone number for 1-1-2020 through 1-20-2020, I don't want this phone number to be booked by another customer before 1-27-2020. I want the phone number to have a 7 day window of being clear.
I have a table with the phone numbers and a table with the orders that is related to the phone numbers table via phone_number_id. The orders table has the current customers start_date and end_date for travel without the phone number id saved yet to it. The orders table also has the start_date and end_date for all other customers dates of travel as well as which phone_number_id was assigned/booked up for their travel dates.
How would the MySQL query look like when trying to select the phone numbers that are available for the current customers dates?
I build below query at the moment
SELECT x.id
, x.area_code
, x.phone_number
, y.start_date
, y.end_date
FROM vir_num_table x
LEFT
JOIN orderitemsdetail_table y
ON y.vn_id = x.id
WHERE y.start_date BETWEEN '2020-01-11' AND '2020-01-18'
OR y.start_date IS NULL
I've build this query but stuck here how can I add end_date logic.
Any help would be appreciated! Thanks in advance.
The way I'd approach the problem would be to look at conceptually, is as a cross product of the set of all phone numbers, along with the reservation timeframe, and then exclude those where there's a conflicting reservation.
A conflict would be an overlap, existing reservation that has a start_date before the end of the proposed reservation AND has an end_date on or after the start of the proposed reservation.
I'd do an anti-join pattern, something like this:
SELECT pn.phone_number
FROM phone_number pn
LEFT
JOIN reservation rs
ON rs.phone_number = pn.phone_number
AND rs.start_dt <= '2019-12-27' + INTERVAL +7 DAY
AND rs.end_dt > '2019-12-20' + INTERVAL -7 DAY
WHERE rs.phone_number IS NULL
That essentially says get all rows from phone number, along with matching rows from reservations (rows that overlap), but then exclude all the rows that had a match, leaving just phone_number rows that did not have a match.
We can make the < test a <= or , subtract 8 days, to tailor the "7 day" window before; we can tweak as we run the query through the test cases,
We can achieve an equivalent result using a NOT EXISTS and a correlated subquery. Some people find this easier to comprehend than the ant-join, but its essentially the same query, doing the same thing, get all rows from phone_number but exclude the rows where there is a matching (overlapping) row in reservation
SELECT pn.phone_number
FROM phone_number pn
WHERE NOT EXISTS
( SELECT 1
FROM reservation rs
WHERE rs.phone_number = pn.phone_number
AND rs.start_dt <= '2019-12-27' + INTERVAL +7 DAY
AND rs.end_dt > '2019-12-20' + INTERVAL -7 DAY
)
There are several questions on StackOverflow about checking for overlap, or no overlap, of date ranges.
See e.g.
How to check if two date ranges overlap in mysql?
PHP/SQL - How can I check if a date user input is in between an existing date range in my database?
MySQL query to select distinct rows based on date range overlapping
EDIT
Based on the SQL added as an edit to the question, I'd do the query like this:
SELECT pn.`id`
, pn.`area_code`
, pn.`phone_number`
FROM `vir_num_table` pn
LEFT
JOIN `orderitemsdetail_table` rs
ON rs.vn_id = pn.id
AND rs.start_date <= '2020-01-18' + INTERVAL +7 DAY
AND rs.end_date > '2020-01-11' + INTERVAL -7 DAY
WHERE rs.vn_id IS NULL
The two "tricky" parts. First is the anti-join, understanding how that works. (An outer join, to return all rows from vir_num_table but exclude any rows that have a matching row in reservations. The second tricky part is checking for the overlap, coming up with the conditions: r.start <= p.end AND r.end >= p.start, then tweaking whether we want to include the equals as an overlap, and tweaking the extra seven days (easiest to me to just subtract the 7 days from the beginning of the proposed reservation)
... now occurs to me like we need to add a guard period of 7 days on the end of the reservation period as well, doh!
Here's a query plus sorting algo to choose the optimal phone number selection for maximum utilization efficiency (i.e. getting as close as possible to exactly 7 days before and after each use).
I set it to give open ends a weight of 9, so that "near perfect" fits (7-8 days before or after) would be selected ahead of open-ended numbers. This will yield a slight efficiency improvement, as open numbers can accommodate any reservation. You can adjust this for your needs. If you set this to 0, for example, it would always select open numbers first.
SELECT ph.phone_number,
COALESCE(
MIN(
IF(res.end_date > res.start_date > '2020-01-18',
NULL, -- ignore before-comparison for reservations starting and ending after date range
DATEDIFF('2020-01-11', res.end_date)
), 9) AS open_days_before,
COALESCE(
MIN(
IF(res.start_date < res.end_date < '2020-01-11',
NULL, -- ignore after-comparison for reservations starting and ending before date range
DATEDIFF(res.start_date, '2020-01-18')
), 9) AS open_days_after
FROM phone_number ph
LEFT JOIN reservation res
ON res.phone_number = ph.phone_number
AND res.end_date >= CURRENT_DATE() - INTERVAL 6 DAY
GROUP BY ph.phone_number
HAVING open_days_before >= 7
AND open_days_after >= 7
ORDER BY open_days_before + open_days_after
LIMIT 1
Edit: updated to add grouping, because I realize this is an aggregate problem.
Edit 2: bug fix, changed MAX to MIN
Edit 3: added res.end_date >= CURRENT_DATE - INTERVAL 6 DAY to ignore past reservations, limiting aggregate data and treating phone number with no reservations between 6 days ago and the beginning of the new order as "open on the front-end"
Edit 4: added IF conditions to eliminate reservations outside the given before-or-after comparison ranges (e.g. comparing reservations after the selected range from influencing the "open days before" number), to prevent negative numbers, except when there's overlap with the selected range.
Based on the info you've added then you shouldn't need to check the start date of phone numbers which have been booked out.
You customer provides you with a start date and an end date.
You only rent out phone numbers 7 days after their last lease ended
All you need to do is fetch back phone numbers which either:
- Are not rented out and therefor aren't in the orderitems table
- OR have an end_date which is 7 days before the new customer's start date.
Here you go:
SELECT
`main_table`.`id`,
`main_table`.`area_code`,
`main_table`.`phone_number`,
`orderitemsdetail_table`.`start_date`,
`orderitemsdetail_table`.`end_date`
FROM
`vir_num_table` AS `main_table`
LEFT JOIN
`orderitemsdetail_table` AS `orderitemsdetail_table` ON main_table.id = orderitemsdetail_table.vn_id
WHERE
(DATE_ADD(orderitemsdetail_table.end_date, INTERVAL 7 DAY) < '<CUSTOMER START DATE>'
AND orderitemsdetail_table.start_date > '<CUSTOMER END DATE>')
OR orderitemsdetail_table.id IS NULL

"neater" way of counting holidays

I have a neat table of holidays showing date and recurrence if such holiday is recurrent (i.e. New Year)
Now, I need to count the number of holidays between two dates. If it was a simple list of dates without info about recurrence (so i.e. it would show all New Years between 2000-01-01 and 2015-01-01) it would be quite easy, i.e. something like
declare #start_Date Date= '2013-01-02',
#end_date Date ='2014-01-02'
SELECT COUNT(CE.name) AS holidays_count
FROM dbo.argo_cal_event AS CE INNER JOIN dbo.argo_cal_event_type AS CET
ON CE.event_type_gkey = CET.gkey
WHERE (CET.name = 'EXEMPT_DAY') AND (CE.name <> 'Sundays')
AND (CE.occ_start BETWEEN #start_Date AND #end_date)
But now we have a neat recurrence, so the query above won't count all the New Years, Christmases etc that have been declared as happening "every year starting from".
I COULD create a table with such list, but I've been wondering, is there any other way?
EDIT: Let me precise what I had in mind: I'd like to count the event normally if it event occurs once (I will assume here that user will have to populate all the irregular holidays as i.e. Easter), but when the recurrence <> Once, then get the occurrence start and count the years between that date and final date.
EDIT2: I think I've got it - for the recurrent holidays I can use
SELECT sum (datediff (year, ce.occ_start, #end_date)) as recurrent_holidays
FROM dbo.argo_cal_event AS CE INNER JOIN dbo.argo_cal_event_type AS CET
ON CE.event_type_gkey = CET.gkey
WHERE (CET.name = 'EXEMPT_DAY') and (CE.repeat_interval ='ANNUALLY')
EDIT3: unfortunately this solution doesn't work (or at least getting quite complicated) if I'd like to count between TWO dates, in which one is taken from another table, i.e if I'd like to count recurring holidays between unit.time_in and getddate() :/
The most straightforward way would be to make them all one-time occurrences.
The table isn't going to be that big that you can't add in every Sunday. It's only 52 or 53 entries per year.
If you do it this way now you can do something like,
select count(*) from events where event_date between start_date and end_date
Done.
The main reason to do it this way, though, is that some of your holidays are tricky to calculate. You'll need to be calculating (or looking up) the dates for Good Friday, Easter Monday, and others. They're based on the phase of the moon.
Why not just calculate them all and make your query really easy?
In the end, it matters a lot less how "neat" your data structures are and a lot more on whether your code does what it's supposed to do, how long it takes to do it, and how much effort it takes to get it working.
You could create a function that searches your holiday table for holidays of each type (weekly, monthly, yearly, one-off, etcetera) or even "Nth/Last/Nth Last Whateverday of Whichevermonth" (which would be a variant on Yearly), and for each holiday, interval by interval, loops through and increments each holiday date and checks if it falls between the StartDate and EndDate dates that the function would need to take as parameters. You could even find an Easter Date calculation algorithm and insert that too.
However, such a function wouldn't be simple or as fast as a simple Select statement - you need to decide whether it is worth expending the time and effort to develop such a function and the performance penalty it could impose on your queries is worth the flexibility it would give you and your users.
Almost anything can be done; you should also ask if it should be done...
One terrible approach:
declare #Holidays as Table ( Name VarChar(16), OccurrenceStart Date, Recurrence VarChar(10) );
insert into #Holidays ( Name, OccurrenceStart, Recurrence ) values
( 'New Year Day', '20000101', 'Annually' ),
( 'Sundays', '20000202', 'Weekly' ),
( 'Labour Day 2014', '20141027', 'Once' );
select * from #Holidays;
declare #Start as Date = '20130102';
declare #End as Date = '20150101';
with DateRange as ( -- All dates from #Start to #End .
select #Start as ADate
union all
select DateAdd( day, 1, DR.ADate )
from DateRange as DR
where DR.ADate < #End ),
Once as ( -- Holidays that occur once within the date range.
select DR.ADate as Holiday
from DateRange as DR inner join
#Holidays as H on H.OccurrenceStart = DR.ADate and H.Recurrence = 'Once' ),
Weekly as ( -- Holidays that occur weekly within the date range, give or take.
select DateAdd( week, case when H.OccurrenceStart < #Start then DateDiff( week, H.OccurrenceStart, #Start ) else 0 end, H.OccurrenceStart ) as Holiday
from #Holidays as H
where H.OccurrenceStart <= #End and H.Recurrence = 'Weekly'
union all
select DateAdd( week, 1, W.Holiday )
from Weekly as W
where DateAdd( week, 1, W.Holiday ) <= #End ),
Annually as ( -- Holidays that occur annually within the date range, give or take.
select DateAdd( year, case when H.OccurrenceStart < #Start then DateDiff( year, H.OccurrenceStart, #Start ) else 0 end, H.OccurrenceStart ) as Holiday
from #Holidays as H
where H.OccurrenceStart <= #End and H.Recurrence = 'Annually'
union all
select DateAdd( year, 1, A.Holiday )
from Annually as A
where DateAdd( year, 1, A.Holiday ) <= #End )
select Count( 42 ) as 'Number of Holidays'
from DateRange as DR inner join
( select Holiday from Once union
select Holiday from Weekly union
select Holiday from Annually ) as H on H.Holiday = DR.ADate
option ( MaxRecursion 0 );
Note that multiple 'hits' on one day, e.g. New Years Day falling on a Sunday, are counted as a single holiday. There are more efficient ways to generate DateRange, e.g. with a numbers table. And the whole thing is hideous.
Ask and ye shall be deceived.

SELECT returning total booking price

I have the following table SEASONS with time periods defining a booking prices for each period day;
ID, STARTDATE, ENDDATE, PRICE
6, 2012-06-01, 2012-06-30, 20
7, 2012-07-01, 2012-07-31, 35
8, 2012-08-01, 2012-08-31, 30
9, 2012-09-01, 2012-09-30, 25
This table defines pricing periods (start and end dates of pricing period with price of booking for each day in that particular pricing period). The question is how to create a query which will return the total price of booking for all days in some given booking period? For example, how to calculate (SELECT?) the total (SUM) booking price for period from 2012-06-10 to 2012-08-20 ?
(Of course one can easily calculate it manually = 21(days in Jun)x20 + 31(days in Jul)x35 + 20(days in Aug)x30 = 2105) How SELECT statement returning that total booking price should look like?
Use DATEDIFF function:
SELECT SUM(DATEDIFF(ENDDATE,STARTDATE) * PRICE) AS TOTAL_PRICE
FROM SEASONS
WHERE STARTDATE <= '2012-06-10' AND ENDDATE >= '2012-08-20'
Also, we can add a check for booking start/end since they fall somewhere in the middle and you don't need to include all the period...
So:
SET #START='2012-06-10';
SET #END='2012-08-20';
SELECT SUM(
DATEDIFF(
IF(YEAR(ENDDATE)=YEAR(#END) AND MONTH(ENDDATE)=MONTH(#END), #END, ENDDATE),
IF(YEAR(STARTDATE)=YEAR(#START) AND MONTH(STARTDATE)=MONTH(#START), #START, STARTDATE)
)
) AS TOTAL_SUM
FROM SEASONS
WHERE STARTDATE <= #END AND ENDDATE >= #START
Just for other readers, as a result from previous answer the final query is:
SET #START='2012-06-10';
SET #END='2012-08-20';
SELECT SUM(
(DATEDIFF(
IF(enddate>=#END,#END,enddate),
IF(startdate<=#START,#START,startdate)
)+1)*price ) AS TOTAL_SUM
FROM seasons WHERE startdate<=#END AND enddate>=#START
1 should be added to date difference in order both starting and ending date to be included in pricing range and, of course, it should be multiplied with price.
#poncha thank you very much, I already created a procedure which loops through all dates in booking period fetching multiple prices and summing them as a solution but I knew there should be a simpler and more efficient solution

two date columns and one date range , typical query?

I have a table
tbl_charge
id hotel_id start_date end_date charge_per_day ( in $)
1 6 2012-02-15 2010-02-15 20
2 6 2012-02-16 2010-02-18 30
4 6 2012-02-20 2010-02-25 50
Note: if any date is not in the table then we set 25$ for each days (i.e. default charge)
now if someone wants to book a hotel from 2012-02-15 to 2012-02-22 , then I want to calculate the total charges for dates
Date : 15+16+17+18+19+20+21+22
Charge : 20+30+30+30+25+50+50+50 = 285$
what i have done so far:
this query returns all rows successfully
SELECT * FROM `tbl_charge` WHERE
start_date BETWEEN '2012-02-15' AND '2012-02-22' OR
end_date BETWEEN '2012-02-15' AND '2012-02-22' OR
( start_date <'2012-02-15' AND end_date > '2012-02-22')
HAVING property_id=6
it returns all necessary rows but how do I sum the charges??
is ther any way to count days between given date range like last row is 20 -25 but i want only upto 22 then it return 3 days and we multiply charges by 3
is it good to create procedure for this or use simple query
I think this will do the trick:
select sum(DayDifference * charge_per_day) +
(RealDayDifference - sum(DayDifference)) * 25 as TotalPerPeriod
from (
select charge_per_day, datediff(
least(end_date, '2012-02-22'),
greatest(start_date, '2012-02-15')) + 1 as DayDifference,
datediff('2012-02-22', '2012-02-15') + 1 as RealDayDifference
from t1
where
((start_date between '2012-02-15' and '2012-02-22') or
(end_date between '2012-02-15' and '2012-02-22') or
(start_date < '2012-02-15' and end_date > '2012-02-22'))
and hotel_id=6
) S1
I've had to solve this same issue previously and it's a fun one, however since then I've learnt some better methods. At the time I believe I created a procedure or function to loop over the requested dates and return a price.
To return the required rows, you can simply select using the upper and lower limits. You can do a datediff within the select criteria to return the number of iterations of each to apply.
If all you are ultimately looking for is a single price I would advise combining this logic into a function
I've assumed a second table, tbh_hotel with id (int PK == hotel_id) and default_charge (int) with row (id=6,default_charge=20)
Further assumptions are that where your dates are "2010" you meant them to be "2012", and that this is for someone that is checking in in the 15th, and checking out on the 22nd (and so needs a hotel for 15th, 16th, 17th, 18th, 19th, 20th, 21st, 7 nights). I will also assume that you have logic in place that prevents the date ranges overlapping, so that there are no 2 rows in tbl_charge which match the date 14th Feb 2012 (for example)
So to get this started, a query to select the applicable rows
SELECT
*
FROM tbl_charge AS c
WHERE
(
c.end_date >= '2012-02-15'
OR
c.start_date < '2012-02-22'
)
This is pretty much what you have already, so now will add in some more fields to get the information for how many days each rule is applied for.
SET #StartDate = '2012-02-15';
SET #EndDate = SUBDATE('2012-02-22',1);
SELECT
c.id,
c.start_date,
c.end_date,
c.charge_per_day,
DATEDIFF(IF(c.end_date>#EndDate,#EndDate,c.end_date),SUBDATE(IF(c.start_date<#StartDate,#StartDate,c.start_date),1)) AS quantityOfThisRate
FROM tbl_charge AS c
WHERE c.end_date >=#StartDate OR c.start_date < #EndDate
I am SUBDATEing the end date, because if you check out on the 22nd, your final checkin date is the 21st. I am SUBDATING the start date on each DATEDIFF because if you are staying on 15th -> 16th, the subdate on END DATE makes this 15th-15th, and so this SUBDATE makes it get 14th-15th to return the correct value of 1. Output now looks a bit like this
id start_date end_date price quantityAtThisRate
1 2012-02-10 2012-02-15 20 1
2 2012-02-16 2012-02-18 30 3
3 2012-02-20 2012-02-29 50 2
So moving on I'll put this into a subquery and combine tbl_hotel to get a default charge
SET #StartDate = '2012-02-15';
SET #EndDate = SUBDATE('2012-02-22',1);
SET #NumberOfNights = DATEDIFF(ADDDATE(#EndDate,1),#StartDate);
SET #HotelID = 6;
SELECT
SUM(specificDates.charge_per_day*specificDates.quantityAtThisRate) AS specificCharges,
#NumberOfNights-SUM(specificDates.quantityAtThisRate) AS daysAtDefault,
h.default_charge * (#NumberOfNights-SUM(specificDates.quantityAtThisRate)) AS defaultCharges
FROM tbl_hotel AS h
INNER JOIN
(
SELECT
c.charge_per_day,
DATEDIFF(IF(c.end_date>#EndDate,#EndDate,c.end_date),SUBDATE(IF(c.start_date<#StartDate,#StartDate,c.start_date),1)) AS quantityAtThisRate
FROM tbl_charge AS c
WHERE (c.end_date >=#StartDate OR c.start_date < #EndDate) AND c.hotel_id = #HotelID
) AS specificDates
WHERE h.id = #HotelID
Realistically a single query will get rather .... complex so I'd settle at a stored procedure relying on the logic above (as if there are no specific rules the above query will return null due to the inner join)
Hope this is of help

Group by day and still show days without rows?

I have a log table with a date field called logTime. I need to show the number of rows within a date range and the number of records per day. The issue is that i still want to show days that do not have records.
Is it possible to do this only with SQL?
Example:
SELECT logTime, COUNT(*) FROM logs WHERE logTime >= '2011-02-01' AND logTime <= '2011-02-04' GROUP BY DATE(logTime);
It returns something like this:
+---------------------+----------+
| logTime | COUNT(*) |
+---------------------+----------+
| 2011-02-01 | 2 |
| 2011-02-02 | 1 |
| 2011-02-04 | 5 |
+---------------------+----------+
3 rows in set (0,00 sec)
I would like to show the day 2011-02-03 too.
MySQL will not invent rows for you, so if the data is not there, they will naturally not be shown.
You can create a calendar table, and join in that,
create table calendar (
day date primary key,
);
Fill this table with dates (easy with a stored procedure, or just some general scripting), up till around 2038 and something else will likely break unitl that becomes a problem.
Your query then becomes e.g.
SELECT logTime, COUNT(*)
FROM calendar cal left join logs l on cal.day = l.logTime
WHERE day >= '2011-02-01' AND day <= '2011-02-04' GROUP BY day;
Now, you could extend the calendar table with other columns that tells you the month,year, week etc. so you can easily produce statistics for other time units. (and purists might argue the calendar table would have an id integer primary key that the logs table references instead of a date)
In order to accomplish this, you need to have a table (or derived table) which contains the dates that you can then join from, using a LEFT JOIN.
SQL operates on the concept of mathematical sets, and if you don't have a set of data, there is nothing to SELECT.
If you want more details, please comment accordingly.
I'm not sure if this is a problem that should be solved by SQL. As others have shown, this requires maintaining a second table that contains the all of the individual dates of a given time span, which must be updated every time that time span grows (which presumably is "always" if that time span is the current time.
Instead, you should use to inspect the results of the query and inject dates as necessary. It's completely dynamic and requires no intermediate table. Since you specified no language, here's pseudo code:
EXECUTE QUERY `SELECT logTime, COUNT(*) FROM logs WHERE logTime >= '2011-02-01' AND logTime <= '2011-02-04' GROUP BY DATE(logTime);`
FOREACH row IN query result
WHILE (date in next row) - (date in this row) > 1 day THEN
CREATE new row with date = `date in this row + 1 day`, count = `0`
INSERT new row IN query result AFTER this row
ADVANCE LOOP INDEX TO new row (`this row` is now the `new row`)
END WHILE
END FOREACH
Or something like that
DECLARE #TOTALCount INT
DECLARE #FromDate DateTime = GetDate() - 5
DECLARE #ToDate DateTime = GetDate()
SET #FromDate = DATEADD(DAY,-1,#FromDate)
Select #TOTALCount= DATEDIFF(DD,#FromDate,#ToDate);
WITH d AS
(
SELECT top (#TOTALCount) AllDays = DATEADD(DAY, ROW_NUMBER()
OVER (ORDER BY object_id), REPLACE(#FromDate,'-',''))
FROM sys.all_objects
)
SELECT AllDays From d