Find booking overlaps to check dates availability - mysql

i asking your help to write an query for checking the availability of an room inside MySql.
Currently i have this kind of table:
ROOM | FROM | TO
-----------------------------
101 | 2014-08-09 | 2014-08-14
102 | 2014-08-09 | 2014-08-14
... ... ...
So i have the room 101 booked from 09-08-2014 to 14-08-2014, my query to check availability is look like =
SELECT order_id FROM booking
WHERE `ROOM` = '101'
AND (`FROM` BETWEEN '2014-08-08' AND '2014-08-20')
AND (`TO` BETWEEN '2014-08-08' AND '2014-08-20')
In the above example i will check the availability in the dates between
What i trying to archive is this
Order --------09++++++++++13--------------
Check1 -----08+++++++++++++++++++++++++17-- Not availble
Check2 -----------------12+++++++++++++17-- Not availble
Check3 -----------10----------------------- Not availble
Check4 -----------10+11-------------------- Not availble
Check5 -----------------------14+++++++17-- Available
Check6 --07++++09-------------------------- Not availble
Check7 --------------------------15-------- Availble
SCALE 6-07-08-09-10-11-12-13-14-15-16-17-18-19...
I must check if the room is available. So if i get some result out of that query that means that the room is already booked... if i get nothing just the opposite...

First, let's generalize an algorithm for how to check for an overlap between intervals [a,b] and [c,d]. Note the square braces on those intervals, which means an inclusive interval. We can use this logic to check for an interval overlap:
a <= d and b >= c
If that condition is true, then we have an overlap.
So to apply this algorithm to SQL, we could do something like this:
a = 2014-08-08
b = 2014-08-20
c = FROM
d = TO
SELECT order_id FROM booking
WHERE NOT EXISTS (
SELECT * FROM booking
WHERE ROOM = '101'
AND '2014-08-08' <= `TO`
AND '2014-08-20' >= `FROM`
)
AND ROOM = '101'
The other problem with your approach is that you are checking to see if a room is available, and the assumption here is that if the room is available, then you will book it with another SQL statement. This is a problematic approach, because there is the possibility that you could double book a room. Consider the possibility that two processes check for room availability at the same (or close to the same) time. Or another example would be if this code were part of a transaction that hadn't been committed yet. The other process wouldn't see your committed result, and thus, would double book the room.
To remedy this flawed approach, we need to lock the room row before we check for its availability. Assuming you have some other table called ROOM, you could lock the row using a 'FOR UPDATE' statement:
SELECT * FROM `ROOM` WHERE ROOM = '101' FOR UPDATE
The "FOR UPDATE" will lock that room row, which will prevent another process from checking that room for availability until your transaction is finished. After you lock the row, you could run your overlap check. Thus, you eliminate the double booking problem.
You can read more about 'FOR UPDATE' here.

If you want to check whether room is available for the whole period, look for existing bookings that overlap with period in question:
SELECT order_id FROM booking
WHERE `ROOM` = '101'
AND `FROM` <= '2014-08-20'
AND `TO` >= 2014-08-08'
If query returns rows, you have a reservation conflict and room is not available.

To test whether a proposed room booking conflicts with (overlaps) an existing one, you want something like this:
select count(*) as num_conflicts
from booking
where room = ?proposed_room
and (from <= ?proposed_to)
and (to >= ?proposed_from);
That counts a conflict when any day of the proposed booking is already assigned to another booking.

Related

How to capture date of event X and then capture the date of an event that happens after event X

