Need help for join and some calculations on a MySql insert - mysql

I'll try to provide some context so you can understand what I'm trying to achieve here. My company uses open source software to manage the employees leaves (Jorani, feel free to google it :) ).
There are different types of leave (holidays, sick leave, etc.) and we want to calculate the days "not used" from the holidays of 2016 and "copy" them to another type of leave called "Remaining Holidays 2016".
The important tables are:
entitleddays (here you specify how many days of each type you give to an employee)
id employee startdate enddate type days description
661 3 2016-01-01 2017-02-28 1 14.00 Holidays 2016
1296 3 2016-01-01 2016-12-31 4 18.00 Sick leave 2016
leaves (this table has information about the leaves taken by the employees)
id startdate enddate status employee cause duration type
2436 2016-08-01 2016-08-01 3 78 OK from managers 1.00 1
2766 2016-09-05 2016-09-12 3 63 Holidays 6.00 1
So basically we have:
Entitled leaves:
Data stored in the entitleddays table shown above. In our example let's say I have 14 days for my 2016 holidays.
Taken leaves:
Leaves taken by the user, stored in the table called leaves shown above. For our example let's say I took a day off the first of August and 6 days on September.
Available leaves:
Available days are calculated: entitled days minus "taken leaves". For this examplee, 14 entitled days - 7 = 7 days. So I still have seven days available for holidays :D
So my goal is to insert these 7 days for this user as entitled days for the new type: "Remaining days from 2016" and do this for every user. So the solution that comes up to my mind is to do something like this for every user:
INSERT INTO entitleddays (employee, startdate, enddate, type, days, description)
SELECT id, '2017-01-01', '2017-02-31', '8', (entitled holidays for 2016 minus all the taken leaves of this type), 'Remaining holidays from 2016'
FROM users
Where 8 is the new type of leave where I want to copy the days (Remaining holidays from 2016).
For example I can get the taken holidays from 2016 for a specific user doing this:
SELECT SUM(duration)
FROM leaves
WHERE employee=3 AND status=3 AND type=1
Note: Type 1 is the type of leave "Holidays 2016" and status 3 means that the leave request was accepted.
I can probably achieve all of this in a single SQL instruction but it can also be split in more if simpler or easiest to manage/understand.
Many thanks in advance.

This is how you can handle the calculation:
sum the entitleddays in a subquery by grouping the datasets in its table per employee
maybe even group by year? In this case I just filtered for 2016 via WHERE-clause
sum the taken holidays in a subquery, again by grouping per employee
group by year or filter directly for the one you need
join this subquery onto the other resultset of the other query
calculate (entitled days - taken leaves) in the outer query
Query:
SELECT
entitled.employee,
'2017-01-01',
'2017-02-31',
'8' AS type,
entitled.days - takenDays.days,
'Remaining holidays from 2016'
FROM
(
SELECT
employee,
SUM(days) AS days
FROM
entitleddays
WHERE
startdate >= '2016-01-01'
AND type = 1
GROUP BY
employee
) AS entitled
LEFT JOIN (
SELECT
employee,
SUM(duration) AS days
FROM
`leaves`
WHERE
startdate >= '2016-01-01'
AND type = 1
GROUP BY
employee
) AS takenDays ON takenDays.employee = entitled.employee
I am not sure if this is how you want to calculate the sums for the days of entitleddays and taken days. The query just checks if startdate >= '2016-01-01'.
Also you mentioned a table users in your attempt but didn't provide details for the table, so I left it out. I guess you could use it as a basis otherwise. In the current query the grouped result of entitleddays is the basis.
For the insert
INSERT INTO entitleddays (employee, startdate, enddate, type, days, description)
SELECT
entitled.employee,
'2017-01-01',
'2017-02-31',
'8' AS type,
entitled.days - takenDays.days,
'Remaining holidays from 2016'
FROM
(
SELECT
employee,
SUM(days) AS days
FROM
entitleddays
WHERE
startdate >= '2016-01-01'
AND type = 1
GROUP BY
employee
) AS entitled
LEFT JOIN (
SELECT
employee,
SUM(duration) AS days
FROM
`leaves`
WHERE
startdate >= '2016-01-01'
AND type = 1
GROUP BY
employee
) AS takenDays ON takenDays.employee = entitled.employee

