Finding ocuppations via SQL and/or PHP - php

I am making a student web app. Amongst other tables, I have a table in which students enroll and enrollments are between two dates.
This app uses MySQL 5.6 and PHP 7.2
It has the following fields:
IDStudent
StartDate
EndDate
IDCourse
Each course has a maximum capacity in which it cannot be surpassed.
I want to know, given a start date, end date and IDCourse, how many concurrent students are in a course. I get an approxiumate value just counting rows between two dates
SELECT COUNT(*) FROM enrollments
WHERE IDCourse = ?
AND (
(StartDate BETWEEN "<start date>" AND "<end date>")
OR
(EndDate BETWEEN "<start date>" AND "<end date>")
OR
(StartDate <= "<start date>" AND EndDate>= "<end date>")
)
But that doesn't take account non overlapping ranges. It counts every enrollment.
For example, I have this very simple case:
Want to find how many students are enrolled between 01/01/2021 and 05/01/2021 at a specified course
And I have those 3 enrollments on that course:
01/01/2021 - 02/01/2021
03/01/2021 - 04/01/2021
20/12/2020 - 01/02/2021
I should get 2 count and not 3, because 1 and 2 don't overlap while 3 overlaps both.
I tried to search online but I didn't found something similar, maybe I am not using the correct keywords!
I found Determine max number of overlapping DATETIME ranges but that is for MySQL 8
Many thanks for your help
Regards

I think you may need to create a calendar table between the first start date and the last end date, count by date and then select the max between the period you are interested:
select max(stcount)
from
(
select c.dt, count(*) stcount from calendar_table c
join enrollments e on c.dt between e.StartDate and e.EndDate
group by c.dt
) countbydate
where dt between '2021-01-01' and '2021-01-05'
db-fiddle:
https://www.db-fiddle.com/f/dXuKMoRQ2ivLt5qi5AVFcG/0

Related

How to check if a day in a timetable is full or not in SQL Server

I'm finishing a small web app that allows a group of people to reserve rooms for meetings and other activities. So, I have a monthly report with the days of the chosen month. After selecting a day, It will show the scheduled timetable for the day and for that room.
The days are color coded whether they have no reservations made are partially full or are completely full.
On the SQL Server side I have a table to register the reservations made in which I have fields for the role, day (date), start time and end time of the meeting (both datetime).
So my question is: is there a way to know, with querying, if a day is completely full or not? I want to have some kind of left join or CTE to put a flag in the select statement telling me if the day is full or not.
EDIT: I was asked for the table structure so I'm putting it here to help other people who might run into the same kind of question.
Table "reservations":
id INT PRIMARY KEY,
id_type_event INT,
reservation_date DATE,
start_time TIME(5),
end_time TIME(5),
id_reservation_state INT,
date_created DATETIME,
id_user_created INT
Question 2: should I change the "start_time" and "end_time" to DATETIME?
If you have a date table then you could so something simple like this;
DECLARE #QueryDate date; SET #QueryDate = '2016-05-31'
SELECT
dt.BookingDate
,bkd.Room
,CASE
WHEN bkd.HoursBooked < 8
THEN 'Not Full'
WHEN bkd.HoursBooked > 10
THEN 'Over Booked'
ELSE 'Fully Booked'
END BookingStatus
FROM
DateTable dt
LEFT JOIN
(
SELECT
Room
,BookingDate
,SUM(DATEDIFF(mi,StartTimeDate,EndTimeDate)) HoursBooked
FROM RoomBookings
GROUP BY
Room
,BookingDate
) bkd
ON dt.Room = bkd.Room
AND dt.BookingDate = bkd.BookingDate
WHERE dt.BookingDate = #QueryDate
I'm working with limited information in your post, but if you have two tables, one being the date table the other being the bookings table then this will give you what you're after.

PHP/MySQL Select the day before and day after a range from the same table

