Get the detail of overlapping bookings mysql - mysql

I am developing a stable booking system where user can book and update their room bookings and chose stables from interactive map.
My stable registration db structure is like below
event_detail_stable_registrations
| id | accountId | eventId | stableId | checkInDate | checkOutDate |
5 233 55 66 26-06-2017 28-06-2017
6 234 55 66 28-06-2017 29-06-2017
When user updates the booking but do not change the checkInDate and checkOutDate then its an easy scenario which i have implemented already.
In above case if user 234 updates the booking and change checkInDate then the query should return 233 for stableId 66 but my query returns '234' as accountId
Another scenario is when user changes the checkInDate and or checkOutDate of the registration. User A wants to change the booking detail how can i check if any overlapping for updated checkInDate and or checkOutDate for user's booking and if those are already booked then which accountId has booked it.
Right now I am running following query which gives me correct information about overlapping dates but could not get the information of account who has booked it.
Query always returns the user's accountId for overlapping dates as well.
SET #checkInDate = '2017-04-27 14:00:00' , #checkOutDate = '2017-05-01 10:00:00' ;
SELECT a.*,
IF(b.`stableid` IS NULL,"Avalalable","Not Available") as `status`,
IF(NOT b.`stableid` IS NULL,b.`accountId`,"") as `overLapAccount`,
IF(NOT b.`stableid` IS NULL,b.`checkInDate`,"") as `start_overlap`,
IF(NOT b.`stableid` IS NULL,b.`checkOutDate`,"") as `end_overlap`
FROM `event_detail_stable_registrations` b
LEFT JOIN `stables` a
ON a.`id` = b.`stableid` AND
(((`checkInDate` BETWEEN #checkInDate AND #checkOutDate)
OR (`checkOutDate` BETWEEN #checkInDate AND #checkOutDate))
OR ((#checkInDate BETWEEN `checkInDate` AND `checkOutDate`)
OR (#checkOutDate BETWEEN `checkInDate` AND `checkOutDate`))
)
ORDER BY a.`name`
Here is the SQL fiddle where I haven't used the same DB structure but its similar.
The output I get for the same stable booked by multiple account for given period is fine but with the query I am using, I get null in the column name,stableId.

Part 2 of your scenario:
MariaDB-10.5 Application Time Periods - WITHOUT OVERLAPS can enforce the constraints of non-overlapping bookings:
ALTER TABLE event_detail_stable_registrations
ADD period FOR booking(checkInDate, checkOutDate),
ADD PRIMARY KEY (accountId, stableId, booking WITHOUT OVERLAPS)
A user cancelling a day is just:
DELETE
FROM event_detail_stable_registrations
FOR PORTION OF booking
FROM '2017-04-29 14:00:00' TO '2017-04-30 10:00:00'
WHERE accountId=5
AND stableId=866
Which splits the non-deleted dates booking entry over periods.
Any UPDATE of a booking enforces the constraints.
ref: dbfiddle

Related

MySQL - SQL select query with two tables using where, count and having

There are two tables: client and contract.
client table:
client_code INT pk
status VARCHAR
A client can have 1 or more contracts. The client has a status column which specifies if it has valid contracts - the values are 'active' or 'inactive'. The contract is specified for a client with active status.
contract table:
contract_code INT pk
client_code INT pk
end_date DATE
A contract has an end date. A contract end date before today is an expired contract.
REQUIREMENT: A report requires all active clients with contracts, but with all (not some) contracts having expired date. Some example data is shown below:
Client data:
client_code status
----------------------------------
1 active
2 inactive
3 active
4 active
Contract data:
contract_code client_code end_date
-------------------------------------------------------------
11 1 08-12-2018
12 1 09-12-2018
13 1 10-12-2018
31 3 11-31-2018
32 3 10-30-2018
41 4 01-31-2019
42 4 12-31-2018
Expected result:
client_code
-------------
1
RESULT: This client (client_code = 1) has all contracts with expired dates: 08-12-2018, 09-12-2018 and 10-12-2018.
I need some help to write a SQL query to get this result. I am not sure what constructs I have to use - one can point out what I can try. The database is MySQL 5.5.
One approach uses aggregation. We can join together the client and contract tables, then aggregate by client, checking that, for an active client, there exist no contract end dates which occur in the future.
SELECT
c.client_code
FROM client c
INNER JOIN contract co
ON c.client_code = co.client_code
WHERE
c.status = 'active'
GROUP BY
c.client_code
HAVING
SUM(CASE WHEN co.end_date > CURDATE() THEN 1 ELSE 0 END) = 0;
Demo
Note: I am assuming that your dates are appearing in M-D-Y format simply due to the particular formatting, and that end_date is actually a proper date column. If instead you are storing your dates as text, then we might have to make a call to STR_TO_DATE to convert them to dates first.
Is that what you're looking for?
select clients.client_code
from clients
join contracts
on contracts.client_code=clients.client_code
where status='active'
group by clients.client_code
having min(end_date)>curdate()

Getting a syntax error when trying to join two tables

I have created the following tables:
USER TABLE
user_id (primary key)
account_created (date)
email (varchar)
usage_count (number)
PRODUCT TABLE
product_id (primary key)
product (varchar) (values include “iPhone”, “Android”, “Windows”)
users_supported (number) (users supported notes: some phones can support group calls up to 1000 users, some can only support normal calls of 2 users)
USAGE TABLE
usage_id (primary key)
product_id (foreign key)
user_id (foreign key)
usage_date (date)
purchase_call (number) (can be a 0, 2, 4, 6, or 10 min call)
usage_winnings (number) (when users use their minutes, sometimes they will randomly earn cash back)
computer_usage (binary value) (users can link the phone to a computer, and make calls through their computer, similar to google voice)
I want to write a select statement with the following constraints:
Time frame between 2014 and 2016
% of calls made for 2 users
% of purchased minutes used for only 2 users
Only in the first 30 days after a user created their account
In each year between 2014 and 2016, what percentage of calls and purchased calls were for only 2 users in each user's first 30 days after they created their account.
I have been practicing joins and what I have is:
SELECT COUNT(p.users_supported = 2)/COUNT(p.users_supported), SUM(CASE WHEN users_supported = 2 THEN us.purchase_call ELSE 0 END)/SUM(CASE WHEN users_supported <> 2 THEN us.purchase_call ELSE 0 END)
FROM USERS u
JOIN USAGE us ON u.user_id = us.user_id
JOIN PRODUCT p ON p.product_id = us.product_id
WHERE u.account_created >= '2014-01-01'
AND u.account_created <= '2016-12-31'
AND u.account_created <= u.account_created + 30
I have several errors right now - the percentages are not coming out correct and the account created with 30 days constraint is causing an error that breaks the whole query. Any suggestions would be much appreciated!
You did not state what database you are using...
First thing I notice is that
AND u.account_created <= u.account_created + 30
is always true. I think you want the query based on the current date like
AND u.account_created > NOW() - 30
If you are using sql server then you could use the datediff function and check for a result < 30
Since it was pointed out that you tagged this question mysql then using TIMESTAMPDIFF will work. Here is an example showing the syntax and the use of the NOW() function.
mysql> select TIMESTAMPDIFF(day,NOW(),'20161206');
+-------------------------------------+
| TIMESTAMPDIFF(day,NOW(),'20161206') |
+-------------------------------------+
| -17 |
+-------------------------------------+
1 row in set (0.04 sec)

Retrieve rows that have a first entry in 2014 in MySQL

I want to retrieve all rows from a table that have their first entry on or after 01/01/2014 but no later than 31/12/2014
Example of the table:
OID FK_OID Treatment Trt_DATE
1 100 19304 2011-05-24
2 100 19304 2011-08-01
3 100 19306 2014-03-05
4 200 19305 2012-02-02
5 300 19308 2014-01-20
6 400 19308 2014-06-06
For example. I would like to pull all entries that have STARTED treatment in 2014. So above i would to extract FK_OID's 300 and 400 because their first entry is in 2014, but i would like to omit FK_OID 100 because they have 2 entries prior to 2014.
How do i go about this? I can extract all entries within a date range etc but that brings back all entries for that date and doesn't omit anyone who has an entry prior to the start of the date range. It just returns their first entry in 2014.
For the ones who need to see that i have tried something. See below.
I am not an experienced coder and this is the best i can get because i don't have the knowledge.
SELECT
mod,
(select NHSNum from person p
WHERE
p.oid = t.fk_oid) as 'NHS'
FROM
timeline t
Where trt_date BETWEEN '2014-01-01' AND '2014-12-31'
ORDER BY trt_date ASC
This returns every treatment for 2014 regardless of whether it is the first ever one for that person. I want to omit anyone from this list who has had treatment before 01/01/2014 as well as only return the first treatment per person. For example, this code returns all treatments for all people in 2014. I only want their first one and only if it is their first one ever.
Thanks.
create table aThing
( oid int auto_increment primary key,
fk_oid int not null,
treatment int not null,
trt_date date not null
);
insert aThing (fk_oid,treatment,trt_date) values
(100, 19304, '2011-05-24'),
(100, 19304, '2011-08-01'),
(100, 19306, '2014-03-05'),
(200, 19305, '2012-02-02'),
(300, 19308, '2014-01-20'),
(400, 19308, '2014-06-06');
select fk_oid,dt
from
( select fk_oid,min(trt_date) as dt
from aThing
group by fk_oid
) xDerived
where year(dt)=2014;
+--------+------------+
| fk_oid | dt |
+--------+------------+
| 300 | 2014-01-20 |
| 400 | 2014-06-06 |
+--------+------------+
The inner part, the nested one, become a derived table, and is given a name xDerived. This means that even though it is just a result set, by making it a derived table, it can be referred to by name. So it is not a physical table, but a derived one, or virtual one.
So that derived table is a very simple group by with an aggregate function. It says, for every fk_oid, bring back one row and only 1 row, with its minimum value for trt_date.
So if you have 10 million rows in that table called aThing, but only 17 distinct values for fk_oid, it will return only 17 rows. Each row being the minimum of trt_date for its fk_oid.
So now that that is achieved, the outer wrapper says just show me those two columns (but with a year check). There is a complicated to explain reason why I had to do that, so I will try to do it here.
But I might need a little time to explain it well, so bear with me.
This will be a shortcut way to say it. I had to get the min into an alias, and I only had access to that alias if resolved in a derived table, to cleanse it so to speak, and then access it with an outer wrapper.
An alias of aggregate column, like as dt, is not available (as a pseudo like column name which is what an alias is) ... it is not available in a where clause. But by wrapping it in a derived table name, I cleanse it so to speak, and then I can access it in a where clause.
So I can't access it directly in its own query in the where clause, but when I wrap it in an envelope (a derived table), I can access it on the outside.
I will try better to explain it later, maybe, but I would have to show alternative attempts to gain access to results, and the syntax errors that would result.
There's probably a more elegant solution, but this seems to satisfy the requirement...
SELECT x.*
FROM my_table x
JOIN
( SELECT fk_oid
, MIN(trt_date) min_date
FROM my_table
GROUP
BY fk_oid
HAVING min_date > '2014-01-01'
) a
ON a.fk_oid = x.fk_oid
LEFT
JOIN my_table b
ON b.fk_oid = a.fk_oid
AND b.trt_date > '2014-12-31'
WHERE b.oid IS NULL;
Having a few years a experience with this, i decided to revisit it. The solution i now use regularly is:
SELECT t1.column1, t1.column2
FROM MyTable AS t1
LEFT OUTER JOIN MyTable AS t2
ON t1.fkoid = t2.fkoid
AND (t1.date > t2.date
OR (t1.date = t2.date AND t1.oid > t2.oId))
WHERE t2.fkoid IS NULL and t1.date >= '2014-01-01'

Grouping by date and people

I am developing a database in php/mysql.
I have a table ‘matterjuncactions’ which contains the fields
actiondate
howlong
staffid
When a member of staff records an action it is entered into the table with the field howlong recording time as a decimal.
A member of staff could record any number of actions in a day. (There are currently 12 staff members)
What I would like to do is have a page showing a table with dates down the left hand side and staff ids across the top with each cell containing the sum of the time spent for that day. (i.e. sum of’howlong’)
So something like:
Date | Staffid 1 | Staff id2 |
6th August | 3.5 | 2.7 |
5th August | 5.7 | 4.6 |
etc
I can get the totals for a single staff member using:
SELECT DATE_FORMAT(matterjuncactions.actiondate,'%W-%D') AS fDt
, SUM(howlong) AS tottime
FROM matterjuncactions
WHERE staffid=1
GROUP
BY matterjuncactions.actiondate
ORDER
BY matterjuncactions.actiondate DESC
I can’t work out how to get this to display all of the data for all of the staffids.
Try this one
SELECT DATE_FORMAT(matterjuncactions.actiondate,'%W-%D') AS fDt,
( select SUM(howlong) from matterjuncactions AS tottime1 where staffid=1) as total1,
( select SUM(howlong) from matterjuncactions AS tottime2 where staffid=2) as total2
FROM matterjuncactions
GROUP
BY matterjuncactions.actiondate
ORDER
BY matterjuncactions.actiondate DESC
Hope it helps :)

Find booking overlaps to check dates availability

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.