How to check if specific datetime was not used using multiple tables? - php

There are 3 tables: buildings, rooms, reservations.
1 building = n rooms
1 room = n reservations
TABLE BUILDINGS - ID(int), name(varchar)
TABLE ROOMS - ID(int), building_id(int)
TABLE RESERVATIONS - ID(int), room_id(int), date_start(datetime), date_end(datetime)
Buildings table example
<pre>
ID name
1 Building A
2 Building B
3 Building C
</pre>
Rooms table example
<pre>
ID building_id
1 1
2 1
3 2
4 3
</pre>
Reservations table example
<pre>
ID room_id date_start date_end
1 1 2014-08-09 14:00:00 2014-08-09 14:30:00
2 1 2014-08-09 14:30:00 2014-08-09 15:30:00
3 3 2014-08-09 16:30:00 2014-08-09 17:30:00
4 2 2014-08-09 16:00:00 2014-08-09 17:00:00
5 3 2014-08-09 16:00:00 2014-08-09 16:30:00
</pre>
Question
How to filter all buildings which have atleast 1 room available at the specific date and time?
For example we want to filter all buildings which have atleast 1 available room at 2014-08-09 16:00:00 until 2014-08-09 17:00:00. In this case it will show that Building A AND Building C are available. Building A has 1 free room and Building B also has 1 free room, because there is no reservation on that room.
Can someone help me out on this one? I just can not figure it out with a single MySQL query to get all buildings. Thank you.

Use LEFT JOIN to connect the rooms table to the reservations table, with the time condition in the on clause. Then, look where there are no matches -- these are rooms that are available.
SELECT b.id, b.name, COUNT(*) as NumRoomsAvailable
FROM buildings b JOIN
rooms r
ON b.id = r.building_id LEFT JOIN
reservations rv
ON rv.room_id = r.id AND
'2014-08-09 16:00:00' BETWEEN rv.date_start AND rv.date_end
WHERE rv.room_id is null
GROUP BY b.id;
The final step is just aggregating by the building, rather than listing each available room out separately.

Try this query
SELECT b.name,r.id,rv.date_start,rv.date_end FROM buildings b
LEFT JOIN rooms r ON b.id = r.building_id
LEFT JOIN reservataions rv ON rv.room_id = r.id
WHERE '2014-08-09 16:00:00' BETWEEN rv.date_start AND rv.date_end

Related

Counting field total against another table column value

room | beds available | ****table room****
==================================
room1 | 4
room2 | 2
room3 | 4
room | occupant | ****table occupant****
==================================
room1 | arnold
room1 | berry
room2 | charles
room2 | daisy
room3 | eric
room3 | frank
room3 | greg
I looking to get the following output:
No. of rooms with beds available: 2 || Rooms with beds available: room 1, room 3
i reckon i need to
store count* for each unique room and store the count as an array
subtract beds available for each room against this array
display the room name when there's a result > 0
How should the php snippet code look like?
These SQL queries will do the trick.
Remember, you can remove unwanted columns to save the amount of data you're processing...
select r.id AS 'room',
r.beds as 'total_beds',
count(o.occupant) as 'taken_beds',
r.beds-count(o.occupant) as 'free_beds'
FROM room r LEFT JOIN occupant o ON r.id = o.room
GROUP BY r.id
HAVING r.beds > count(o.occupant)
This will only return the rooms that are not full.
If at any other point, you wish to return full rooms too, simply remove the "HAVING" clause
select r.id AS 'room',
r.beds as 'total_beds',
count(o.occupant) as 'taken_beds',
r.beds-count(o.occupant) as 'free_beds'
FROM room r LEFT JOIN occupant o ON r.id = o.room
GROUP BY r.id
I think this all can be done with a simple SQL query that should look something like this:
SELECT room.beds AS beds, COUNT(occupant.occupant) AS beds_occupied
FROM room
LEFT JOIN occupant ON occupant.room = room.room
GROUP BY room
HAVING (beds - beds_occupied) > 0;
You can use the 'HAVING' clause which is similar to the WHERE clause but works with aggregates. I haven't run this exact query on your exact tables so there might be a typo, however, I hope the idea what the query is supposed to do is clear.
So something like this where you can change how many available beds you need..
SELECT r.*, COUNT(*) AS occupied, r.beds - COUNT(*) AS rest
FROM room r
LEFT JOIN occupant o ON r.room = o.room
GROUP BY r.room
HAVING r.beds - occupied >= 1
MySQL offers GROUP_CONCAT to aggregate strings:
select
count(*) as number_of_rooms,
group_concat(room) as rooms
from room r
where beds_available >
(
select count(*)
from occupant o
where o.room = r.room
);
This selects rooms with more beds available than occupied and then aggregates the resulting rows to one row containing the number of available rooms and a string with the room names comma-separated.