I have two tables, one called check_ins and another called holidays.
check_ins has a datetime_start and datetime_end columns (in addition to other stuff that isn't needed for this question). The holidays table has a date range of two columns for the start and end of the holiday.
I need to figure out who was in the day prior to and the day directly after the holiday range to determine who gets holiday pay. In other words, I need only results from the table that the same employee was in one day before and one day after, ignoring the rest.
I've been racking my brain all day trying to figure out a way to do this and have found nothing. Am I barking up the wrong tree here? Should I do this via PHP?
Thanks!
Edit: this is what I used and though I had it until I realized that their might be multiple check-ins in a single day:
SELECT DISTINCT count(check_ins.Employee_ID), check_ins.ShiftStart_Datetime, check_ins.ShiftEnd_Datetime, holidays.* FROM check_ins, holidays WHERE holidays.ID = 2 AND DATE(DATE_ADD(Datefrom, INTERVAL -1 DAY)) = DATE(ShiftStart_Datetime) GROUP BY Employee_ID HAVING count(check_ins.Employee_ID) >1 UNION SELECT DISTINCT check_ins.Employee_ID, check_ins.ShiftStart_Datetime, check_ins.ShiftEnd_Datetime, holidays.* FROM check_ins, holidays WHERE DATE(DATE_SUB(Dateto, INTERVAL -1 DAY)) = DATE(ShiftStart_Datetime) GROUP BY Employee_ID HAVING count(check_ins.Employee_ID) >1
You can (inner) join the check_ins table twice. Once for the day before the start of the holiday and once for the day after.
If the datetime_start and datetime_end may have different dates, you need to use BETWEEN. Cast both of them to a date instead of a datetime, since you don't care about the time.
Add a GROUP BY in case the employee has multiple check ins on one day.
SELECT holidays.id AS holiday_id, ci_before.employee_id FROM holidays
INNER JOIN check_ins ci_before ON holidays.holiday_start - INTERVAL 1 DAY
BETWEEN DATE(ci_before.datetime_start) AND DATE(ci_before.datetime_end)
INNER JOIN check_ins ci_after ON holidays.holiday_end + INTERVAL 1 DAY
BETWEEN DATE(ci_after.datetime_start) AND DATE(ci_after.datetime_end)
AND ci_before.employee_id = ci_after.employee_id
GROUP BY ci_before.employee_id
See the SQL fiddle here
The used tables are
CREATE TABLE `holidays` (id int, holiday_start date, holiday_end date);
CREATE TABLE `check_ins` (employee_id int, datetime_start datetime, datetime_end datetime);
holidays.id is an ID for a holiday, not an employee.
you can do it via php in 2 queries, but why would you... this'll be fun!
SELECT * FROM check_ins
LEFT JOIN holidays AS holi_before
ON (
datediff(holi_before.datetime_end,check_ins.datetime_end) <= 1
)
LEFT JOIN holidays AS holi_after
ON (
holi_before.id = holi_after.id
AND datediff(holi_after.datetime_end,check_ins.datetime_end) >= 1
)
That should about do it (might need to tweak the datediff comparison a bit to get it exactly right.)
haven't test it, but i hope it gives you the right push :-)

Optimising PHP/mysql algorithm

I have to make some statistics for my application, so I need an algorithm with a performance as best as possible. I have some several question.
I have a data structure like this in the mysql database:
user_id group_id date
1 5 2012-11-20
1 2 2012-11-01
1 4 2012-11-01
1 3 2012-10-15
1 9 2013-01-18
...
So I need to find the group of some user at a specific date. For example, the group of the user 1 at date 2012-11-15 (15 november 2012) should return the most recent group, which is 2 and 4 (many group at the same time) at date 2012-11-01 (the closest and smaller date).
Normally, I could do a Select where date <= chosen date order by date desc, etc... but that's not the point because if I have 1000 users, it will need 1000 requests to have all the result.
So here are some question:
I have already using the php method to loop through the array to avoid the high number of mysql request, but it's still not good because the array size may be 10000+. Using a foreach (or for?) is quite costly.
So my question is if given an array, ordered by date (desc or asc), what's the fastest way to find the closest index of the element which contain a date smaller (or greater) than a given date; beside using a for or foreach loop to loop through each element.
If there is no solution for the first question, then what kind of data structure would you suggest for this kind of problem.
Note: the date is in mysql format, it's not converted in timestamp when you stored it in an array
EDIT: this is a sql fiddle http://sqlfiddle.com/#!2/dc28d/1
For dos_id = 6, t="2012-11-01" it should returns only 2 and 5 at date "2010-12-10 13:16:58"
Not sure why you'd want to do this in php. Here's some SQL using joins instead to get most recent group(s) for all users given a date. Make sure you've got indexes on date and userid.
SELECT *
FROM test t1
LEFT JOIN test t2
ON t1.userid = t2.userid AND t2.thedate <= '2012-11-15' AND t2.thedate > t1.thedate
WHERE t1.thedate <= '2012-11-15' AND t2.userid IS NULL;
SQLfiddle
Or using your SQLFiddle
SELECT t1.*
FROM dossier_dans_groupe t1
LEFT JOIN dossier_dans_groupe t2
ON t1.dos_id = t2.dos_id AND t2.updated_at <= '2012-11-01'
AND t2.updated_at > t1.updated_at
WHERE t1.updated_at <= '2012-11-01' AND t2.dos_id IS NULL;
This would give you a list of all users and their groups (1 row per group) for the latest date that is smaller than the one you specify (2012-11-15 below).
SELECT user_id, group_id, date FROM table WHERE date <= '2012-11-15' AND NOT EXISTS (SELECT 1 FROM table test WHERE test.user_id = table.user_id AND test.date > table.date and test.date <= '2012-11-15')

Finding empty time blocks between two dates?

I have 2 MySQL tables, 'scheduled_time' and 'appointments'
'scheduled_time' has 2 DateTime fields, 'start' and 'end' - this is a time range of when I am available for appointments.
'appointments' contains appointment details but also a 'start' and 'end' field, this will ultimately be within the range specified in 'scheduled_time'.
What is the best way for me to find empty time blocks when taking into account both tables?
Lets say I have 'scheduled_time' starting 11/9/2010 from 8am to 2pm. and I have one 'appointment' from 8am to 10am and one from 1pm to 2pm. How can I find the next available block of say 1 hour?
I did this a while ago. We had a similar structure:
Available (contained all working hours for an employee, flexible working hours)
Appointments (similar to yours)
What I did was basically this (steps):
Get all start and end datetimes for employee < x >, sorted by startdate
let startAvailable = start of the time search (in your case 11/9/2010 # 8am)
let appointment = first appointment in the list of appointments
get the startdate of the first appointment. If the difference between these is big enough, there's your block
if not, let startAvailable = enddate of appointment
remove appointment from the list, let appointment be the next appointment
repeat the process of checking for an available block
First, create a number table. Here as example named "Numbers" with a column "number".
Then you can do something like
select chour as [Free Hour] from Numbers n
inner join Scheduled s on n.chour >= s.start and n.chour < s.[end]
where chour not in
(
select chour from Numbers n
inner join Appointments a ON n.chour >= q.start and n.chour < a.[end]
)
Numbers is my Numbers table, chour is a computed column defined as
DATE_ADD('2010-11-08', INTERVAL number HOUR)
You can also store it as a persisted column of course.
Sorry, if the syntax isn't completely right, I do T-SQL normally :-)
Edit: This technique only works for fixed blocks of time (hour is just an example, you could do half hours just as easily), but it's quite efficient and readable in this case. A usual application in business is mapping dates, because that's the granularity where most contracts live and you can cover a lot of days with a small number table.
This is a great place to use a dummy integer table, which helps you to create data from nothing. Here is an example:
create table ints(i tinyint);
insert into ints values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
You can then use a few cross joins with this table to generate a table of hour-long windows represented by your scheduled_time table, and left join the result against the appointments table to find windows that do not have something already scheduled.
SELECT
h.HourWindowStart, h.HourWindowEnd
FROM (
SELECT
s.start + INTERVAL t.i*100 + u.i*10 + v.i HOUR AS HourWindowStart,
s.start + INTERVAL t.i*100 + u.i*10 + v.i + 1 HOUR AS HourWindowEnd
FROM scheduled_time s
JOIN ints AS t
JOIN ints AS u
JOIN ints AS v
WHERE s.start + INTERVAL t.i*100 + u.i*10 + v.i HOUR < s.end
ORDER BY HourWindowStart
) as h
LEFT JOIN appointments a ON a.end > h.HourWindowStart AND a.start < h.HourWindowEnd
WHERE a.start IS NULL
You can tweak various parts of this process to calculate larger/smaller availability windows (by the half hour, by the day, etc), use more or less cross joins of the integer table based on the maximum number of availability windows that could be represented in a single start/end range in scheduled_time, pre-create a date calendar and join against the two tables, etc.

calculate total number of days in specied dates

i have one table with two columns as shown in picture
table columns names are (MAXDATE,AMOUNT).
if you see we have
first date range (from current date to 20-jan-2010)
second date range from 20-jan-2010 to 30-jan-2010
3rd range is from 20-jan-2010 to 31-jan-2010.
at the execution of page user enter the start and end date.
for example, if user put
start date: 18-jan-2010
end date: 23-jan-2010
then he has 2 dates in first options and 3 dates in second options.
what i want to calculate in sql
1. how many days in first range (if any this depends on supplied dates from user)
2. how many days in 2nd range (if any this depends on supplied dates from user)
3. how many days in 3rd range (if any this depends on supplied dates from user)
Thanks
Here is an example how to calculate days.
http://dev.mysql.com/doc/refman/5.0/en/date-calculations.html
You can do all of this in MySQL:
SELECT DATEDIFF(NOW(), max_date) as days;
SELECT DATEDIFF(date2, date1) FROM
(SELECT max_date as date1 FROM table1 LIMIT 1) as s1
JOIN
(SELECT max_date as date2 FROM table1 LIMIT 1 OFFSET 1) as s2;
//etc.

Categories