Related

calculate Age from year, month and day fields

I have a table of people and I need to know how many of them are actual minors.
I have the following query:
SELECT count(*) as minors from
FilesMain a INNER JOIN Sides b
ON a.FileID = b.FileID
INNER JOIN SideData c
ON b.SideDataID = c.SideDataID
WHERE a.StatusCode IN (100,101) AND (YEAR(CURDATE()) - BirthYear<17)
Basically in the query above, I am calculating current date year minus BirthYear field.
I have the persons birth date separated to year, month and day in 3 different fields. please don't ask why. I inherited the data. What would be the correct way to use the Month and Day fields as well to get a more specific result. Just using Year will treats someone born January first and December 31 the same.
Thanks
... AND TIMESTAMPDIFF(YEAR,
CONCAT_WS('-', BirthYear, BirthMonth, BirthDay),
CURRENT_DATE) < 17
Also you may add generated column:
ALTER TABLE tablename
ADD COLUMN DOB DATE
GENERATED ALWAYS AS (CONCAT_WS('-', BirthYear, BirthMonth, BirthDay));
and use this column instead of the above expression.

MySQL - Day, Month, Unspecified Year

I'm working with 4 tables as following:
hotels(hotel_num, hotel_name, city)
rooms(room_num, hotel_num, room_type, price)
bookings(hotel_num, guest_num, arr_date, dep_date, room_num)
guests(guest_num, guest_name, guest_address)
I have to find bookings where someone will be staying at a particular hotel on New Year's Day but of a non specific year.
I have tried:
(SELECT COUNT(*)
FROM hotels AS h
JOIN rooms AS r
JOIN bookings AS b
ON h.hotel_num = r.hotel_num
AND b.hotel_num = h.hotel_num
AND b.room_num = r.room_num
WHERE h.hotel_name = 'Hotel California'
AND 01 BETWEEN MONTH(b.arr_date) AND MONTH(b.dep_date)
AND 01 BETWEEN DAY(b.arr_date) AND DAY(b.dep_date)
GROUP BY b.hotel_num);
I imagine this approach isn't working from the point of view that a booking can start on Christmas Day and end in February but the month goes from 12 to 2 which 1 is not between.
Is there a way to specify a month and day without a year?
Edit: Results should be a number, ie 7, which tells you how many rooms are booked on 01/01/xxxx
I think this condition will select the bookings on "New Years Day":
where year(arr_date)<>year(dep_date)
Unless someone arriving on "New Years "day" and departing on the same day should also be selected, but that is not very hard to add, is it?

Retrieving of Specific Age Group