So I am working with event data.
I need to identify when "X happens", store that data in a column and then identify when "in-production" happens.
Now, I just want the first "in-production" that shows up after "X happens" I do not care about the previous ones.
Note: Between "X happens" and "in-production" happens multiple states can exist.
What have I tried: case whens, self joins, with tables... nothing to my avail.
Any help is much appreciated, thanks a lot friends!
TblEvents
=========
EventID OrderID EventDate Status
1 2 01/02/2011 in-production
2 2 02/02/2011 pending
3 2 03/02/2011 on-hold
4 2 03/02/2011 stuck
5 2 03/02/2011 *X happens*
6 2 04/02/2011 pending
7 2 05/02/2011 *in-production*
Output table:
date of event X | date of "in-production"
03/02/2011 05/02/2011
Try this:
SELECT
OuterTblEvents.EventDate AS 'date of event X',
(
SELECT EventDate
FROM TblEvents
WHERE
TblEvents.Status = 'in-production'
AND TblEvents.EventID > OuterTblEvents.EventID
ORDER BY TblEvents.EventID
LIMIT 1
) AS 'date of "in-production"'
FROM TblEvents OuterTblEvents
WHERE OuterTblEvents.Status = 'X happens'
Though note that if the table is large and/or you run this often - performance may suffer, because the inner query is invoked once per each row that is found in the outer query.
Also, a composite index on Status, EventID should make both the outer and inner queries more performant. The outer query would use only the Status part of the index, and the inner query would use both Status and EventID. Note that the order of the columns in this composite index matters.

sql get all available people that are not booked on a particular date and time

I'm struggling with an SQL query.
I am building a booking system for a ski resort and in my database I have instructors and sessions. A session can have an instructor, and it has a date and startTime and endTime.
In order to add a session, I want to get all available instructors for a chosen time and date. In other words, all instructors who don't have a booking on that date and at that time.
Table example:
e.g
instructors: i1, i2, i3, i4, i5, i6, i7, i8
sessions:
Instructor | date | start | end |
**i1** **2017-05-03** **14:30:00** **15:30:00**
**i2** **2017-05-03** **14:30:00** **15:30:00**
**i3** **2017-10-03** **10:30:00** **11:30:00**
**i4** **2017-05-03** **10:30:00** **11:30:00**
**i1** **2017-11-03** **14:30:00** **15:30:00**
Then for input date='2017-05-03' and start='14:30' and end='15'30' i want to get
i3,i4,i5,i6,i7,i8
Figured out that I need to left join session to instructors, group by instructor id and then eliminate those ids that have a field in the group with the selected
inputs. However, for the GROUP BY clause, i have to use an aggregate function and i don't know which one could apply here.
SirWinning's self-answer looks like it should work, but my version below removes some parts which weren't required.
select *
from instructor
where id not in
(select instructorid
from Session
where date='2017-03-19' and starttime<='15:30:00' and endtime>='14:30:00')
This code will find any instructors who aren't booked for a session which overlaps the 14:30-15:30 window on the relevant date.
If that's what's wanted, then you're good to go. Of course it doesn't follow that the instructor is "really available". There could be other things which affect their availabilty (working hours, annual leave etc), so you'll need to ensure that there are things in place to handle such things.
Note also, that this code will prevent an instructor appearing available for "back to back" bookings. If you want to allow a booking to start at 14:30 when another one ends at that time, you'll need to change the <= and >= to < and >.
using not exists()
select *
from instructors i
where not exists (
select 1
from sessions s
where s.instructor = i.id
and s.date = '2017-05-03'
and s.start = '14:30'
and s.end = '15:30'
)
So I tried this query and apparently it works(at least for my test case)
Can anybody take a look and tell me if it looks correct?
select *
from instructor
where id in
(select id
from instructor
group by id
having id not in
(select distinct(instructorid) from Session
where date='2017-03-19' and starttime<='15:30:00' and endtime>='14:30:00') )

How can I find days between different paired rows?

