MySQL complex date calculation in inner join - mysql

I am reading the MySql tutorial in the docs and have the following tables and SQL statements:
Event table:
+----------+------------+----------+------------------------------+
| name | date | type | remark |
+----------+------------+----------+------------------------------+
| Fluffy | 1995-05-15 | litter | 4 kittens, 3 females, 1 male |
| Buffy | 1993-06-23 | litter | 5 puppies, 2 female, 3 male |
| Buffy | 1994-06-19 | litter | 3 puppies, 3 female |
| Chirpy | 1999-03-21 | vet | needed beak streightened |
| Slim | 1997-08-03 | vet | broken rib |
| Bowser | 1991-10-12 | kennel | NULL |
| Fang | 1991-10-12 | kennel | NULL |
| Fang | 1998-08-28 | birthday | Gave him new chew toy |
| Claws | 1998-03-17 | birthday | Gave him a flea collar |
| Whistler | 1998-12-09 | birthday | First birthday |
+----------+------------+----------+------------------------------+
Pet table:
+----------+--------+---------+------+------------+------------+
| name | owner | species | sex | birth | death |
+----------+--------+---------+------+------------+------------+
| Fluffy | Harold | cat | f | 1993-02-04 | NULL |
| Claws | Gwen | cat | m | 1994-03-17 | NULL |
| Buffy | Harold | dog | f | 1989-05-13 | NULL |
| Fang | Benny | dog | m | 1990-08-27 | NULL |
| Bowser | Diane | dog | m | 1989-03-31 | 1995-07-29 |
| Chirpy | Gwen | bird | f | 1998-09-11 | NULL |
| Whistler | Gwen | bird | NULL | 1997-12-09 | NULL |
| Slim | Benny | snake | m | 1996-04-29 | NULL |
| Puffball | Diane | hamster | f | 1999-03-30 | NULL |
| Jenny | Robert | dog | f | 2004-01-01 | 2014-05-04 |
+----------+--------+---------+------+------------+------------+
SQL:
select pet.name,
( YEAR(date) - YEAR(birth) ) - ( RIGHT(date,5) < RIGHT(birth,5) ) AS age, remark
from pet inner join event
on pet.name = event.name
where event.type = 'litter';
I understand the SQL statement except for this one:
( YEAR(date) - YEAR(birth) ) - ( RIGHT(date,5) < RIGHT(birth,5) )
A step by step explanation would greatly help. I know that the YEAR() function is used to extract the year from a date.

As you've mentioned YEAR() gets the year from the date.
We will use Fluffy as an example with birth = 1993-02-04 and date = 1995-05-15
Step 1:
Subtract the extracted year from both dates ( YEAR(date) - YEAR(birth) )
You now have 1995 - 1993 which is equal to 2
Step 2:
( RIGHT(date,5) < RIGHT(birth,5) ) this will actually read the date and birth string 5 paces from right to left, so if you perform RIGHT(date,5) you will get the value 0, and if you perform RIGHT(birth,5) you will also get a value of 0.
Step 3:
Now we get on to the < operator, this returns a boolean value of 1 or 0 if it satisfies the condition. Since 0 = 0, the statement is false, so it will return to 0.
The whole function actually checks if the day part of your date is less than the day on your birthdate which will determine if you have a sort of remaining days before a whole year. And if you do, it will return 1 which will be subtracted from the current year - year operation you performed earlier.
But in our case, since the < will return 0, we can definitely say that Fluffy's age is 2 - 0, which is 2.
If, however, Fluffy's birthdate is say, 1993-12-04, this will yield a 1 value for the < operation meaning that the year is not yet complete which will bring a result of 2 - 1, which is 1.
Sorry if its a bit messy.

If you were born in 1980 and I know that right now it's 2015, then I I can almost compute your age by computing YEAR(date) - YEAR(birth) = 2015 - 1980 = 35. The sticking point is that your birthday might not have happened yet. So how can I tell if your birthday has happened? Lets say you were born on July 20, 1980. Then in MySQL date format your birthday would look like 2015-06-20. If I look at the last 5 characters of your birthday, the right-most 5 characters, or RIGHT(birth, 5) I'd get 06-20. If the the last 5 characters of today's date, 06-07 is less than the last 5 characters of your birthday then your birthday hasn't happened yet. Here, by "less than" we mean only that it would "sort before" in normal string order. So, if RIGHT(date, 5) < RIGHT(birth, 5) then your birthday hasn't happened yet. And, luckily, MySQL treats a "true" as the number 1 and a "false" as the number 0. So, the above will subtract 1 from our date computation only if your birthday hasn't happened yet.