Mysql select multiple count giving wrong values

I'm trying to find a patient's appointments and messages count. My table records are like below 3 table patient, appointments and messages
Patient table
pid fname lname
1 john sid
2 rother ford
3 megan rough
4 louis kane
appointments table
id pid appointment_date
1 1 2015-08-04
2 2 2015-08-05
3 1 2015-08-06
4 1 2015-08-07
5 3 2015-08-07
6 2 2015-08-08
7 4 2015-08-13
8 1 2015-08-12
Messages table
id pid description message_date
1 2 join 2015-08-04
2 2 update 2015-08-05
3 3 join 2015-08-05
4 4 update 2015-08-10
5 3 test 2015-08-07
So if write query to find counts i'm getting wrong values
SELECT pd.fname,pd.lname , pd.pid, COUNT( a.id ) AS app_cnt, COUNT( m.id ) AS mes_cnt
FROM patient pd
LEFT OUTER JOIN appointments a ON a.pid = pd.pid
LEFT OUTER JOIN messages m ON m.pid = pd.pid
GROUP BY pd.pid
ORDER BY pd.pid
fname lname pid app_cnt mes_cnt
john sid 1 4 0
rother ford 2 4 4
megan rough 3 2 2
louis kane 4 1 1
Here pid 1 have 4 appointments and 0 messages, pid 2 have 2 appointments and 2 messages but getting wrong values.
Can someone please help to resolve this issue. I'm not interested in writing sub queries for this.
Functionality looks simple but I'm really facing problem for writing query.
Anymore suggestions please.
After thoroughly analysing your problem and tables, It cannot be done directly using simple query as LEFT OUTER JOIN is returning some superfluous records, that's why to filter it, you will have to use temporary table and modify the query as:
Select temp.fname, temp.lname, temp.pid, a_count, count(m.pid) as m_count from
(SELECT fname,lname,pd.pid, count(a.pid) as a_count
FROM patients pd
LEFT OUTER JOIN appointments a ON a.pid = pd.pid group by pd.pid) temp
LEFT OUTER JOIN messages m ON m.pid = temp.pid
group by temp.pid
Explanation:
It will join patients and appointments table and group them by pid so that messages from message table do not repeat for each patients.pid.
The wrong result is as a result of left outer join as it is giving wrong results for this query
SELECT *
FROM patients pd
LEFT OUTER JOIN appointments a ON a.pid = pd.pid
LEFT OUTER JOIN messages m ON m.pid = pd.pid
Since we need to limit the results of first two joins, hence temporary table is necessary.

Query from 2 tables in MYSQL

I have two tables in MySQL:
Table 1
Week From Until
1 2015-04-01 2015-04-07
2 2015-04-08 2015-04-14
3 2015-04-15 2015-04-21
4 2015-04-22 2015-04-28
Table 2
Input_Date Code
2015-04-10 123
2015-04-22 456
2015-04-25 123
2015-04-26 123
I used this query to select the current week based on the current date:
SELECT Week FROM table_1 WHERE (NOW() BETWEEN From AND Until)
I need to select the "code" and count it from table 2 where "Code" = 123 and "Input_Date" corresponds to the current "Week".
*If the current date is 2015-04-23, the "Week" would be = 4
The result would be:
Week Code Count
4 123 2
Try-
SELECT t1.Week, t2.Code, COUNT(*)
FROM table_1 t1
LEFT JOIN table_2 t2
ON t2.Input_Date BETWEEN t1.From AND t1.Until
WHERE (NOW() BETWEEN t1.From AND t1.Until)
AND t2.Code = 123
http://sqlfiddle.com/#!9/3d2a6/16
Alternate way of doing the same thing without LEFT JOIN
SELECT a.week, b.code, count(a.week)
FROM table_1 a, table_2 b
WHERE (b.input_date BETWEEN a.from AND a.until)
AND (NOW() BETWEEN a.From AND a.Until)
AND b.code = 123