I've been racking my brain about how to do this in one query without PHP code.
In a nutshell, I have a table that records email activity. For the sake of this example, here is the data:
recipient_id activity date
1 delivered 2011-08-30
1 open 2011-08-31
2 delivered 2011-08-30
3 delivered 2011-08-24
3 open 2011-08-30
3 open 2011-08-31
The goal: I want to display to users a single number that tells how many recipients open their email within 24 hours.
E.G. "Users that open their email within 24 hours: 13 Readers"
In the case of the sample data, above, the value would be "1". (Recipient one was delivered an email and opened it the next day. Recipient 2 never opened it and recipient 3 waited 5 days.)
Can anyone think of a way to express the goal in a single query?
Reminder: In order to count, the person must have a 'delivered' tag and at least one 'open' tag. Each 'open' tag only counts once per recipient.
** EDIT ** Sorry, I'm using MySQL
Here is a version in mysql.
select count(distinct recipient_id)
from email e1
where e1.activity = 'delivered'
and exists
(select * from email e2
where e1.recipient_id = e2.recipient_id
and e2.activity = 'open'
and datediff(e2.action_date,e1.action_date) <= 1)
The basic principle is that you want to find a delivered row for a recipient that also has an open within 24 hours.
The datediff() is a good way to do the date arithmetic in mysql -- other dbs will vary on exact methods for this step. The rest of the sql will work anywhere.
SQLFiddle here: http://sqlfiddle.com/#!2/c9116/4
Untested, but should work ;) Don't know which SQL dialect you use, so I've used TSQL DATEDIFF function.
select distinct opened.recipient_id -- or count(distinct opened.recipient_id) if you want to know number
from actions as opened
inner join actions as delivered
on opened.recipient_id = delivered.recipient_id and delivered.activity = 'delivered'
where opened.activity = 'open' and DATEDIFF(day, delivered.date, opened.date) <= 1
Edit: I'd confused opened with delivered - now replaced.
Assumptions: MySql, table is called "TABLE"
Ok, I am not 100% on this, because I don't have a copy of the table to run it against, but I think that you could do something like this:
SELECT COUNT(DISTINCT t1.recipient_id) FROM TABLE t1
INNER JOIN TABLE t2 ON t1.recipient_id = t2.recipient_id AND t1.activity != t2.activity
WHERE t1.activity in ('delivered', 'open') AND t2.activity in ('delivered', 'open')
AND ABS(DATEDIFF(t1.date, t2.date)) = 1
Basically, you are joining a table onto itself, where the activities don't match, but recipient_ids do, and the status is either 'delivered' or 'open'. What you would end up getting, is a result that looks like this:
1 delivered 2011-08-30 1 open 2011-08-31
You are then doing a diff between the two dates (with an absolute value, because we don't know which order they will be in) and making sure that it is equal to 1 (or 24 hours).

Mysql subquery with joins

I have a table 'service' which contains details about serviced vehicles. It has an id and Vehicle_registrationNumber which is a foreign key. Whenever vehicle is serviced, a new record is made. So, for example if I make a service for car with registration ABCD, it will create new row, and I will set car_reg, date and car's mileage in the service table (id is set to autoincreament) (e.g 12 | 20/01/2012 | ABCD | 1452, another service for the same car will create row 15 | 26/01/2012 | ABCD | 4782).
Now I want to check if the car needs a service (the last service was either 6 or more months ago, or the current mileage of the car is more than 1000 miles since last service), to do that I need to know the date of last service and the mileage of the car at the last service. So I want to create a subquery, that will return one row for each car, and the row that I'm interested in is the newest one (either with the greatest id or latest endDate). I also need to join it with other tables because I need this for my view (I use CodeIgniter but don't know if it's possible to write subqueries using CI's ActiveRecord class)
SELECT * FROM (
SELECT *
FROM (`service`)
JOIN `vehicle` ON `service`.`Vehicle_registrationNumber` = `vehicle`.`registrationNumber`
JOIN `branch_has_vehicle` ON `branch_has_vehicle`.`Vehicle_registrationNumber` = `vehicle`.`registrationNumber`
JOIN `branch` ON `branch`.`branchId` = `branch_has_vehicle`.`Branch_branchId`
GROUP BY `service`.`Vehicle_registrationNumber` )
AS temp
WHERE `vehicle`.`available` != 'false'
AND `service`.`endDate` <= '2011-07-20 20:43'
OR service.serviceMileage < vehicle.mileage - 10000
SELECT `service`.`Vehicle_registrationNumber`, Max(`service`.`endDate`) as lastService,
MAX(service.serviceMileage) as lastServiceMileage, vehicle.*
FROM `service`
INNER JOIN `vehicle`
ON `service`.`Vehicle_registrationNumber` = `vehicle`.`registrationNumber`
INNER JOIN `branch_has_vehicle`
ON `branch_has_vehicle`.`Vehicle_registrationNumber` = `vehicle`.`registrationNumber`
INNER JOIN `branch`
ON `branch`.`branchId` = `branch_has_vehicle`.`Branch_branchId`
WHERE vehicle.available != 'false'
GROUP BY `service`.`Vehicle_registrationNumber`
HAVING lastService<=DATE_SUB(CURDATE(),INTERVAL 6 MONTH)
OR lastServiceMileage < vehicle.mileage - 10000
;
I hope I have no typo in it ..
If instead of using * in the subquery you specify the fields you need (which is always good practice anyway), most databases have a MAX() function that returns the maximum value within the group.
Actually, you don't even need the subquery. You can do the joins and use the MAX in the SELECT statement. Then you can do something like
SELECT ...., MAX('service'.'end_date') AS LAST_SERVICE
...
GROUP BY 'service'.'Vehicle_registrationNumber'
Or am I missing something?

