SQL - getting totals from a crosstab query - mysql

TRANSFORM Count(Research.Patient_ID) AS CountOfPatient_ID
SELECT DateDiff("yyyy",[DOB],Date()) AS [Age Group 22 - 24]
FROM (Research INNER JOIN Demographics ON Research.Patient_ID = Demographics.ID) INNER JOIN [Letter Status] ON Research.Patient_ID = [Letter Status].Patient_ID
WHERE (((DateDiff("yyyy",[DOB],Date())) Between 22 And 24))
GROUP BY DateDiff("yyyy",[DOB],Date())
PIVOT [Letter Status].Letter_Status;
This code lists individual ages, but I want to calculate the Total of all ages that fall within that range. So if there were two 22 year olds, and one 23 year old, the Total would be 3, as opposed to seeing all of their individual ages in the column...
Please help.

Please be sure you understand what you're getting when you use this method to determine a person's age.
DateDiff("yyyy",[DOB],Date())
If DOB = #1987-12-31#, on today's date (#2011-6-16#), DateDiff will give you this as the age:
? DateDiff("yyyy", #1987-12-31#, #2011-6-16#)
24
(And actually for any DOB during 1987, DateDiff with today's date would give you 24 as the "age". Furthermore, for any DOB during 1987, using that method on any day in 2011, you would get 24.)
OTOH, as age is commonly understood, one might say "Nonsense! His birthday is still more than 6 months away. He's only 23 today."
The reason for this discrepancy is that DateDiff("yyyy" only evaluates the year component of two dates. Consider these two dates which are one day apart:
? DateDiff("yyyy",#2010-12-31#,#2011-1-1#)
1
That same thing happens when you use that expression to calculate "age".
To return the age as commonly understood, you can use an expression like this:
? DateDiff("yyyy", #1987-12-31#, Date())+ _
Int( Format(Date(), "mmdd") < Format( #1987-12-31#, "mmdd"))
I copied that expression from the Access Web: Calculate Age of a person. That page includes other approaches for determining age.
None of this matters if, apparently like the OP, you want all DOB in 1987 to be counted as age 24 today, or on any day in 2011.

You need to define the age ranges in a switch statement. To make it easier I've created a inline query to calcuate the age (I'm assuming its on the research table).
TRANSFORM Count(Research.Patient_ID) AS CountOfPatient_ID
SELECT SWITCH (Age <= 22, "Under 22",
Age > 22 and AGE <= 24 , "Between 22 And 24",
Age > 24 and AGE <= 26, "Between 24 And 26",
Age > 26 , "Over 26") as Age_Range
FROM
(Research
INNER JOIN (SELECT ID, DateDiff("yyyy",DOB,Date()) as AGE
FROM Demographics) Demographics
ON Research.Patient_ID = Demographics.ID)
INNER JOIN [Letter Status]
ON Research.Patient_ID = [Letter Status].Patient_ID
GROUP BY AGE
PIVOT [Letter Status].Letter_Status;

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.

Need help for join and some calculations on a MySql insert

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

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))

MySQL - Chaining multiple SQL commands

I've looked upon multiple threads but can't seem to find a desirable answer to my question. I am creating a system with a scheduler in it and I need multiple chains in order for the query to return an answer. So here is the scenario. A user needs to register and upon registration, the user is presented with a date.
I have users table where users(obviously) are listed. One column here has the date.
There is also a date table where the dates are stored. Each date can only occupy 30 persons.
The date table also has the availability column. If the date is available, it is labeled 1. If the date has expired (the current date is higher than this date), it is labeled 0.
for example, i have dates Jan 1, Jan 2 and Jan 3 and the current date is Jan 2. Obviously, Jan 1 should be expired. That wouldn't be included in the list so I will set the availability to 0 (yes, manually). There is only Jan 2 and Jan 3. I also need to find if Jan 2 has accommodated 30 people. Else, I need to put him to Jan 3. I got a bunch of parts of the codes that I don't know how to chain.
Expected Output :
query1 (Jan 2 and Jan 3 should be the result)
SELECT * FROM rooms WHERE availability = 0
RoomID Room Date Room Availability
1 Jan 1 1
2 Jan 2 0
3 Jan 3 0
query2 - (count people assigned in specific rooms)
SELECT COUNT(RoomAssigned) FROM users
Users RoomAssigned
Jack 2
Eddie 2
query3 - (system should be able to locate the first room that is available)
if (query2 results<30)
put new user to rooms from result in query1
If ever the chaining I was looking for would possibly be not advisable, I am open for different suggestions. Thanks. :)
Your question seems to be very confused about what tables you have and what's in them, and I don't see how your sample queries can produce the output you showed. But it seems like this query will do what you want:
SELECT *
FROM rooms
WHERE availability = 1
AND roomID NOT IN (
SELECT roomAssigned
FROM users
WHERE availability = 1
GROUP BY roomAssigned
HAVING COUNT(*) >= 30)
ORDER BY roomDate
LIMIT 1
The subquery finds all the rooms that are filled, and then we exclude those from the main query. Then we sort the remaining rooms by date, and select the first one with LIMIT 1.
I think you're going to want something like this:
SELECT r.[RoomID],
r.[Room Date]
FROM rooms r
LEFT JOIN users u
ON r.[RoomID] = u.[RoomAssigned]
WHERE r.[Room Date] >= CURDATE()
GROUP BY r.[RoomId], r.[Room Date]
ORDER BY r.[Room Date], r.[Room Id]
HAVING COUNT(u.[RoomAssigned]) < 30
LIMIT 1
I haven't tested it, so it may require some tweaking. It's like #Barmar's answer, only using a join instead of a nested select. I also checked the availability based on the current date, not the availability column, which I don't think should be stored in the database, because it can be determined based on the Room Date.

some select queries that use date format

So.. to return the age based on a date type it's really easy :
SELECT YEAR(CURDATE()) - YEAR(DateofBirth) and we get a result set with the age of the person
But what if I need to search the people with an age range of 60-70 in the table based on their date of birthdays in this format d/m/y ( Ex: 2005-04-11). I considered it was kind of hard because it's a different thing searching for an certain age if u have only dateofbirths instead of searching through a column where u already have their ages being counted as normal years ( 50, 60 etc )
Another problem that's not related to the one mentioned above:
Let's say I have a table with Medics that has their names, surnames and their speciality.
We got 17 specialities : reumatologie, o.r.l, chirurgie etc. and 108 doctors
How do I get the list of the medical specialities and the doctors that are in that range based on this format:
Ex: reumatologie - 4, o.r.l - 5, chirurgie - 10 etc
4 and 5 from the examples are the number of medics. I've tried concatenating and all sort of queries none of them worked properly though. Maybe will work with a case? if yes how?
For question 1, you can just do (not very good regarding performance though):
SELECT YEAR(CURDATE()) - YEAR(DateofBirth) AS age
FROM tableX
WHERE (YEAR(CURDATE()) - YEAR(DateofBirth)) BETWEEN 60 AND 70 ;
If you want performance, you can add an index on DateofBirth and use this version:
SELECT YEAR(CURDATE()) - YEAR(DateofBirth) AS age
FROM tableX
WHERE DateofBirth >= MAKEDATE(YEAR(CURDATE())-70, 1)
AND DateofBirth < MAKEDATE(YEAR(CURDATE())-60+1, 1) ;