MySQL querying date range

Here's my database (free rooms in a hotel, simplified)
rooms_available:
id date_available room_id
================================
1 2013-12-19 2
2 2013-12-20 2
3 2013-12-21 2
4 2013-12-22 2
5 2013-12-23 2
6 2013-12-25 3
rooms:
id name minimal_range
================================
2 Apartment A 5
3 Apartment B 1
I want to query all rooms which are available between 2013-12-20 and 2013-12-22
My query is like:
select *
from rooms_available
where (date='2013-12-20' OR date='2013-12-21' OR date='2013-12-22')
My questions:
is there a more comfortable way? when the date range will be like 2 weeks, the query will also be very long (which will take much longer for querying)
would it be possible to consider minimum ranges - for example: room_id 2 is only available for at least 5 nights (see table "rooms") -> so above query should return no records
Thanks
date >= '2013-12-20' and date <= '2013-12-22'
SELECT * FROM rooms_available WHERE `date_available` BETWEEN "2013-12-20 " AND "2012-03-31"
I didn't test this but it should point you in the right direction especially for the second part of your question about minimal range.
SELECT t1.id as id, t1.date_available as date_available, t1.room_id
FROM rooms_availble as t1 JOIN rooms as t2 on t1.room_id = t2.id
WHERE t1.date_available BETWEEN DATE('2013-12-20') AND DATE('2012-03-31') AND
t2.minimal_range <= datediff('2013-12-22', '2012-12-20');
The mysql datediff function will return the number of days between two dates then you can just compare it with the minimal_range from the rooms join table. Also you might consider binding the start and end dates to variables so that you only have to write each date once.

I want the last row of all players in a specific month+year

Here is a simplified version of my sql table of 2 months (ORDERED BY DATE):
player_id |
date |
score
1 2011-05-25
1200
2 2011-05-25
3400
3 2011-05-26
3200
4 2011-05-26
4400
1 2011-05-28
1000
2 2011-05-28
2000
3 2011-05-29
3000
4 2011-05-29
4000
1 2011-06-24
1300
2 2011-06-24
2500
3 2011-06-24
5000
4 2011-06-24
3000
Basically, I want a query that shows the last score of all players in a specific month/specific year.
Example:
If I want the final scores of all players in the month 05, te result
would be:
1 2011-05-28 1000
2 2011-05-28 2000
3 2011-05-29 3000
4 2011-05-29 4000
My sql query so far:
SELECT m1.* FROM table m1
LEFT JOIN table m2 ON (m1.player_id = m2.player_id AND m1.date < m2.date)
WHERE m2.date IS NULL
AND month(m1.date) = 05
AND year(m1.date) = 2011
ORDER BY score DESC);
This doesn't seem to show all players, only players that didn't play in the months after 05. Where do I add the date select?
**EDIT
John Nestoriak's answer bellow did the trick for me :)
I think he's referring to the technique shown here: Retrieving the last record in each group
With the additional constraint of he doesn't want the last record but the last record in a given month.
Oddly enough you have to give that additional constraint twice, once in the join condition and again to filter the results. This should do it for you.
SELECT m1.* FROM table m1
LEFT JOIN table m2 ON
(m1.player_id = m2.player_id AND m1.date < m2.date
AND m2.date < '2011-06-01')
WHERE m2.date IS NULL AND month(m1.date) = 5 AND year(m1.date) = 2011
Assuming that the (player_id, date) combination in Unique:
SELECT
t.*
FROM
TableX AS t
JOIN
( SELECT
player_id
, MAX(date) AS maxDate
FROM
TableX
WHERE
date BETWEEN '2011-05-01'
AND LAST_DAY('2011-05-01')
GROUP BY
player_id
) AS tg
ON
(tg.player_id, tg.maxDate) = (t.player_id, t.date)
ORDER BY
t.score DESC

Categories