SQL Grabbing hotel room availability between two dates - sql

As the title states, I'm trying to grab all available hotel rooms when a user specifies a check in and check out date. I've made some progress, but I'm struggling to understand the logic behind this process.
Here's what I have:
SELECT r.FLOOR, r.ROOM
FROM BOOKING b, ROOMS r
WHERE TO_DATE('2015-03-28', 'YYYY-MM-DD')
BETWEEN TO_DATE(b.CHECKIN, 'YY-MM-DD') AND TO_DATE(b.CHECKOUT, 'YY-MM-DD')
AND r.ROOMID = b.ROOMID;
This simply returns back all taken rooms on the specified date. (2015-03-28)
How can I change this code to take in two dates, checkin an checkout, while also providing available rooms instead of taken rooms.
Any help is much appreciated!

You can use Oracle's wm_overlaps function, which finds overlapping time spans:
select *
from rooms
where roomid not in
(
select b.room_id
from booking b
where wm_overlaps (
wm_period(b.checkin, b.checkout),
wm_period(
to_date('2014-01-01', 'yyyy-mm-dd'),
to_date('2014-01-05', 'yyyy-mm-dd')
)
) = 1
)
In this query, the rooms have no bookings between the both given parameters.

Try to get list of all rooms and exclude from it booked set, for example
SELECT r.FLOOR, r.ROOM
FROM ROOMS r
EXCEPT
SELECT r.FLOOR, r.ROOM
FROM BOOKING b, ROOMS r
WHERE TO_DATE('2015-03-28', 'YYYY-MM-DD')
BETWEEN TO_DATE(b.CHECKIN, 'YY-MM-DD') AND TO_DATE(b.CHECKOUT, 'YY-MM-DD')
AND r.ROOMID = b.ROOMID;