I want to ask some help on SQL Query on how to retrieve bookings with specific age group. Basically, i want to retrieve bookings where there are customers who are Adults and child, these are determined only by date of birth. Children are treated as 15 years old below and adults are more than 15 years of age. I want to retrieve bookings who have children and adults that does not exceed 20yrs of age. No bookings should be retrieve if there is one customer in the booking that has age of more than 20 yrs old. And bookings should have more than 1 customer. Here's a sample table for your reference -
Booking No 123
Customer 1 - March 1, 2008
Customer 2 - Aug 3, 1998
Booking No 456
Customer 1 - March 2, 1986
Customer 2 - Feb 9, 2007
Customer 3 - Apr 10, 1999
Booking No 789
Customer 1 - Jun 7, 1999
The booking that needs to be retrieved is only Booking No 123. No age is provided in the table and computed only using Date of birth - DateDiff.
BookingID
CustomerID
LName
FName
DOB
ReservationID
BookingID
CompanyID
ArrivalDate
CompanyName
This is the where statement that i've put
(SELECT DATEDIFF(YEAR ,bp.DOB,GETDATE())) <= 20 AND (SELECT DATEDIFF(YEAR ,bp.DOB,GETDATE())) < 15
But still pulling bookings containing customers > 20 yrs old.
This should get you the bookings. The date computation should be based on the RESERVATION Arrival Date, as such if considering older or future reservations, getdate() WOULD alter the computed age at the time of arrival.
I am doing a direct join between the reservation and booking tables grouped by booking and qualifying every occupant's age.
SELECT
R.BookingID
FROM
BookingCustomer BC
JOIN Reservation R
ON BC.BookingID = R.BookingID
group by
R.BookingID
having
SUM( case when DATEDIFF(YEAR , BC.DOB, R.ArrivalDate ) < 16 then 1 else 0 end ) > 0
AND SUM( case when DATEDIFF(YEAR , BC.DOB, R.ArrivalDate ) >= 16
and DATEDIFF(YEAR , BC.DOB, R.ArrivalDate ) < 21 then 1 else 0 end ) > 0
AND SUM( case when DATEDIFF(YEAR , BC.DOB, R.ArrivalDate ) > 20 then 1 else 0 end ) = 0
Now, since a booking is all pointing to a same reservation, you COULD grab all the other fields at the same time
SELECT
R.BookingID,
R.ReservationID,
R.CompanyID,
R.ArrivalDate,
R.CompanyName ... rest of query.
If the query nags about non-aggregate fields, you could just wrap the other fields not part of the group by as MAX() since a booking is always pointing to the same respective reservation and the parent reservation details would not change anyhow.
SELECT
R.BookingID,
MAX( R.ReservationID ) ReservationID,
MAX( R.CompanyID ) CompanyID,
MAX( R.ArrivalDate ) ArrivalDate,
MAX( R.CompanyName ) CompanyName ... rest of query.
Okay, now we can see what's going on (and what's going wrong for you).
Your current query has this:
WHERE (SELECT DATEDIFF(YEAR ,bp.DOB,GETDATE()) <= 20
AND (SELECT DATEDIFF(YEAR ,bp.DOB,GETDATE())) < 15
...this can be translated to:
WHERE (the number of times January 1st is passed) <= 20
AND AT THE SAME TIME (the number of times January 1st is passed) < 15
Besides the fact that ANDs are exclusive - rows have to match both conditions - what's going on is that DATEDIFF counts the number of "boundaries" crossed for the given measure:
Returns the count (signed integer) of the specified datepart boundaries crossed between the specified startdate and enddate.
... and of course the boundary for a year would be January 1st.
First, a digression on range-searching on database. What you do this, WHERE DATEDIFF(YEAR ,bp.DOB,GETDATE() <= 20, you usually cause the database to ignore indices, which are ways to speed up queries; this is because it has to calculate a value (here, the difference in the year), for each row in the table (because otherwise it doesn't know if the calculated value matches).
Instead, it's better to do any "math", whenever possible, on constant values, since the database is going to remember them. The form we should use here will also solve the "selecting older customers" problem too:
WHERE DOB <= DATEADD(year, -21, GETDATE())
(This is equivalent to those "you are 21 if you were born on or before this date in the year XXXX" signs you see in grocery stores)
No that that's out of the way, we need to figure out what we actually need. Restating your conditions above, we're looking for bookings with (all of):
At least one customer 20 years or younger
At least one customer younger than 15 years
No customers more than 20 years old
At least two customers.
Now... Presumably we don't care about multiple (or single) customers that are younger than 20 years old, so long as they're also all more than 15 years old, so we should modify the first condition. Also, quite probably we need to warn if there are only customers in a booking who are 15 years or younger - they don't even have an "almost" adult! And quite probably we need to warn if this person would be all alone, too! So the conditions should be changed to:
At least one customer younger than 15 years
No customers 21 years or older
(please tell me if this restatement was incorrect)
Now that we no our conditions, we can write our statement. We are looking for bookings:
SELECT ReservationID, BookingID, CompanyID, ArrivalDate, CompanyName
FROM Booking
Where there is at least one customer younger than 15 years:
WHERE EXISTS (SELECT 1
FROM BookingCustomer
WHERE BookingCustomer.bookingId = Booking.BookingId
-- birthday after 15 years ago today
AND BookingCustomer.dob > DATEADD(year, -15, GETDATE()))
And there is also no customer 21 years or older:
AND NOT EXISTS (SELECT 1
FROM BookingCustomer
WHERE BookingCustomer.bookingId = Booking.BookingId
-- birthday before or on 21 years ago today
AND BookingCustomer.dob <= DATEADD(year, -21, GETDATE()))
side note: most of the time for booked tours and stuff, they only care about ages at the time the trip is taken, not the booking time, or whatever "today" happens to be when you run this. You probably don't want GETDATE(), but something else, likely ArrivalDate. Since you'd be doing math on a column it would again force a table scan, but keeping the age check - and modifying it a bit to take into account how far ahead a booking can be made - would knock out bookings "earlier" because somebody is definitely old enough (or nobody young enough).
Thanks for the detailed response. I tried the suggested query and it's not returning any booking
WHERE EXISTS (SELECT 1
FROM BookingCustomer
WHERE BookingCustomer.bookingId = Booking.BookingId
-- birthday after 15 years ago today
OR BookingCustomer.dob > DATEADD(year, -15, BOOKING.ARRIVALDATE))
AND NOT EXISTS (SELECT 1
FROM BookingCustomer
WHERE BookingCustomer.bookingId = Booking.BookingId
-- birthday before or on 21 years ago today
AND BookingCustomer.dob <= DATEADD(year, -21, BOOKING.ARRIVALDATE))

Get stats for each day in a month without ignoring days with no data

I want to get stats for each day in a given month. However, if a day has no rows in the table, it doesn't show up in the results. How can I include days with no data, and show all days until the current date?
This is the query I have now:
SELECT DATE_FORMAT(FROM_UNIXTIME(timestamp), '%d'), COUNT(*)
FROM data
WHERE EXTRACT(MONTH FROM FROM_UNIXTIME(timestamp)) = 6
GROUP BY EXTRACT(DAY FROM FROM_UNIXTIME(timestamp))
So if I have
Row 1 | 01-06
Row 2 | 02-06
Row 3 | 03-06
Row 4 | 05-06
Row 5 | 05-06
(i changed timestamp values to a day/month date just to explain)
It should output
01 | 1
02 | 1
03 | 1
04 | 0
05 | 2
06 | 0
...Instead of ignoring day 4 and today (day 6).
You will need a calendar table to do something in the form
SELECT `date`, count(*)
FROM Input_Calendar c
LEFT JOIN Data d on c.date=d.date
GROUP BY `date`
I keep a full copy of a calendar table in my database and used a WHILE loop to fill it but you can populate one on the fly for use based on the different solutions out there like http://crazycoders.net/2012/03/using-a-calendar-table-in-mysql/
In MySQL, you can use MySQL variables (act like in-line programming values). You set and can manipulate as needed.
select
dayofmonth( DynamicCalendar.CalendarDay ) as `Day`,
count(*) as Entries
from
( select
#startDate := date_add( #startDate, interval 1 day ) CalendarDay
from
( select #startDate := '2013-05-31' ) sqlvars,
AnyTableThatHasAsManyDaysYouExpectToReport
limit
6 ) DynamicCalendar
LEFT JOIN Input_Calendar c
on DynamicCalendar.CalendarDay = date( from_unixtime( c.date ))
group by
DynamicCalendar.CalendarDay
In the above sample, the inner query can join against as the name implies "Any Table" in your database that has at least X number of records you are trying to generate for... in this case, you are dealing with only the current month of June and only need 6 records worth... But if you wanted to do an entire year, just make sure the "Any Table" has 365 records(or more).
The inner query will start by setting the "#startDate" to the day BEFORE June 1st (May 31). Then, by just having the other table, will result in every record joined to this variable (creates a simulated for/next loop) via a limit of 6 records (days you are generating the report for). So now, as the records are being queried, the Start Date keeps adding 1 day... first record results in June 1st, next record June 2nd, etc.
So now, you have a simulated calendar with 6 records dated from June 1 to June 6. Take that and join to your "data" table and you are already qualifying your dates via the join and get only those dates of activity. I'm joining on the DATE() of the from unix time since you care about anything that happend on June 1, and June 1 # 12:00:00AM is different than June 1 # 8:45am, so matching on the date only portion, they should remain in proper grouping.
You could expand this answer by changing the inner '2013-05-31' to some MySQL Date function to get the last day of the prior month, and the limit based on whatever day in the current month you are doing so these are not hard-coded.
Create a Time dimension. This is a standard OLAP reporting trick. You don't need a cube in order to do OLAP tricks, though. Simply find a script on the internet to generate a Calendar table and join to that table.
Also, I think your query is missing a WHERE clause.
Other useful tricks include creating a "Tally" table that is a list of numbers from 1 to N where N is usually the max of the bigint on your database management system.
No code provided here, as I am not a MySQL guru.
Pseudo-code is:
Select * from Data left join TimeDimension on data.date = timedimension.date

Understanding how MySQL fetches data with same user id

I working on a table that keeps record of student attending course. The data are keep using student identification number, course id, coursename, start date, end date. Student can take more than one course so there are repeating student identification number but with different course id, course name, start date and end date. What I'm trying to to here is to select students based on number of day using DATEDIFF.
Sorry for not explaining my problem properly. the structure of the table i'm talking about:
studcourse1(internalstudentid, staffnoic, courseid, coursenm, StDt, EndDt, location, organizer, generalcategorycd, generalsubcategorycd, eid)
staffnoic - staff identification number,
StDt - Start Date.
EndDt - End Date
I've checked, there's no primary key or indexes on this table as it's not a base but a view.
Sorry if the previous statement is too long. Let's use this instead.
SELECT GradeGroupCd, StudCourse1.StaffNoIC, (DATEDIFF( EndDt, StDt ) +1) TotalDay, StDt, EndDt
FROM StudCourse1, tblStaff, tblRefTitleGred
WHERE tblStaff.TitleGredCd = tblRefTitleGred.TitleGredCd
AND StudCourse1.StaffNoIC = tblStaff.StaffNoIC
AND StDt >= '2009-1-1' AND YEAR(EndDt) <= YEAR(NOW())
AND (DATEDIFF( EndDt, StDt ) +1) > 90
AND (GeneralSubCategoryCd = 'S0012' OR GeneralSubCategoryCd = 'S0014')
GROUP BY GradeGroupCd, StudCourse1.StaffNoIC
The statement above fetches results for student(using staffnoic from table tblStaff and table StudCourse1) having taking course for more than 90 days using (DATEDIFF(EndDt, StDt) + 1). What that really confuse me is that for example a record from studcourse1 with staffnoic of '111111111111', sample data:
studcourse1(internalstudentid, staffnoic, courseid, coursenm, StDt, EndDt, location, organizer, generalcategorycd, generalsubcategorycd, eid)
studcourse1(10629,111111111111,AAA1811,Course1,2010-01-01 00:00:00, 2010-12-31 00:00:00, '', ABC Org, G003, S0012, E00001812)
(30684,111111111111,AAA6968,Course2,2009-02-10 00:00:00, 2012-02-09 00:00:00, '', ABC Org, G003, S0012, E00006894)
(30685,111111111111,AAA6970,Course3,2011-01-01 00:00:00, 2012-02-09 00:00:00, '', ABC Org, G003, S0014, E00006896)
Running the SQL statement will select the one with the StDt of 2010-01-01 00:00:00 and EndDt of 2010-12-31 00:00:00. Why is it singling out that record and not others as they all fall under AND StDt >= '2009-1-1' AND YEAR(EndDt) <= YEAR(NOW())". Year now referring to year 2012. How can I make it select the one with the StDt(2009-02-09) and EndDt(2012-02-09)?
And also "(DATEDIFF( EndDt, StDt ) +1) > 90", why does it add 1 to it? Wouldn't DATEDIFF( EndDt, StDt ) > 90 be the right one?
Sorry if that too many questions. Just learn MySQL recently. Thank you for your time.
Ok it is hard to understand what you have written. Please provide the tables you have used and the structure.
Simplest method to get this done is having 3 tables,
1) students with student id, name and so on
2) Course - course id, course name and so on.
3) Enrolment - enroll id, start date, end date and foreign keys(student id and course id).
And I didnt get what you meant by (What I'm trying to to here is to select students based on number of day using DATEDIFF.)