mysql query which needs to do calculations - mysql

I'm not so great with sql but I think what I want should be possible, I'm just not sure how to do it.
I have a site which allows users to list their youth hostel. Potential customers can then browse these hostels and place bookings.
Tables:
hostels
-------
max_guests
bookings
-------
hostel_id
check_in
check_out
guests
When users search the site I allow them to select a checkin date, a checkout date, a place name, and the number of guests. Note that each hostel has a maximum number of guests which they can accommodate, for example 20.
So say previously a booking has been made at a particular hostel for 8 people during the dates selected for the search, then I need to know that the maximum number of guests that particular hostel can accommodate between those dates is now 12, and it would only appear in searches for <= 12 guests.
Likewise if that hostel had a couple more bookings and was at it's maximum capacity, then it shouldn't turn up in the search at all.
At the moment I have a query which gets all the hostels but if there's any bookings for a hostel between the selected dates, then it's excluded from the results. I'd like to be able to get all the bookings for each hostel during the selected dates, count the numbers of guests booked in, and if that number subtracted from the max_guests of the hostel gives a result which is lower than the number of guests specified in the search, then exclude it from the results.
at the moment my query looks like:
SELECT DISTINCT `Hostel`.`id`, `Hostel`.*, `Hostel`.`id`
FROM `hostelsdatabase`.`hostels` AS `Hostel`
LEFT JOIN `hostelsdatabase`.`bookings` AS `bookings` ON (`bookings`.`hostel_id` = `Hostel`.`id`)
LEFT JOIN `hostelsdatabase`.`users` AS `User` ON (`Hostel`.`user_id` = `User`.`id`)
WHERE ((`Hostel`.`address_one` LIKE '%london%') OR (`Hostel`.`address_two` LIKE '%london%') OR (`Hostel`.`city` LIKE '%london%'))
AND `Hostel`.`maxguests` >= 4
AND NOT EXISTS (SELECT * FROM bookings WHERE `bookings`.`hostel_id` = `Hostel`.`id` AND NOT('2013-07-31' > `bookings`.`checkout` OR '2013-08-02' < `bookings`.`checkin`))
LIMIT 10
Can anyone tell me how I could modify that query to make it do what I need?

Related

How to execute SQL query for selection of info from more than 1 table

I wanted to select values from one table according to another table
I have a booking system with 'user' information table and 'appointment table', they have columns:
user_id, name, surname, telephone_number
appointment_id, user_id, appointment_date_time
I want to make a SQL query so that I can choose a date, for example, 20th of May and retrieve all the booking's on 20th of May, but in different times, like 12:00, 9:00, 14:00, 15:00, 10:00 and so on, as they are not ordered in the table
Sort the time of the appointments so that it will be: 9:00, 10:00, 12:00, 14:00, 15:00
Select user_ids of the bookings from 'appointments'
Select the user credentials through 'user' table like their names, surname, and phone number
At the end I am going to implement this into the admin panel
So that when the admin will choose a specific date
They can see all the appointment bookings, their time, and who booked them.
Could you please tell me what kind of query I can use (preferable a complex one)
or direct me to useful and understandable resources?
SELECT appointments.appointment_index, appointments.appointment_time, users.name,
users.surname, users.telnumber
FROM users
INNER JOIN appointments
ON users.user_id = appointments.user_id
WHERE appointment_date = '$date'
ORDER BY appointment_time";

MySQL query to select all hostels with at least X spaces between start and end dates?