It's a clever(?) way of checking if the month and day of the date of birth happened before or after the date from the event (litter) in order to properly calculate the number of years between the events (as the year part in itself isn't enough).
In this part:
( YEAR(date) - YEAR(birth) ) - ( RIGHT(date,5) < RIGHT(birth,5) )
the RIGHT(date,5) returns the month and day part like (03-30) and does a boolean less than comparison which returns either 0 or 1 depending on the result. This is then subtracted from the YEAR(date) - YEAR(birth) calculation so that the years between the events get adjusted correctly.

Related

Mysql - Get season from current month

I have the following table of seasons:
| id | name | start_month | end_month |
------------------------------------------
| 101 | Summer | 12 | 2 |
| 102 | Winter | 6 | 8 |
| 103 | Spring | 9 | 11 |
| 104 | Fall | 3 | 5 |
I need to get the season by month. Say current month is 2 (February), I want Summer to be the output.
I can get other seasons to work by simply having the where condition start_month >= 4 and end_month <= 4. But this won't work with Summer since the season crosses into next year.
What do I have to do to handle the case of Summer?
One solution I thought was to use dates instead of month number like 1980-12-01 and use between function but it gets a bit complicated for the user end.
It'd be great if it could work with just month numbers.
You could do:
(month(d) between start_month and end_month) or
(start_month>end_month and (month(d)>=start_month or month(d)<=end_month))
See db-fiddle

Select count if column name contain and value equal to something MYSQL

Here is my table call TrainingPresence
+-------+-----------+-----------+-----------+
| Name | FootNov12 | HandNov15 | FootNov22 |
+-------+-----------+-----------+-----------+
| Clem | x | Abs | Abs |
+-------+-----------+-----------+-----------+
| Kevin | x | x | x |
+-------+-----------+-----------+-----------+
I want to select the name and the number of time when player came to the Foot training.
FootNov12 = Football Novembrre 12th
FootNov22 = Football November 22th
HandNov15 = Handball November 15th
The select query should give me :
Name | Count
Clem | 1
Kevin| 2
But I don't know how to do it
Thanks for your help.
A normalised Schema might look like this
users
user_id name
1 Clem
2 Kevin
training
user_id date sport
1 2018-11-15 Handball
1 2018-11-11 Football

Mysql query for hotel room availability

I am trying to create an online room reservation system for a small hotel. One of the tables of the database
is supposed to hold the bookings. It has an autonumber field, customer data fields, two date fields for arrival and departure, and a number field for the number of rooms booked.
A search page submits the arrival and departure dates to a result page which is then supposed to tell the customer how many rooms are available within the period if any. This is where it all goes wrong.
I just can't get an accurate count of the number of rooms already booked within the period requested.
guest | arrive | depart |booked
Smith | 2002-06-11 | 2002-06-18 | 1
Jones | 2002-06-12 | 2002-06-14 | 2
Brown | 2002-06-13 | 2002-06-16 | 1
White | 2002-06-15 | 2002-06-17 | 2
If the hotel has 9 rooms, here is a day-by-day listing of the number of available rooms.
I want the result like this.
date available status
2002-06-10 | 9 | Hotel is empty
2002-06-11 | 8 | Smith checks in
2002-06-12 | 6 | Jones checks in
2002-06-13 | 5 | Brown checks in
2002-06-14 | 7 | Jones checks out
2002-06-15 | 5 | White checks in
2002-06-16 | 6 | Brown checks out
2002-06-17 | 8 | White checks out
2002-06-18 | 9 | Smith checks out
Please help me to find a solution
A calendar table isn't strictly necessary for problems of this nature, but they can help to conceptualise the problem in a quick and easy manner. So I have a calendar table holding dates from 1900 until 4000 and something...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(booking_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,guest VARCHAR(12) NOT NULL
,arrive DATE NOT NULL
,depart DATE NOT NULL
,booked INT NOT NULL
,UNIQUE KEY(guest,arrive)
);
INSERT INTO my_table (guest,arrive,depart,booked) VALUES
('Smith','2002-06-11','2002-06-18',1),
('Jones','2002-06-12','2002-06-14',2),
('Brown','2002-06-13','2002-06-16',1),
('White','2002-06-15','2002-06-17',2);
SELECT x.dt
, 9 - COALESCE(SUM(booked),0) available
FROM calendar x
LEFT
JOIN my_table y
ON x.dt >= y.arrive AND x.dt < y.depart
WHERE x.dt BETWEEN '2002-06-10' AND '2002-06-20'
GROUP
BY dt;
+------------+-----------+
| dt | available |
+------------+-----------+
| 2002-06-10 | 9 |
| 2002-06-11 | 8 |
| 2002-06-12 | 6 |
| 2002-06-13 | 5 |
| 2002-06-14 | 7 |
| 2002-06-15 | 5 |
| 2002-06-16 | 6 |
| 2002-06-17 | 8 |
| 2002-06-18 | 9 |
| 2002-06-19 | 9 |
| 2002-06-20 | 9 |
+------------+-----------+
11 rows in set (0.00 sec)

