I have spent more than 14 hours trying to find a way to accomplish this but no luck!
I'm working on a booking system , I have 2 tables :
Rooms
---------
ID - ROOM_TYPE - NAME - ROOM_COUNT
1 1 single room 6
2 2 double room 4
3 3 studio 2
Booking
---------
ID - ROOM_TYPE - Customer_NAME - CHECK_IN - CHECK_OUT
1 3 John A 1-1-2013 4-1-2013
2 3 John B 2-1-2013 5-1-2013
3 2 John C 8-1-2013 9-1-2013
after the user put 2 dates to search for an available room , I make a query to display all type rooms that I have , I have 3 types (single/double/studio), each type has number of rooms available in the hotel (ex: the hotel has 6 single rooms by default)
I end up with this query to get all available type rooms
SELECT *
FROM rooms
where id NOT IN
( SELECT room_type FROM booking
WHERE "'.$check_in.'" <= check_out
AND "'.$check_out.'" >= check_in
)
I need to make another condition that WHERE count(room_type) from booking LESS than the value saved in ROOM_COUNT field in ROOMS table ..
depend on the table structures above , the hotel has just two studios and all aren't available in 4-1-2013 ..
so, the query shouldn't display studio type room when somebody search in that date ..
I need something like count how many bookings for each room type then display the results ..
Thank you in advance
Here is the idea. You need to know the maximum number of rooms taken during the period. You can get the number of rooms taken on any given day by looking at the number of checks ins prior to that day minus the number of checkouts.
Next, to get the max, we only need to look at the check in day (since check outs reduce the maximum value). The following gets the maximum on any day that has checkins:
select r.room_type, r.room_count, r.room_count - RoomsTaken
from Rooms r join
(select room_type, max(Num_CheckOuts - num_CheckIns) as RoomsTaken
from (select b.*,
(select count(*) from booking b2 where b2.room_type = b.room_type and b2.check_in <= b.check_out
) as num_CheckIns,
(select count(*) from booking b2 where b2.room_type = b.room_type and b2.check_out <= b.check_in
) as Num_CheckOuts
from booking b
) b
where $check_in <= check_out and $check_out >= check_in
group by room_type
) b
on r.room_type = b.room_type
(Note: this query is not tested so it may have syntax errors.)
It uses correlated subqueries to get the cumulative counts. In other databases, window functions might be used for this purpose.
Related
I have legacy tables which tracks flight and had to extract data. We have three tables named booking, airlines and flighttype. Note this is a dummy samples
booking :
id
customer
request_date
airline
flightType
price
currency
1
1
11-20-2020 10:23
1
1
120
Eur
2
1
11-21-2020 10:24
1
2
110
CHF
3
2
11-01-2020 11:25
2
2
120
Eur
4
1
15-01-2020 10:23
1
1
100
Eur
5
1
11-01-2020 11:23
1
2
60
Eur
6
1
12-01-2020 10:23
1
3
35
Eur
airline :
id
airline
1
French
2
Swiss
type :
id
flightType
1
domestic
2
international
Now the data we are trying to figure out is number of bookings consecutively within x days (let say if two days it would mean how many bookings were made in 2 days) for various parameters like
airline
flightType
airline & flightype
currency
price total price
For example lets say I wish to see what is the percentage of customer who have made multiple bookings within x days across multiple airline I should be able to do so or if I want to see the total revenue of customers who have made multiple booking within x days or customers who have made multiple booking with different set of currencies with x days
I am trying to make self join to extract such data and then group it but I am always reaching a dead end
SELECT
t1.customer, t1.request_date, t1.airline, count(*)
FROM booking t1
JOIN booking t2
ON t1.customer= t2.customer
WHERE t2.request_date > t1.request_date and DATEDIFF(t2.request_date, t1.request_date) > 0 and DATEDIFF(t2.request_date, t1.request_date) <=2
GROUP BY t1.customer, t1.request_date
The problem I am facing is the date has time and it gives me wrong results. And I am not sure what is the right way to get %share of customers who make booking in such way like
% share of customers who book more than one flight / more than one type of flight within span of x days.
Sorry if the question is too vague or if this violates any rules.
By the way I am using mysql 5.5
I want to see the total revenue of customers who have made multiple booking within x days or customers who have made multiple booking with different set of currencies with x days
You can answer questions like this using window functions. For the first question, this looks like:
select count(distinct customer)
from (select b.*,
lag(request_date) over (partition by customer order by request_date) as prev_request_date
from booking b
) b
where request_date <= prev_request_date + interval <n> day;
I am trying to write an complex mySQL query in which there are 2 tables action and revenue what I need is:
From auction table take out location, postal code on the basis of user, cat_id, cat and spent and join with revenue table which has revenue column so as that given cat_id, cat and date I can figure out the returns that each 'postal' is generating.
Complexities:
User is unique key here
In auction table has column 'spent' but its populates only when 'event' column has 'show' but it has 'cat' entry. And 'cat_id' starts populating at any event except show. So need to map cat_id from 'cat' for event 'show' to get the spent for that cat_id.
The date has to be setup such that while joining the tables the timestamp should be compared for plus minus 10 mins. Right now in my query I have 24 hrs duration
Aggregating on postal in desc order to postal giving highest returns
**Auction Table**
dt user cat_id cat location postal event spent
2020-11-01 22:12:25 1 0 A US X12 Show 2
2020-11-01 22:12:25 1 0 A US X12 Show 2 (duplicate also in table)
2020-11-01 22:12:25 1 6 A US X12 Mid null
2020-11-01 22:13:20 2 0 B UK L23 Show 2
2020-11-01 22:15:24 2 3 B UK L23 End null
**Revenue table**
dt user cat_id revenue
2020-11-01 22:14:45 1 6 null
2020-11-01 22:13:20 2 3 3
Want to create final table(by aggregating on revenue for each 'postal' area):
location postal spend revenue returns
UK X12 2 0 0
US L23 2 3 3/2=1.5
I have written a query but unable to figure out solution for above mention 3 complexities:
Select s.location, s.postal, s.spend, e.revenue
From revenue e JOIN
auction s
on e.user = s.user
where s.event in ('Mid','End','Show') and
TO_DATE(CAST(UNIX_TIMESTAMP(e.dt, 'y-M-d') AS TIMESTAMP)) = TO_DATE(CAST(UNIX_TIMESTAMP(s.dt, 'y-M-d') AS TIMESTAMP)) and
s.cat_id in ('3') and
s.cat = 'B'
Any suggestion will be helpful
This answers the question for MySQL, which is the original tag on the question as well as mentioned in the question.
If I understand correctly, your issue is "joining" within a time frame. You can do what you want using a correlated subquery. Then the rest is aggregation, which I think is:
select location, postal, max(spend), max(revenue)
from (select a.*,
(select sum(r.revenue)
from revenue r
where r.user = a.user and
r.dte >= s.dt - interval 10 minute and
r.dte <= s.dte + interval 10 minute
) as revenue
from auction a
where s.event in ('Mid', 'End', 'Show') and
s.cat_id in (3) and
s.cat = 'B'
) a
group by location, postal;
I use a query to find free rooms from hotel DB. I wrote a query which select rooms that not in booking table:
SELECT * FROM room WHERE roomId NOT IN
(SELECT roomId FROM booking b WHERE STR_TO_DATE('${endDate}', '%m-%d-%Y') <= b.endDate AND
STR_TO_DATE('${startDate}', '%m-%d-%Y') >= b.startDate);
My booking table looks like:
+-----------+------------+------------+--------+---------+
| bookingId | startDate | endDate | roomId | guestId |
+-----------+------------+------------+--------+---------+
| 1 | 2016-03-12 | 2016-03-22 | 1 | 1 |
+-----------+------------+------------+--------+---------+
But if my startDate is 2016-03-10 and endDate is 2016-03-25 - I've got already booked room from 2016-03-12 to 2016-03-22. How I can fix it? I don't need to show room that booked between my dates.
General approach for the problem of finding free rooms in range ($BOOKING_BEGIN <=> $BOOKING_END) would be like:
SELECT
rooms.room_id
FROM
rooms
LEFT JOIN
bookings
ON (
bookings.room_id = rooms.room_id AND
NOT (
(bookings.begin < $BOOKING_BEGIN and bookings.end < $BOOKING_BEGIN)
OR
(bookings.begin > $BOOKING_END and bookings.end > $BOOKING_END)
)
)
WHERE
bookings.room_id IS NULL;
Which simply means 'take all the rooms in the hotel, and join them with ones which are already booked. If there's null, that means room is free in a given range (Join didn't find existing booking).
Here is the query that works, and has been tested for all combinations of vacancy before any other. vacancy after anything. Start date before, on, after existing start. End date before, on, after existing end date. Totally straddling outside another booking. And totally within another booking.
select
r.RoomID
from
Room r
LEFT JOIN
( select
b.RoomID
from
booking b,
( select #parmStartDate := '2016-01-21',
#parmEndDate := '2016-01-23' ) sqlvars
where
b.EndDate >= #parmStartDate
AND b.StartDate <= #parmEndDate
AND ( timestampdiff( day, b.StartDate, #parmEndDate )
* timestampdiff( day, #parmStartDate, b.EndDate )) > 0 ) Occupied
ON r.RoomID = Occupied.RoomID
where
Occupied.RoomID IS NULL;
The sample booking data I created included
BookID RoomID StartDate EndDate
1 1 2016-02-03 2016-02-04
2 1 2016-02-04 2016-02-08
3 1 2016-02-12 2016-02-16
4 1 2016-02-20 2016-02-28
I then tested with the following booking dates and came up with the following valid vacancy vs conflict and already occupied. This test is just for a single room, but obviously applicable for any room in the hotel.
Both dates before anything on file... Room available
2016-01-10 - 2016-01-15
Both dates after anything on file... Room available
2016-03-10 - 2016-03-15
Occupied ID 1 -- Same start date
2016-02-03 - 2016-02-04
Occupied ID 2 -- Same start date, but less than existing occupied end date
2016-02-04 - 2016-02-05
Occupied ID 2 -- Same start, Exceeds end occupancy date
2016-02-04 - 2016-02-09
Occupied ID 3 -- Start before, but end date WITHIN existing booking
2016-02-09 - 2016-02-13
Available. The END Date is the START Date of the existing booking
(Between 2 & 3 booking)
2016-02-09 - 2016-02-12
Occupied ID 3 -- Started within date, but end outside existing booking
2016-02-15 - 2016-02-17
Available. End of existing booking and nothing booked on 2/17
2016-02-16 - 2016-02-17
Occupied ID 3 -- Completely encompasses booking entry
2016-02-11 - 2016-02-17
Occupied ID 4 -- totally WITHIN another entry
2016-02-21 - 2016-02-23
Now, to explain what is going on. I did with a LEFT-JOIN and looking for NULL (ie: no conflict of another booking) which is quite similar to your NOT IN subselect. So I will skip that part.
First, the FROM clause. So I dont have to "declare" variables like a stored procedure, I am doing them IN-LINE via the #parmStartDate, #parmEndDate and assigning the alias sqlvars just for declaration purposes. Since this returns one row, having a Cartesian applied to the booking table is no problem.
from
booking b,
( select #parmStartDate := '2016-01-21',
#parmEndDate := '2016-01-23' ) sqlvars
Now, the WHERE clause. If your table has years worth of bookings after time, and 100's of rooms, this could get quite large quickly, so I want to pre-start with only those dates where existing bookings would come into place this is the
where
b.EndDate >= #parmStartDate
AND b.StartDate <= #parmEndDate
At a minimum, I only care about those bookings where an existing checkout date is AT LEAST the date you are trying to find availability. Ex: You are looking for a check-in date of July 4th. Why would you even care if someone checked out in Feb, Mar, Apr, etc... So now, how far out do you go... You also only care for those records where the next existing booking has a START Date UP TO the day you would be checking out. So, if checking out July 6th, you don't care about any bookings for July 7th or after. So far, so good.
Now, comes the how do I know if a room is occupied or not. I was having difficulties comparing existing Start Date to looking for dates and was getting false answers, so I had to resort to date math and comparing start to end and end to start, and if the multiplier result was positive, there is a conflict.
AND ( timestampdiff( day, b.StartDate, #parmEndDate )
* timestampdiff( day, #parmStartDate, b.EndDate )) > 0 )
Since we already know we have records within the POSSIBLE date range, this is doing a conflict check in either direction for full outside, inside, conflict left or conflict right. it just works.
You would have to see it to understand it better and this is the query that I ran so you could look at the results for yourself. Just plug in the respective start / end dates you are looking for.
select
b.BookID,
b.RoomID,
b.StartDate,
b.EndDate,
#parmStartDate as pStart,
#parmEndDate as pEnd,
( timestampdiff( day, b.StartDate, #parmEndDate )
* timestampdiff( day, #parmStartDate, b.EndDate )) <= 0 as Available,
( timestampdiff( day, b.StartDate, #parmEndDate )
* timestampdiff( day, #parmStartDate, b.EndDate )) > 0 as Occupied
from
booking b,
( select #parmStartDate := '2016-01-21',
#parmEndDate := '2016-01-23' ) sqlvars
Good Luck...
I have two tables; room and reservation.
data on room
id_room | name_room | available_room
1 Class3 6
2 Class2 8
3 Class1 6
data on reservation is empty
id_reservation | id_room | reserved_qty | checkin_date | checkout_date
1
2
3
If the guest wants to book a room, the website will display all the available room during data on table reservation is empty (like table above), but, if there is data on table reservation then the website will show remaining quantity of room (result from reduction between room.available_room reservation.reserved_qty (room.available_room - reservation.reserved_qty)) according to the date of booking and date already booked (checkin_date and checkout_date).
I don't understand why you would have "defective" rows in the reservation table where the id_room column is NULL. But that doesn't change the proposed solution, since those defective rows would simply be disregarded, since a NULL value for id_room would never successfully join anything.
What you want is an OUTER JOIN instead of an INNER JOIN, so that the lack of a reservation doesn't prevent a room from appearing in the result.
SELECT room.id_room
, room.name_room
, room.available_room - SUM(COALESCE(reservation.reserved_qty, 0)) AS remaining_rooms
FROM room
LEFT OUTER JOIN reservation
ON room.id_room = reservation.id_room
WHERE
checkin_date IS NULL OR
#day BETWEEN checkin_date AND checkout_date
GROUP BY room.id_room, room.name_room;
The comment by #Thilo is sensible, since you might not really have records in the reservation table which are just sitting there emtpy. But if you are, one way to deal with "empty" data would be to use the COALSECE() function when computing the difference:
SELECT r1.id_room, r1.name_room,
r1.available_room - COALESCE(r2.reserved_qty, 0) AS numAvailable
FROM room r1
INNER JOIN reservation r2
ON r1.id_room = r2.id_room
I have a record set for inspections of many pieces of equipment. The four cols of interest are equip_id, month, year, myData.
My requirement is to have EXACTLY ONE record per month for each piece of equipment.
I have a query that makes the data unique over equip_id, month, year. So there is no more than one record for each month/year for a piece of equipment. But now I need to simulate data for the missing month. I want to simply go back in time to get the last piece of my data.
So that may seem confusing, so I'll show by example.
Given this sample data:
equip_id month year myData
-----------------------------
1 1 2010 500
1 2 2010 600
1 5 2010 800
2 2 2010 300
2 4 2010 400
2 6 2010 500
I want this output:
equip_id month year myData
-----------------------------
1 1 2010 500
1 2 2010 600
1 3 2010 600
1 4 2010 600
1 5 2010 800
2 2 2010 300
2 3 2010 300
2 4 2010 400
2 5 2010 400
2 6 2010 500
Notice that I'm filling in missing data with the data from the month (or two months etc.) before. Also note that if the first record for equip 2 is in 2/2010 than I don't need a record for 1/2010 even though I have one for equip 1.
I just need exactly one record for each month/year for each piece of equipment. So if the record does not exist I just want to go back in time and grab the data for that record.
Thanks!
By no means perfect:
SELECT equip_id, month, mydata
FROM (
SELECT equip_id, month, mydata FROM equip
UNION ALL
SELECT EquipNum.equip_id, EquipNum.Num,
(SELECT Top 1 mydata
FROM equip
WHERE equip.month<n.num And equip.equip_id=equipnum.equip_id
ORDER BY equip.month desc) AS Data
FROM
(SELECT e.equip_id, n.Num
FROM
(SELECT DISTINCT equip_id FROM equip) AS e,
Numbers AS n) AS EquipNum
LEFT JOIN equip
ON (EquipNum.Num = equip.month)
AND (EquipNum.equip_id = equip.equip_id)
WHERE EquipNum.Num<DMax("month","equip")
AND
(SELECT top 1 mydata
FROM equip
WHERE equip.month<n.num And equip.equip_id=equipnum.equip_id
ORDER BY equip.month desc) Is Not Null
AND equip.equip_id Is Null AND equip.Month Is Null) AS x
ORDER BY equip_id, month
For this to work you need a Numbers table, in this case it needs only hold integers from 1 to 12. The numbers table I used is called Numbers and the field is called Num.
EDIT re years comment
SELECT equip_id, year, month, mydata
FROM (
SELECT equip_id, year, month, mydata FROM equip
UNION ALL
SELECT en.equip_id, en.year, en.Num, (SELECT Top 1 mydata
FROM equip e
WHERE e.month<n.num And e.year=en.year And e.equip_id=en.equip_id
ORDER BY e.month desc) AS Data
FROM (SELECT e.equip_id, n.Num, y.year
FROM
(SELECT DISTINCT equip_id FROM equip) AS e,
Numbers AS n,
(SELECT DISTINCT year FROM equip) AS y) AS en
LEFT JOIN equip AS e ON en.equip_id = e.equip_id
AND en.year = e.year
AND en.Num = e.month
WHERE en.Num<DMax("month","equip") AND
(SELECT Top 1 mydata
FROM equip e
WHERE e.month<n.num And e.year=en.year And e.equip_id=en.equip_id
ORDER BY e.month desc) Is Not Null
AND e.equip_id Is Null
AND e.Month Is Null) AS x
ORDER BY equip_id, year, month
I've adjusted to account for year and month... The primary principles remain the same as the original queries presented where just the month. However, for applying a month and year, you need to test for the SET of YEAR + MONTH, ie: what happens if Nov/2009, then jump to Feb/2010, You can't rely on just a month being less than another, but the "set". So, I've apply the year * 12 + month to prevent a false value such as Nov=11 + year=2009 = 2009+11 = 2020, then Feb=2 of year=2010 = 2010+2 = 2012... But 2009*12 = 24108 + Nov = 11 = 24119 compared to 2010*12 = 24120 + Feb =2 = 24122 -- retains proper sequence per year/month combination. The rest of the principles apply. However, one additional, I created a table to represent the span of years to consider. For my testing, I added a sample Equip_ID = 1 entry with a Nov-2009, and Equip_ID = 2 with a Feb-2011 entry and the proper roll-over works too. (Table C_Years, column = year and values of 2009, 2010, 2011)
SELECT
PYML.Equip_ID,
PYML.Year,
PYML.Mth,
P1.MyData
FROM
( SELECT
PAll.Equip_ID,
PAll.Year,
PAll.Mth,
( SELECT MAX( P1.Year*12+P1.Mth )
FROM C_Preset P1
WHERE PAll.Equip_ID = P1.Equip_ID
AND P1.Year*12+P1.Mth <= PAll.CurYrMth) as MaxYrMth
FROM
( SELECT
PYM1.Equip_ID,
Y1.Year,
M1.Mth,
Y1.Year*12+M1.Mth as CurYrMth
FROM
( SELECT p.equip_id,
MIN( p.year*12+p.mth ) as MinYrMth,
MAX( p.year*12+p.mth ) as MaxYrMth
FROM
C_Preset p
group by
1
) PYM1,
C_Years Y1,
C_Months M1
WHERE
Y1.Year*12+M1.Mth >= PYM1.MinYrMth
AND Y1.Year*12+M1.Mth <= PYM1.MaxYrMth
) PAll
) PYML,
C_Preset P1
WHERE
PYML.Equip_ID = P1.Equip_ID
AND PYML.MaxYrMth = P1.Year*12+P1.Mth
If this is going to be a repetative thing/report, I would just create a temporary table with 12 months -- then use that as the primary table, and do a left OUTER join to the rest of your data. This way, you know you'll always get every month, but only when a valid join to the "other side" is identified, you'll get that data too. Ooops... missed your point about the filling in missing elements from the last element... Thinking...
The following works... and I'll describe the elements to what is going on. First, I created a temp table "C_Months" with a column Mth (month) with numbers 1-12. I used "Mth" as an abbreviation of Month to not cause possible conflict with POSSIBLE reserved word MONTH. Additionally, in my query, the table reference "C_Preset" is the prepared set of data you mentioned you already have of distinct elements.
SELECT
LVM.Equip_ID,
LVM.Mth,
P1.Year,
P1.MyData
FROM
( SELECT
JEM.Equip_ID,
JEM.Mth,
( SELECT MAX( P.Mth )
FROM C_Preset P
WHERE P.Equip_ID = JEM.Equip_ID
AND P.Mth <= JEM.Mth ) as MaxMth
FROM
( SELECT distinct
p.equip_id,
c.mth
FROM
C_months c,
C_Preset p
group by
1, 2
HAVING
c.mth >= MIN( p.Mth )
and c.mth <= MAX( p.Mth )
ORDER BY
1, 2 ) JEM
) LVM,
C_Preset P1
WHERE
LVM.Equip_ID = P1.Equip_ID
AND LVM.MaxMth = P1.Mth
ORDER BY
1, 2
The inner most query is a query of the available months (C_Months) associated with a given equipment ID. In your example, equipment ID 1 had a values of 1,2,5. So this would return 1, 2, 3, 4, 5. And for Equipment ID 2, it started with 2, but ended with 6, so it would return 2, 3, 4, 5, 6. Hence the aliased reference JEM (Just Equipment Months)
Then, the field selection for MaxMth (Maximum month)... This is the TRICKY ONE
( SELECT MAX( P.Mth )
FROM C_Preset P
WHERE P.Equip_ID = JEM.Equip_ID
AND P.Mth <= JEM.Mth ) as MaxMth
From this, stating I want the maximum month AVAILABLE (from JEM) associated with the given equipment that is AT OR LESS than the month In question (detecting the highest "valid" equipment item/month within the qualified list. The result of this would result in...
Equip_ID Mth MaxMth
1 1 1
1 2 2
1 3 2
1 4 2
1 5 5
2 2 2
2 3 2
2 4 4
2 5 4
2 6 6
So, for your example of ID = 1, you had months 1, 2, 5 (3 and 4 were missing), so the last valid month that 3 and 4 would refer to is sequence #2's month. Likewise for ID = 2, you had months 2, 4 and 6... Here, 3 would refer back to 2, 5 would refer back to 4.
The rest is the easy part. Now, we join your LVM (Last Valid Month) result as shown above to your original C_Preset (less records). But since we now have the last valid month to directly associate to an existing record in the C_Preset, we join by equipment id and the MaxMth colum, and NOT THE ACTUAL month.
Hope this helps... Again, you'll probably have to change my "mth" column references to "month" to match your format.