I have 2 tables, one with hostels (effectively a single-room hotel with lots of beds), and the other with bookings.
Hostel table: unique ID, total_spaces
Bookings table: start_date, end_date, num_guests, hostel_ID
I need a (My)SQL query to generate a list of all hostels that have at least num_guests free spaces between start_date and end_date.
Logical breakdown of what I'm trying to achieve:
For each hostel:
Get all bookings that overlap start_date and end_date
For each day between start_date and end_date, sum the total bookings for that day (taking into account num_guests for each booking) and compare with total_spaces, ensuring that there are at least num_guests spaces free on that day (if there aren't on any day then that hostel can be discounted from the results list)
Any suggestions on a query that would do this please? (I can modify the tables if necessary)
I built an example for you here, with more comments, which you can test out:
http://sqlfiddle.com/#!9/10219/9
What's probably tricky for you is to join ranges of overlapping dates. The way I would approach this problem is with a DATES table. It's kind of like a tally table, but for dates. If you join to the DATES table, you basically break down all the booking ranges into bookings for individual dates, and then you can filter and sum them all back up to the particular date range you care about. Helpful code for populating a DATES table can be found here: Get a list of dates between two dates and that's what I used in my example.
Other than that, the query basically follows the logical steps you've already outlined.
Ok, if you are using mysql 8.0.2 and above, then you can use window functions. In such case you can use the solution bellow. This solution does not need to compute the number of quests for each day in the query interval, but only focuses on days when there is some change in the number of hostel guests. Therefore, there is no helping table with dates.
with query as
(
select * from bookings where end_date > '2017-01-02' and start_date < '2017-01-05'
)
select hostel.*, bookingsSum.intervalMax
from hostel
join
(
select tmax.id, max(tmax.intervalCount) intervalMax
from
(
select hostel.id, t.dat, sum(coalesce(sum(t.gn),0)) over (partition by t.id order by t.dat) intervalCount
from hostel
left join
(
select id, start_date dat, guest_num as gn from query
union all
select id, end_date dat, -1 * guest_num as gn from query
) t on hostel.id = t.id
group by hostel.id, t.dat
) tmax
group by tmax.id
) bookingsSum on hostel.id = bookingsSum.id and hostel.total_spaces >= bookingsSum.intervalMax + <num_of_people_you_want_accomodate>
demo
It uses a simple trick, where each start_date represents +guest_num to the overall number of quests and each 'end_date' represents -guest_num to the overall number of quests. We than do the necessary sumarizations in order to find peak number of quests (intervalMax) in the query interval.
You change '2017-01-05' in my query to '2017-01-06' (then only two hostels are in the result) and if you use '2017-01-07' then just hostel id 3 is in the result, since it does not have any bookings yet.

MySQL Calculating The Difference Between two dates but with a twist

I have 2 tables: Product, User.
Product:
Date(Datetime)
Phone_Number(Number).
User:
Date (Datetime)
Phone_Number(Number)
Type(Value of N or C).
When a new user is added to the User table, it is given a date, Phone Number and value of "N" (N for New).
When a user gets cancelled, it is given a date, the Phone Number and value of "C" (C for Cancelled).
Using MySQL, I need to find out the date difference between the Date the user was created and the date the user was cancelled and if they had a product (can match the user to the product by Phone Number) between the date of the creation and the date of the cancellation.
Any assistance would be greatly appreciated as my Query writing isn't terrible but this I can't work out.
Thanks.
You need to start by self-joining the User table to find the created and cancelled dates. Then join to the Product table to see if anything is there.
SELECT u.Phone_Number, u.Created, u.Cancelled, p.Date as 'ProductDate'
FROM (
SELECT u1.Phone_Number, u1.Date as 'Created', u2.Date as 'Cancelled'
FROM User u1
JOIN User u2 on u1.Phone_Number=u2.Phone_Number
WHERE u1.Type = 'N'
and u2.Type = 'C'
) u
LEFT JOIN Product p on u.Phone_Number = p.Phone_Number
and p.Date between u.Created and u.Cancelled
SELECT
/* Anything you want from the User table */
/* CASE statement that checks if prod.phone_number is not null which means a product was present */
FROM
User user_new
JOIN User user_can
ON user_new.phone_number = user_can.phone_number
LEFT JOIN Product prod
ON prod.phone_number = user_new.phone_number
WHERE
(prod.Date IS NULL) OR -- Either there was no record.
(prod.Date BETWEEN user_new.Date AND user_can.Date) -- or a product existed between the two dates.
It doesn't sound like your table will have the required data to determine the difference between the date the user is created and cancelled if you're only storing one date field. But if you had 2 columns, user.createdDate and user.cancelledDate you could use
SELECT DATEDIFF(user.createdDate, user.cancelledDated) AS DiffDate
I don't know if DATEDIFF works with datetime objects, I've used it with plain dates formatted like "year-mm-dd" and it will give you a Query result object with DiffDate, an integer of the number of days.
If user.cancelledDate is before user.createdDate, you will get a negative integer.

MySQL query one table based on sum of records or no records in another table

I'm struggling to select records from a table of locations (eg hotels) based on their availability stored in a seperate table. To avoid having lots of availability records for every possible day/location combo, the availability table only holds records for limited or no availability for a given date - so the absence of a matching record means there is FULL availabilty.
The tables are a bit like...
locations: id, name, maxRooms etc.
availability: locationID, date, roomsAvailable( an integer from zero meaning no availability to maxRooms)
...and what I need to do is to select all locations who - for a given date period - have some availability. That means they either have no matching availability records (ie fully available) or the sum of their matching availability.roomsAvailable records is greater than zero.
I'm getting a headache just trying to explain this :-( any ideas gratefully received...
SELECT locations.id FROM locations LEFT JOIN availability
ON( locations.id = availability.locationID AND
( availability.roomsAvailable > 0 OR ISNULL( availability.locationID ) ) )
You need to use a LEFT JOIN
I think you can query like this, get all locations whose sum of rooms available are greater than 0 for a specific date, then get all locations that are not listed in availability.
SELECT * FROM locations
WHERE id in
(
SELECT locationId
FROM availability
WHERE date >= '2011-05-01' and date <= '2011-07-01'
GROUP by locationId
HAVING SUM(roomsAvailable) > 0
)
OR id not in (SELECT DISTINCT locationId FROM availabilty)
Thanks for your suggestions everyone. After considering them I came up with this combined PHP/MySQL solution - which only works on MySQL 4 or later ...
SELECT * FROM locations
WHERE locations.id NOT IN (
SELECT distinct availability.locationID
FROM availability
WHERE availability.date >= '$periodStartYMD'
AND availability.date <= '$periodEndYMD'
GROUP BY locationID
HAVING sum(availability.availableRooms) = 0
AND count(availability.locationID) = $daysInPeriod)
the PHP vars should be self explanatory.
To summarise - If there are less availability records than days in the period that indicates full availability for the missing day records. If there IS a record, it indicates no or limited availability..
The sub-select statement gets all availability records matching a location which have a total availability of zero and ( the bit that was eluding me) a count of matching records totalling the number of days in the period.
So if there is an availability record for every day AND the total availability count is zero there must be no rooms available for the whole period.
Phew - hope that makes some sort of sens and thanks again...

hotel reservation system SQL: identify any room available in date range

(In case this seems familiar: I asked a different question similar to this one, but I just got a note that the site design has changed and now the owners do want a way to search for any room that is available between a range of dates. So this is a new question...)
I'm working on a hotel reservation system and I need to find which rooms are available between a range of dates. The existing 'availability' table schema is simple, the columns are:
room_id
date_occupied - a single day that is occupied (like '2011-01-01')
So, if, for example, room #6 is occupied from January 1 to January 5, five rows are added to the availability table, one for each day that room is occupied.
I'm trying to figure out the query to find what rooms are available between a start and end date (while also filtering on some other parameters like non-smoking, bed size, etc.)... in pseudo-SQL it would be sort of like:
SELECT (a list of room IDs) FROM rooms, availability
WHERE rooms.nonsmoking = 1
AND rooms.singlebed = 1
AND nothing between start_date and end_date is in availability.date_occupied
As with my other question, I'm a bit stuck trying to determine the exact query and how to join the tables it in the most efficient way. Thanks for the help.
If I understood your db structure properly, you need to find a row in rooms with no corresponding rows in availability.
SELECT r.*
FROM rooms r
LEFT JOIN availability a ON (r.id = a.room_id
AND a.date_occupied BETWEEN :start_date AND :end_date)
WHERE a.id IS NULL