Troubles conceptualizing a query

I have a 'Course' table and an 'Event' table.
I would like to have all the courses that actually take place, i.e. they are not cancelled by an event.
I have done this by a simple request for all the course and a script analysis (basically some loops), but this request take a time that I believe too long. I think what I want is possible in one query and no loops to optimize this request.
Here are the details :
'Course' c have the fields 'date', 'duration' and a many to many relation with the 'Grade' table
'Event' e have the fields 'begin', 'end', 'break' and a many to many relation with the 'Grade' table
A course is cancelled by an event if they occur at the same time and if the event is a break (e.break = 1)
A course is cancelled by an event if all the grades of the course are in the events that occurs at the same time (many events can occurs, I have to sum up the grades of these events and compare them to the grades of the courses). This is the part I'm doing with a loop, I have some trouble to conceptualize that.
Any help is welcome,
Thanks in advance,
PS : I'm using mysql
EDIT : Tables details
-Course
+-----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| date | datetime | NO | | NULL | |
| duration | time | NO | | NULL | |
| type | int(11) | NO | | NULL | |
+-----------+-------------+------+-----+---------+----------------+
+-------+---------------------+----------+------+
| id | date | duration | type |
+-------+---------------------+----------+------+
| 1 | 2013-12-10 10:00:00 | 02:00:00 | 0 |
| 2 | 2013-12-11 10:00:00 | 02:00:00 | 0 |
+-------+---------------------+----------+------+
-Event
+-------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| begin | datetime | NO | | NULL | |
| end | datetime | YES | | NULL | |
| break | tinyint(1) | NO | | NULL | |
+-------------+-------------+------+-----+---------+----------------+
+----+---------------------+---------------------+-------+
| id | begin | end | break |
+----+---------------------+---------------------+-------+
| 1 | 2013-12-10 00:00:00 | 2013-12-11 23:59:00 | 1 |
+----+---------------------+---------------------+-------+
-course_grade
+-----------+----------+
| course_id | grade_id |
+-----------+----------+
| 1 | 66 |
| 2 | 65 |
| 2 | 66 |
+-----------+----------+
-event_grade
+----------+----------+
| grade_id | event_id |
+----------+----------+
| 66 | 1 |
+----------+----------+
So here, only the course 2 should appear, because course 1 has only one grade, and this grade has an event.
I like riddles, this is a nice one, has many solutions, I think
As you say 'Any help is welcome', I give an answer altough its not the solution (and it does not fit into a comment)
I dont know, if you just want (A) the naked statement (over and out), or if you want (B) to understand how to get to the solution, I take (B)
I start with 'what would I change' before starting about the solution:
you are mixing date,datetime,start,end and duration, try to use only one logic (if it is your model ofcourse) ie.
an event/course has a start and an end time (or start/duration)
duration should (IMHO) not be a time
try to find a smallest timeslice for events/course (are there 1 sec events? or is a granularity of 5' (ie. 10:00, 10:05, 10:10 ans so on) a valid compromise?
My solution, a prgmatic one not academic
(sounds funny, but does work good in a simillar prob I had see annotation)
Create a table (T_TIME_OF_DAY) having all from 00:00, 00:05, .. 23:55
Create a Table (T_DAYS) in a valid and usefull range (a year?)
the carthesian product - call it points in time - (ie. select date, time from T_DAYS,T_TIME_OF_DAY no condition) of them (days x times) 300*24*12 ~ 100.000 rows if you need to look at a whole year (and 5' are ok for you) - thats not much and no prob
the next step is to join the curses fitting to your points in time (and the rows are down to <<100.000)
if you next join these with your events (again using point in time) you should get what you want.
simplyfied quarters of a day:
12 12 12 12 12 12 12 12
08 09 10 11 12 13 14 15
|...|...|...|...|...|...|...|...
grade 65 (C).............2..................
grade 66 (C).........1...2..................
grade 65 (E)................................
grade 66 (e)........1111..................
(annotation: I use this logic to calculate the availabillity of services regarding to their downtimes per Month / Year, and could use the already in timeslices prepared data for display)
(second answer, because it is a totaly different and mor3 standard aproach)
I made an SQLFiddle for you
so what to do:
and thats the a solution:
step one (in mind) select course,grades (lets call them C)
step two (in mind) select events, grades (lets call them E)
and - tada -
select all from C where there a no rows in E that have the same grade and the same date(somehow) and eventtype='break'
so your solution:
select
id, date start_time, date+duration end_time, grade_id
from Course c join course_grade cg on c.id=cg.course_id
where not exists (
select grade_id, begin start_time, end end_time
from event_grade eg join event e on eg.event_id=e.id
where
eg.grade_id=cg.grade_id
and e.break=1
and
(
(e.begin<=c.date and e.end >=c.date+c.duration)
or e.begin between c.date and c.date+c.duration
or e.end between c.date and c.date+c.duration
)
)
I did take no attention to optimize here

Combining multiple rows to make up a date range

I'm not sure if this is possible with MySQL and its a little complicated, but I'll try and explain as best as possible.
I amending out accommodation availability & price checking system based on an existing database. The visitor will be able to select an arrival and a departure date and I want to be able to show all the rates/offers that are available for those dates. Easy. However, an offer may be set up to be available for only the first part of the given date range and a second rate may be set up for that room type covering the later part of the date range.
e.g. I select September 29th until October 3rd for my stay. A rate has been set up for room 1 to cover September 1st - 30th, and a second rate for October 1st - 31st.
Can I query the table to check if besides rates that span the entire range, there are pairs (or maybe even more) rates for the same room id that together cover the date range, and retrieve the rates & descriptions for those?
The query below checks for basic rates that are valid between the given dates (where roomtype is the id of the room):
SELECT id, caption, price, startdate, enddate, nights, roomtype,
(#arrival := DATE('2011-09-29')) AS arrivaldate,
(#departure := DATE('2011-10-05')) AS departuredate
FROM bnbrates
WHERE
bnb_ref = 16639
AND active = 'TRUE'
AND rooms != 0
AND #arrival < enddate
AND #departure > startdate
AND roomtype != ''
ORDER BY roomtype, enddate, startdate, price;
and this is a sample of the data stored:
id | caption | price | startdate | enddate | nights | roomtype | arrivaldate | departuredate
-------------------------------------------------------------------------------------------------------------------
23553 | Single | 50 | 2011-10-01 | 2011-10-31 | 1 | 1064 | 2011-09-29 | 2011-10-05
23544 | Double | 55 | 2011-09-01 | 2011-09-30 | 1 | 1647 | 2011-09-29 | 2011-10-05
23545 | Double | 80 | 2011-10-01 | 2011-10-31 | 1 | 1647 | 2011-09-29 | 2011-10-05
30312 | Triple | 109 | 2011-09-01 | 2011-09-30 | 1 | 1649 | 2011-09-29 | 2011-10-05
34234 | Executive | 109 | 2011-09-01 | 2011-09-30 | 1 | 1653 | 2011-09-29 | 2011-10-05
23569 | Executive | 99 | 2011-10-01 | 2011-10-31 | 1 | 1653 | 2011-09-29 | 2011-10-05
The desired result would for example combine 23544 with 23545 and 34234 with 23569
Thank you in advance for your help, and apologies if this doesn't make sense...