Help diagnose bizzare MySQL query behavior

I have a very specific query that is acting up and I could use any help at all with debugging it.
There are 4 tables involved in this query.
Transaction_Type
Transaction_ID (primary)
Transaction_amount
Transaction_Type
Transaction
Transaction_ID (primary)
Timestamp
Purchase
Transaction_ID
Item_ID
Item
Item_ID
Client_ID
Lets say there is a transaction in which someone pays $20 in cash and $0 in credit it inserts two rows into the table.
//row 1
Transaction_ID: 1
Transaction_amount: 20.00
Transaction_type: cash
//row 2
Transaction_ID: 1
Transaction_amount: 0.00
Transaction_type: credit
here is the specific query:
SELECT
tt.Transaction_Amount, tt.Transaction_ID
FROM
ItemTracker_dbo.Transaction_Type tt
JOIN
ItemTracker_dbo.Transaction t
ON
tt.Transaction_ID = t.Transaction_ID
JOIN
ItemTracker_dbo.Purchase p
ON
p.Transaction_ID = tt.Transaction_ID
JOIN
ItemTracker_dbo.Item i
ON
i.Item_ID = p.Item_ID
WHERE
t.TimeStamp >= "2010-01-06 00:00:00" AND t.TimeStamp <= "2010-01-06 23:59:59"
AND
tt.Transaction_Format IN ('cash', 'credit')
AND
i.Client_ID = 3
when I execute this query, it returns 4 rows for a specific transaction. (it should be 2)
When I remove ALL where clauses and insert WHERE tt.Transaction_ID = problematicID it only returns two.
EDIT:::::
still repeats upon changing date range
The kicker:
When I change the initial daterange it only returns two rows for that specific transaction_id.
::::
Is it the way I use join? that's all I can think of...
EDIT: This is the problem
in purchase - two sepparate purchase_ID's can have the same transaction_ID (purhcase_ID breaks down specific item sales).
There are duplicate Transaction_ID rows in purchase_ID
We need to see all the data in all the tables to be able to know where the problem is. However, because the joins are the problem it is because one of your tables has two rows when you think it has only one.
There's a problem with your schema. You have rows with the same transaction_id, which is the primary key. I would think they couldn't be marked primary in that database. With two rows with the same id, that could cause unexpected extra rows to come back from the join(s).