This might be closer. Substitute the parameters (marked with #) as appropriate:
SELECT r.FLOOR, r.ROOM
FROM ROOMS r
WHERE r.ROOMID NOT IN (
-- exclude rooms where checkin or checkout overlaps with the desired dates
SELECT r.ROOMID
FROM BOOKING b
WHERE (
b.CHECKIN BETWEEN TO_DATE(#CHECKIN, 'YY-MM-DD') AND TO_DATE(#CHECKOUT, 'YY-MM-DD')
OR b.CHECKOUT BETWEEN TO_DATE(#CHECKIN, 'YY-MM-DD') AND TO_DATE(#CHECKOUT, 'YY-MM-DD')
)

Related

Using a date field for matching SQL Query

I'm having a bit of an issue wrapping my head around the logic of this changing dimension. I would like to associate these two tables below. I need to match the Cost - Period fact table to the cost dimension based on the Id and the effective date.
As you can see - if the month and year field is greater than the effective date of its associated Cost dimension, it should adopt that value. Once a new Effective Date is entered into the dimension, it should use that value for any period greater than said date going forward.
EDIT: I apologize for the lack of detail but the Cost Dimension will actually have a unique Index value and the changing fields to reference for the matching would be Resource, Project, Cost. I tried to match the query you provided with my fields, but I'm getting the incorrect output.
FYI: Naming convention change: EngagementId is Id, Resource is ConsultantId, and Project is ProjectId
I've changed the images below and here is my query
,_cte(HoursWorked, HoursBilled, Month, Year, EngagementId, ConsultantId, ConsultantName, ProjectId, ProjectName, ProjectRetainer, RoleId, Role, Rate, ConsultantRetainer, Salary, amount, EffectiveDate)
as
(
select sum(t.Duration), 0, Month(t.StartDate), Year(t.StartDate), t.EngagementId, c.ConsultantId, c.ConsultantName, c.ProjectId, c.ProjectName, c.ProjectRetainer, c.RoleId, c.Role, c.Rate, c.ConsultantRetainer,
c.Salary, 0, c.EffectiveDate
from timesheet t
left join Engagement c on t.EngagementId = c.EngagementId and Month(c.EffectiveDate) = Month(t.EndDate) and Year(c.EffectiveDate) = Year(t.EndDate)
group by Month(t.StartDate), Year(t.StartDate), t.EngagementId, c.ConsultantName, c.ConsultantId, c.ProjectId, c.ProjectName, c.ProjectRetainer, c.RoleId, c.Role, c.Rate, c.ConsultantRetainer,
c.Salary, c.EffectiveDate
)
select * from _cte where EffectiveDate is not null
union
select _cte.HoursWorked, _cte.HoursBilled, _cte.Month, _cte.Year, _cte.EngagementId, _cte.ConsultantId, _cte.ConsultantName, _cte.ProjectId, _Cte.ProjectName, _cte.ProjectRetainer, _cte.RoleId, _cte.Role, sub.Rate, _cte.ConsultantRetainer,_cte.Salary, _cte.amount, sub.EffectiveDate
from _cte
outer apply (
select top 1 EffectiveDate, Rate
from Engagement e
where e.ConsultantId = _cte.ConsultantId and e.ProjectId = _cte.ProjectId and e.RoleId = _cte.RoleId
and Month(e.EffectiveDate) < _cte.Month and Year(e.EffectiveDate) < _cte.Year
order by EffectiveDate desc
) sub
where _cte.EffectiveDate is null
Example:
I'm struggling with writing the query that goes along with this. At first I attempted to partition by greatest date. However, when I executed the join I got the highest effective date for every single period (even those prior to the effective date).
Is this something that can be accomplished in a query or should I be focusing on incremental updates of the destination table so that any effective date / time period in the past is left alone?
Any tips would be great!
Thanks,
Channing
Try this one:
; with _CTE as(
select p.* , c.EffectiveDate, c.Cost
from period p
left join CostDimension c on p.id = c.id and p.Month = DATEPART(month, c.EffectiveDate) and p.year = DATEPART (year, EffectiveDate)
)
select * from _CTE Where EffectiveDate is not null
Union
select _CTE.id, _CTE.Month, _CTE.Year, sub.EffectiveDate, sub.Cost
from _CTE
outer apply (select top 1 EffectiveDate, Cost
from CostDimension as cd
where cd.Id = _CTE.id and cd.EffectiveDate < DATETIMEFROMPARTS(_CTE.Year, _CTE.Month, 1, 0, 0, 0, 0)
order by EffectiveDate desc
) sub
where _Cte.EffectiveDate is null

SQL Query to show order of work orders

First off sorry for the poor subject line.
EDIT: The Query here duplicates OrderNumbers I am needing the query to NOT duplicate OrderNumbers
EDIT: Shortened the question and provided a much cleaner question
I have a table that has a record of all of the work orders that have been performed. there are two types of orders. Installs and Trouble Calls. My query is to find all of the trouble calls that have taken place within 30 days of an install and match that trouble call (TC) to the proper Install (IN). So the Trouble Call date has to happen after the install but no more than 30 days after. Additionally if there are two installs and two trouble calls for the same account all within 30 days and they happen in order the results have to reflect that. The problem I am having is I am getting an Install order matching to two different Trouble Calls (TC) and a Trouble Call(TC) that is matching to two different Installs(IN)
In the example on SQL Fiddle pay close attention to the install order number 1234567810 and the Trouble Call order number 1234567890 and you will see the issue I am having.
http://sqlfiddle.com/#!3/811df/8
select b.accountnumber,
MAX(b.scheduleddate) as OriginalDate,
b.workordernumber as OriginalOrder,
b.jobtype as OriginalType,
MIN(a.scheduleddate) as NewDate,
a.workordernumber as NewOrder,
a.jobtype as NewType
from (
select workordernumber,accountnumber,jobtype,scheduleddate
from workorders
where jobtype = 'TC'
) a join
(
select workordernumber,accountnumber,jobtype,scheduleddate
from workorders
where jobtype = 'IN'
) b
on a.accountnumber = b.accountnumber
group by b.accountnumber,
b.scheduleddate,
b.workordernumber,
b.jobtype,
a.accountnumber,
a.scheduleddate,
a.workordernumber,
a.jobtype
having MIN(a.scheduleddate) > MAX(b.scheduleddate) and
DATEDIFF(day,MAX(b.scheduleddate),MIN(a.scheduleddate)) < 31
Example of what I am looking for the results to look like.
Thank you for any assistance you can provide in setting me on the correct path.
You were actually very close. I realized that what you really want is the MIN() TC date that is greater than each install date for that account number so long as they are 30 days or less apart.
So really you need to group by the install dates from your result set excluding WorkOrderNumbers still. Something like:
SELECT a.AccountNumber, MIN(a.scheduleddate) TCDate, b.scheduleddate INDate
FROM
(
SELECT WorkOrderNumber, ScheduledDate, JobType, AccountNumber
FROM workorders
WHERE JobType = 'TC'
) a
INNER JOIN
(
SELECT WorkOrderNumber, ScheduledDate, JobType, AccountNumber
FROM workorders
WHERE JobType = 'IN'
) b
ON a.AccountNumber = b.AccountNumber
WHERE b.ScheduledDate < a.ScheduledDate
AND DATEDIFF(DAY, b.ScheduledDate, a.ScheduledDate) <= 30
GROUP BY a.AccountNumber, b.AccountNumber, b.ScheduledDate
This takes care of the dates and AccountNumbers, but you still need the WorkOrderNumbers, so I joined the workorders table back twice, once for each type.
NOTE: I assume that each workorder has a unique date for each account number. So, if you have workorder 1 ('TC') for account 1 done on '1/1/2015' and you also have workorder 2 ('TC') for account 1 done on '1/1/2015' then I can't guarantee that you will have the correct WorkOrderNumber in your result set.
My final query looked like this:
SELECT
aggdata.AccountNumber, inst.workordernumber OriginalWorkOrderNumber, inst.JobType OriginalJobType, inst.ScheduledDate OriginalScheduledDate,
tc.WorkOrderNumber NewWorkOrderNumber, tc.JobType NewJobType, tc.ScheduledDate NewScheduledDate
FROM (
SELECT a.AccountNumber, MIN(a.scheduleddate) TCDate, b.scheduleddate INDate
FROM
(
SELECT WorkOrderNumber, ScheduledDate, JobType, AccountNumber
FROM workorders
WHERE JobType = 'TC'
) a
INNER JOIN
(
SELECT WorkOrderNumber, ScheduledDate, JobType, AccountNumber
FROM workorders
WHERE JobType = 'IN'
) b
ON a.AccountNumber = b.AccountNumber
WHERE b.ScheduledDate < a.ScheduledDate
AND DATEDIFF(DAY, b.ScheduledDate, a.ScheduledDate) <= 30
GROUP BY a.AccountNumber, b.AccountNumber, b.ScheduledDate
) aggdata
LEFT OUTER JOIN workorders tc
ON aggdata.TCDate = tc.ScheduledDate
AND aggdata.AccountNumber = tc.AccountNumber
AND tc.JobType = 'TC'
LEFT OUTER JOIN workorders inst
ON aggdata.INDate = inst.ScheduledDate
AND aggdata.AccountNumber = inst.AccountNumber
AND inst.JobType = 'IN'
select in1.accountnumber,
in1.scheduleddate as OriginalDate,
in1.workordernumber as OriginalOrder,
'IN' as OriginalType,
tc.scheduleddate as NewDate,
tc.workordernumber as NewOrder,
'TC' as NewType
from
workorders in1
out apply (Select min(in2.scheduleddate) as scheduleddate from workorders in2 Where in2.jobtype = 'IN' and in1.accountnumber=in2.accountnumber and in2.scheduleddate>in1.scheduleddate) ins
join workorders tc on tc.jobtype = 'TC' and tc.accountnumber=in1.accountnumber and tc.scheduleddate>in1.scheduleddate and (ins.scheduleddate is null or tc.scheduleddate<ins.scheduleddate) and DATEDIFF(day,in1.scheduleddate,tc.scheduleddate) < 31
Where in1.jobtype = 'IN'

SQL Count avabiles rooms betwen interval

I'm working on a hotel booking module, and after a few days I'm stack.
Tables
+++++++++++++++++++++++++++++++
rooms bookings
======== =============
room_id b_id
avabile_rooms b_room_id
check_in
check_out
b_rooms
== Where ==
avabile_rooms - the number of rooms avabile
b_rooms = the number of rooms with this b_id booked
Values needed
++++++++++++++++++++++++++++++++
room_id and number_of_rooms_avabile foreach avabile room in interval A(check_in) - B(check_out)
Current Query
++++++++++++++++++++++++++++++++
SELECT r.* FROM rooms AS r WHERE r.room_id NOT IN
(
SELECT b.b_room_id FROM bookings AS b
WHERE (b.check_out >= ? AND b.check_in <= ?)
OR (b.check_out <= ? AND b.check_in >= ?)
)
Now I get the avabile rooms without taking into account the avabile_rooms/b_rooms
The Unkown Query (I thik It needs to be something like this)
++++++++++++++++++++++++++++++++
SELECT r.* FROM rooms AS r WHERE r.room_id IS IN
(
SELECT b.b_room_id FROM bookings AS b
OR b.b_id IS NULL
OR (b.check_out >= ? AND b.check_in <= ?)
OR (b.check_out <= ? AND b.check_in >= ?)
)
I'm not figured out how to get the room_id/number_of_rooms_avabile
P.S.: I did a deep search but did not find a solution that takes into account the number of rooms.
Thanks.
Here is how I would do it:
select rt.room_type_id, available = rt.available_rooms - isnull(sum(b.b_rooms), 0)
from room_types rt
left join bookings b
on rt.room_type_id = b.b_room_type_id
and b.check_in <= #end_date
and b.check_out >= #start_date
group by rt.room_type_id, rt.available_rooms
I renamed your rooms table to room_types as discussed in the comments, and renamed columns as appropriate.
This will take the number of available rooms and subtract all bookings that have any overlap with the selected date range, which I believe is what you want.
Depending on your business rules, you may want this instead:
where b.check_in < #end_date
and b.check_out > #start_date

How to check newer than or equal to a date on access SQL

Currently I have two tables, using Access 2007
TimeSheet(empID, TimeSheet, hours)
and
Rates(empID,Rate,PromotionDate)
How do I select the correct billing rates of employee based on their date of promotion?
For example, I have
Rates(001,10,#01/01/2013#)
Rates(001,15,#01/05/2013#)
Rates(002,10,#01/01/2013#)
and
Timesheet(001,#02/01/2013#,5)
Timesheet(001,#02/05/2013#,5)
Timesheet(002,#02/01/2013#,7)
In this case, I want to show that if empID 001 submited a time sheet at 02/01/2013 it would be billed with $10/hr
, but his timesheets starting at May 1st would be billed with $15/hr
My query right now is
SELECT t.empID , t.timesheet, r.hours ,
(SELECT rate FROM rate WHERE t.timeSheet >= r.promotionDate) AS RateBilled
FROM rate AS r , timesheet AS t
WHERE r.empid = t.empid
When ran, it shows a message of “At most one record can be returned by this subquery”
Any help would be appreciated, thanks.
Edit:
I have some strange output using the sql
SELECT t.empID, t.timesheet, r.rate AS RateBilled
FROM Rates AS r, timesheet AS t
WHERE r.empid=t.empid And t.timeSheet>=r.promotionDate
GROUP BY t.empID, t.timesheet, r.rate, r.promotionDate
HAVING r.promotionDate=MAX(r.promotionDate);
as you can see the output table ratebilled for empID 1 is switching back and forth from 10 to 15, even though past May 01, it should all be using 15 ,
any help is appreciated, thanks.
The select subquery you have setup potentially returns multiple values where only one should be returned. Consider the case where there may be two promotions and a recent timesheet, then the select will return two values because on both occasions the timesheet is newer than the promotion.
Try using the following as your subquery:
SELECT TOP 1 rate FROM rate
WHERE t.timeSheet >= r.promotionDate
ORDER BY r.promotionDate DESC
N.B. I don't think the above is terribly efficient. Instead try something like
SELECT t.empID , t.timesheet, r.hours , r.rate AS RateBilled
FROM rate AS r , timesheet AS t
WHERE r.empid = t.empid AND t.timeSheet >= r.promotionDate
GROUP BY t.empID, t.timesheet
HAVING r.promotionDate = MAX( r.promotionDate);

Detecting duplicates which fall outside of a date interval

I searched in SO but couldnt find a direct answer.
There are patients, hospitals, medical branches(ER,urology,orthopedics,internal disease etc), medical operation codes (examination,surgical operation, MRI, ultrasound or sth. else) and patient visiting dates.
Patient visits doctor, doctor prescribes medicine and asks to come again for control check.
If patient returns after 10 days, (s)he has to pay another examination fee to the same hospital. Hospitals may appoint a date after 10 days telling there are no available slots in following 10 days, in order to get the examination fee.
Table structure is like:
Patient id.no Hospital Medical Branch Medical Op. Code Date
1 H1 M0 P1 01/05/2011
5 H1 M1 P9 03/05/2011
3 H2 M0 P2 09/05/2011
1 H1 M0 P1 14/05/2011
3 H1 M0 P2 20/05/2011
5 H1 M2 P9 25/05/2011
1 H1 M0 P3 26/05/2011
Here, visiting patients no. 3 and 5 does not constitute a problem as patient no. 3 visits different hospitals and patient no.5 visits different medical branches. They would pay the examination fee even if they visited within 10 days.
Patient no.1, however, visits same hospital, same branch and is subject to same process (P1: examination) on 01/05 and 14/05.
26/05 doesnt count because it is not medical examination.
What I want to flag is same patient, same hospital, same branch and same medical operation code (that is specifically medical examination : P1 ), with date range more than 10 days.
The format of resulting table:
HOSPITAL TOTAL NUM. of PATIENTS NUM. of PATIENTS OUT OF DATE RANGE
H1 x a
H2 y b
H3 z c
Thanks.
Once again, it's analytic functions to the rescue.
This query uses the LAG() function to link a record in YOUR_TABLE with the previous (defined by DATE) matching record (defined by PATIENT_ID) in the table.
select hospital_id
, count(*) as total_num_of_patients
, sum (out_of_range) as num_of_patients_out_of_range
from (
select patient_id
, hospital_id
, case
when hospital_id_1 = hospital_id_0
and visit_1 > visit_0 + 10
and med_op_code_1 = med_op_code_0
then 1
else 0
end as out_of_range
from (
select patient_id
, hospital_id as hospital_id_1
, date as visit_1
, med_op_code as med_op_code_1
, lag (date) over (partition by patient_id order by date) as visit_0
, lag (hopital_id) over (partition by patient_id order by date) as hopital_id_0
, lag (med_op_code) over (partition by patient_id order by date) as med_op_code_0
from your_table
where med_op_code = 'P1'
)
)
group by hospital_id
/
Caveat: I haven't tested this code, so it may contain syntax errors. I will check it the next time I can access an Oracle database.
This is a little rough, as I haven't got an Oracle DB to hand, but the key feature is the same: the analytical function LAG(). Along with its companion function, LEAD(), they're great for helping to deal with things like periods of activity.
Here's my attempt at the code:
select n.hospital, COUNT(n.patient_id) as patients_out_of_date_range
from (
select *
from (
select d.*, lag(date, 1) over (partition by d.patient_id, d.hospital, d.medical_branch, d.medical_op_code order by d.date) as prev_date
from datatable d inner join
(
select d.patient_id, d.hospital, d.medical_branch, d.medical_op_code
from datatable d
where d.medical_op_code = 'P1'
group by d.patient_id, d.hospital, d.medical_branch, d.medical_op_code
having COUNT(d.date) > 1
) t on d.patient_id = t.patient_id and d.hospital = t.hospital and d.medical_branch = t.medical_branch and d.medical_op_code = t.medical_op_code
) m
where date - prev_date > 10
) n
group by n.hospital
Like I say, this isn't tested, but it should at least get you started in the right direction.
Some references:
http://www.adp-gmbh.ch/ora/sql/analytical/lag.html
http://www.oracle-base.com/articles/misc/LagLeadAnalyticFunctions.php
I think this is what you're trying for:
WITH Patient_Visits (Patient_Id, Hospital_Id, Branch_Id, Visit_Date, Visit_Order) as (
SELECT Patient_Id, Hospital_Id, BranchId, Visit_Date,
ROW_NUMBER() OVER(PARTITION BY Patient_ID, Hospital_Id, Branch_Id,
ORDER_BY Patient_Id, Hospital_Id, Branch_Id, Visit_Date)
FROM Hospital_Visits
WHERE Procedure_Id = 'P1'),
Hospital_Recent_Visits (Hospital_Id, Recent_Visitor_Count) as (
SELECT a.Hospital_Id, COUNT(DISTINCT a.Patient_Id)
FROM Patient_Visits as a
JOIN Patient_Visits as b
ON b.Hospital_Id = a.Hospital_Id
AND b.Branch_Id = a.Branch_Id
AND b.Patient_Id = a.Patient_Id
AND b.Visit_Order = a.Visit_Order - 1
AND b.Visit_Date + 10 > a.Visit_Date
GROUP BY a.Hospital_Id, a.Patient_Id),
Hospital_Patient_Count (Hospital_Id, Patient_Count) as (
SELECT Hospital_Id, COUNT(DISTINCT Patient_Id)
FROM Hospital_Visits
GROUP BY Hospital_Id, Patient_Id)
SELECT a.Hospital_Id, b.Patient_Count, c.Recent_Visitor_Count
FROM Hospitals as a
LEFT JOIN Hospital_Patient_Count as b
ON b.Hospital_Id = a.Hospital_Id
LEFT JOIN Hospital_Recent_Visits as c
ON c.Hospital_id = a.Hospital_Id
Please note that this was written and tested against a DB2 system. I think Oracle databases have the relevant functionality, so the query should still work as written. However, DB2 appears to lack some of the OLAP functions Oracle has (my version, at least), which could be useful in knocking out some of the CTEs.