SQL Scheduling - Select All Rooms Available for Given Date Range - sql

I'm using Microsoft's idea for storing resource and booking information. In short, resources, such as a hotel room, do not have date records and booking records have a BeginDate and EndDate.
I'm trying to retrieve room availability information using MS's queries but something tells me that MS's queries leave much to be desired. Here's the MS article I'm referring to: http://support.microsoft.com/kb/245074
How can I retrieve available rooms for a given date range? Here's my query that returns a simple list of bookings:
SELECT r.RoomID, b.BeginDate, b.EndDate
FROM tblRoom as r INNER JOIN tblBooking b ON r.RoomID = b.AssignedRoomID;
But I'm still baffled as to how I can get a list of available rooms for a given date range?
I am using Microsoft Access but I'd like to keep my queries DBMS agnostic, as much as possible. While this isn't really my question, if you feel that the data model I'm using is unsound, please say so, as I am willing to consider a better way of storing my data.
Edit1:
I failed to mention that I don't like MS's queries for two reasons. First of all, I'm really confused about the 3 different OR operators in the WHERE clause. Are those really necessary? Secondly, I don't like the idea of saving a query and using it as a table although I'm willing to do that if it gets the job done, which in this case I believe it does.
Edit2:
This is the solution I've landed on using the excellent answer given here. This is MS Access SQL dialect (forgive me):
SELECT * FROM tblRoom AS r
WHERE RoomID NOT IN
(SELECT AssignedRoomID as RoomID From tblBooking
WHERE assignedroomid IS NOT NULL AND assignedroomid = r.roomid AND
(BeginDate < #BookingInquiryEndDate AND EndDate > #BookingInquiryBeginDate)
)

You want all the rooms which do not have a booking in that date range, i.e.,
If your sql engine does subqueries...
Select * From Rooms r
where not exists
(Select * From Bookings
Where room = r.room
And startBooking < #endRange
And endBooking > #startRange)
HIK, to understand the need for the room = r.room clause try these two queries
Query One (with room = r.room clause)
Select r.*,
Case Where Exists
(Select * From Bookings
Where room = r.room
And startBooking < #endRange
And endBooking > #startRange)
Then 'Y' Else 'N' End HasBooking
From Rooms r
Query Two(without room = r.room clause)
Select r.*,
Case Where Exists
(Select * From Bookings
Where startBooking < #endRange
And endBooking > #startRange)
Then 'Y' Else 'N' End HasBooking
From Rooms r
Notice the first one returns different values in HasBooking for each row of the output, because the subquery is 'Correleated' with the outer query... it is run over and over agaio, once for each outer query results row.
The second one is the same value for all rows... It is only executed once, because nothing in it is dependant on which row of the outer query it is being generated for.

Related

Non null value associated with least value in other column

My data is like this:
Desired output:
I have tried using following SQL:
CASE
WHEN (MINDAY_DIFF > 0) AND (MINDAY_DIFF IS NOT NULL)
THEN FIRST_VALUE(BP_MED) OVER (PARTITION BY ID ORDER BY MINDAY_DIFF ASC)
END AS DRUG
This returns NULL.
I also tried
CASE
WHEN (MINDAY_DIFF > 0)
THEN BP_MED
ELSE NULL
END AS DRUG
It returns both non-null values of BP_MED.
I also tried NVL but that didn't work either.
Since it is in Netezza. There are fewer solutions online. Please help.
The concept here is the following working inside out:
We didn't always have analytics to make things easier: So not knowing Netezza I took a more... antiquated approach. There may be better/more efficient ways; which I would look for given a place to play around with; but I think this would work in most any RDBMS as I tried to avoid any RDBMS Specific aspects unless we're dealing with pre left join supported RDBMS.
Find the Min ID and the day difference for that ID in a result set (MinAndID)
LEFT Join back to the baseSet to get all possible values specifically to get BP_Med
Then Join back to base Table to ensure we get ALL records and then populate only the BP_Med which links to the minDay_Diff. Since it's a left join only 1 record per ID should return.
UNTESTED:
SELECT A.*, DesiredDrug.BP_MED
FROM TABLE A
LEFT JOIN (SELECT ID, Fill_Date, BP_MED, MinDay_Diff
FROM TABLE BaseSet
INNER JOIN (SELECT MIN(MINDAY_DIFF) MDD, ID
FROM TABLE
WHERE ID is not null
GROUP BY ID) MinAndID
on BaseSet.ID = MinAndID.ID
and BaseSet.MinDay_Diff = MinAndID.MDD) DesiredDrug
on A.ID = DesiredDrug
and A.Fill_Date = DesiredDrug.Fill_Date
and A.BP_Med= DesiredDrug.BP_Med
and A.MinDay_Diff = DesiredDrug.MinDay_Diff

Slow T-SQL query with datediff function

I have a query which runs fast when the date clause "and datediff(day,con2.DT_DateIncluded),'2017-01-01')<=0" in the code below isn't used in the query, but runs slowly when it is included. Though it runs fast when I run just the part "select top 2 ID_Contact...", even including the date clause. I have this query on a classic ASP application, and it can't be converted in to a stored procedure (project scope reasons). Can you help me find a way to improve the performance of the full query just by changing the query code?
select distinct top 10
ID_Contact, NO_CodCompany
from
tblContacts con1
where
ID_Contact in (select top 2 ID_Contact
from tblContacts con2
inner join tblCompanies cp on con2.NO_CodCompany = cp.ID_Company
where con2.NO_CodCompany = con1.NO_CodCompany
and datediff(day, con2.DT_DateIncluded), '2017-01-01') <= 0)
Instead of `DATEDIFF() < 0' try using:
and con2.DT_DateIncluded <= '2017-01-01'
Also, ensure that there is an index on the `DT_DateIncluded' column.
The reason DATEDIFF() runs slow is that using it takes a bit of time to perform the calculation, the query optimizer is (probably) ending up running it for the entire table, and there is (probably) no index to help it select the required rows.
When you remove that clause the query runs faster, but that is probably helped along by the fact that you're only selecting the first two rows in the inner query and ten rows in the outer query, allowing a table scan to be performant enough.
This is essentially your query:
This is your query:
select distinct top 10 ID_Contact, NO_CodCompany
from tblContacts con1
where ID_Contact in (select top 2 ID_Contact
from tblContacts con2 inner join
tblCompanies cp
on con2.NO_CodCompany = cp.ID_Company
where con2.NO_CodCompany = con1.NO_CodCompany and
datediff(day, con2.DT_DateIncluded), '2017-01-01') <= 0
);
My first suggestion is to change the datediff() to a simple date comparison:
select distinct top 10 ID_Contact, NO_CodCompany
from tblContacts con1
where ID_Contact in (select top 2 ID_Contact
from tblContacts con2 inner join
tblCompanies cp
on con2.NO_CodCompany = cp.ID_Company
where con2.NO_CodCompany = con1.NO_CodCompany and
con2.DT_DateIncluded < '2017-01-02'
);
Then, I would remove the JOIN in the subquery. I'm not 100% sure this is exactly equivalent, because that might depend on nuances in the data:
select distinct top 10 ID_Contact, NO_CodCompany
from tblContacts con1
where con1.ID_Contact in (select top 2 con2.ID_Contact
from tblCompanies cp
where con1.NO_CodCompany = cp.ID_Company and
con1.DT_DateIncluded < '2017-01-02'
);
Then, if you can remove the select distinct in the outermost query, you should do that.
Try this instead:
con2.DT_DateIncluded < '20170102'
It's better because it still allows the server to make use of any indexes on the DT_DateIncluded column. Currently, this is not possible. Even worse, the query is probably having to run that DATEDIFF() function on every record in the table.
Note that this is equivalent to what you posted, even if it might not match what you intended. I suspect con2.DT_DateIncluded < '20170101' is closer to what you really meant.
I also suspect you could do this either without the 2nd instance of tblContacts or with a windowing function to get much better results, or at least by using JOIN instead of IN to filter the results.
Finally, for historical reasons, when entering a date-only value, you should use the unseparated date format as described here:
The ultimate guide to the datetime datatypes
For date/time values, you can still use the separated yyyy-mm-dd hh:mm:ss you're used to, but if you only have the date part, yyyymmdd is better.
Based on this comment:
My goal with this query is to obtain contacts from companies but limited to "n" contacts per company
You should look into the APPLY operator. Unfortunately, it's still not clear to me how everything fits together, but I will least provide a demonstration using the APPLY operator to show two contacts per company that you can use as a starting point:
SELECT TOP 10 ct.ID_Contact, ct.NO_CodCompany
FROM tblCompanies cp
CROSS APPLY (
SELECT TOP 2 ID_Contact, NO_CodCompany
FROM tblContacs
WHERE NO_CodCompany = cp.ID_Company
AND DT_DateIncluded < '20170102'
ORDER BY DT_DateIncluded DESC
) ct
APPLY works kind of like a JOIN on a nested SELECT query, where there is no ON clause; the join conditional is instead included as part of the WHERE clause in the nested SELECT statement.
Note the use of CROSS. This will exclude companies that have no contacts at all. If you want to include those companies, change it to OUTER.
You should also look at what indexes you have defined. A single index on the tblContacts table that looks at NO_CodCompany and DT_DateIncluded (in that order!) might work wonders for this query, especially if it also has ID_Contact in the INCLUDES clause. Then you could complete the tblContacts portion of the query entirely from the index.

Need to make SQL subquery more efficient

I have a table that contains all the pupils.
I need to look through my registered table and find all students and see what their current status is.
If it's reg = y then include this in the search, however student may change from y to n so I need it to be the most recent using start_date to determine the most recent reg status.
The next step is that if n, then don't pass it through. However if latest reg is = y then search the pupil table, using pupilnumber; if that pupil number is in the pupils table then add to count.
Select Count(*)
From Pupils Partition(Pupils_01)
Where Pupilnumber in (Select t1.pupilnumber
From registered t1
Where T1.Start_Date = (Select Max(T2.Start_Date)
From registered T2
Where T2.Pupilnumber = T1.Pupilnumber)
And T1.reg = 'N');
This query works, but it is very slow as there are several records in the pupils table.
Just wondering if there is any way of making it more efficient
Worrying about query performance but not indexing your tables is, well, looking for a kind word here... ummm... daft. That's the whole point of indexes. Any variation on the query is going to be much slower than it needs to be.
I'd guess that using analytic functions would be the most efficient approach since it avoids the need to hit the table twice.
SELECT COUNT(*)
FROM( SELECT pupilnumber,
startDate,
reg,
rank() over (partition by pupilnumber order by startDate desc) rnk
FROM registered )
WHERE rnk = 1
AND reg = 'Y'
You can look execution plan for this query. It will show you high cost operations. If you see table scan in execution plan you should index them. Also you can try "exists" instead of "in".
This query MIGHT be more efficient for you and hope at a minimum you have indexes per "pupilnumber" in the respective tables.
To clarify what I am doing, the first inner query is a join between the registered table and the pupil which pre-qualifies that they DO Exist in the pupil table... You can always re-add the "partition" reference if that helps. From that, it is grabbing both the pupil AND their max date so it is not doing a correlated subquery for every student... get all students and their max date first...
THEN, join that result to the registration table... again by the pupil AND the max date being the same and qualify the final registration status as YES. This should give you the count you need.
select
count(*) as RegisteredPupils
from
( select
t2.pupilnumber,
max( t2.Start_Date ) as MostRecentReg
from
registered t2
join Pupils p
on t2.pupilnumber = p.pupilnumber
group by
t2.pupilnumber ) as MaxPerPupil
JOIN registered t1
on MaxPerPupil.pupilNumber = t1.pupilNumber
AND MaxPerPupil.MostRecentRec = t1.Start_Date
AND t1.Reg = 'Y'
Note: If you have multiple records in the registration table, such as a person taking multiple classes registered on the same date, then you COULD get a false count. If that might be the case, you could change from
COUNT(*)
to
COUNT( DISTINCT T1.PupilNumber )

SQL statement only select items that appear less than twice

I am currently working on a database which stores information which allows users to make reservations at a restaurant.
I am trying create a SQL statement which returns the times which appear less than twice in the reservations table.
At the moment I have this but this only returns the times which do not appear in the reservations table at all.
SELECT *
FROM TIME
WHERE
TIME NOT IN (
SELECT reservation.a_time
FROM
RESERVATION
JOIN TIME ON
reservation.a_time = time.time
WHERE
reservation.a_date = :the_date
)
ORDER BY time;
The above statement returns all times which are not in the reservations table. However how would I return all times which appear in the reservations table including those that appear once but not those that appear twice?
Thanks
select
*
from
TIME t
where
(select
count(r.a_time)
from
RESERVATION r
where
r.a_time = t.time and
r.a_date = :the_date) < 2
or
select
t.time /* and maybe other field, which you need to add to Group By as well */
from
TIME t
left join RESERVATION r on r.a_time = t.time and r.a_date = :the_date
group by
t.time
having
count(t.time) < 2
I prefer the first, because it is cleaner, more clear and can be expanded easier, despite the subselect.
If the only thing you care about is returning reservation.a_times that appear EXACTLY once, then this will do it:
SELECT reservation.a_time
FROM RESERVATION
GROUP BY reservation.a_time
HAVING COUNT(*) = 1;
This will group all RESERVATION entries by time, and only return the groups with exactly one member.

SQL JOIN limit results to rows where specific value does not exist

I am joining two tables using SQL. I'm joining a table which contains charter flight information and a table which contains the crew assigned. In my results, I only want to display the rows that only have a value of "Pilot" in the the crew table and not "Copilot" or both.
SELECT * FROM TABLE_A JOIN TABLE_B ON (TABLE_A.Value = TABLE_B.Value) WHERE TABLE_A.OtherValue = 'Pilot'
This is off the top of my head, so some syntax may be off. The main point is the WHERE clause. You can specify the value that you are looking for in the column (in your case you are looking for Pilot).
EDIT: To prevent a value you can do something like WHERE TABLE.VALUE != 'Copilot' != may need to be written as <> depending on the what SQL it is.
EDIT2: My SQL-Server is throwing a hissy and not connecting, so this is also entirely off the top of my head and I think it's a bit of a hack-job, but I think it'll do the job. :)
SELECT [CHARTER].*, COUNT(*) as Tally FROM [CHARTER] JOIN [CREW] ON ([CHARTER].[CHAR_TRIP] = [CREW].[CHAR_TRIP]) WHERE [CREW].[CREW_JOB] = 'PILOT' OR [CREW].[CREW_JOB] = 'COPILOT' GROUP BY [CHARTER].* HAVING Tally = 1
This assume that all flights have a pilot, but not all flights have a co-pilot. To get the exact display you want, you might have to use it as a sub-query (to remove the Tally column).
SELECT *
FROM charter ch
JOIN crew cr ON ch.char_trip = cr.char_trip
WHERE NOT EXISTS(SELECT *
FROM crew cr2
WHERE cr2.char_trip = ch.char_trip
AND cr2.crew_job != 'PILOT')
I think that should do the trick. The join to the crew table in line 3 is optional, and only if you need results from that table. The NOT EXISTS anti-join is what evaluates all crew for a given trip and checks for any that are not pilots.
You should really help us out here with the schema for us to provide you with a decent query. I think the most important thing here is how do you determine who is a pilot and/or copilot and how do you relate each person to the flight.
I think something like this might help:
SELECT * FROM Charter C
INNER JOIN Crew ON (Charter.CHAR_TRIP = Crew.CHAR_TRIP)
WHERE Crew.Crew_Job = 'PILOT' AND (SELECT COUNT(*) FROM Charter
INNER JOIN Crew ON (Charter.CHAR_TRIP = Crew.CHAR_TRIP)
WHERE Crew.Crew_Job = 'CoPilot'
AND Charter.Chart_Trip = C.ChartTrip) = 0
Although this might not be the cleanest solution.. it should do the work.