php mysql select by month between records - php

I've this MySQL table my_table:
+-------+------------+-----------+
|Student| Date | Classroom |
+-------+------------+-----------+
| 1 | 2018-01-01 | 101 |
| 2 | 2018-01-01 | 102 |
| 3 | 2018-01-01 | 103 |
| 1 | 2018-03-01 | 104 |
| 2 | 2018-06-01 | 103 |
| 3 | 2018-09-01 | 104 |
| 1 | 2018-11-01 | 106 |
| 2 | 2018-12-01 | 101 |
+-------+------------+-----------+
The students stay in the assigned classroom till changed.
I'm trying to get which classroom they were in for a certain month.
For example in October(10), student 1 was in 104, 2 was in 103, and 3 was in 104.
I'm really unsure on how to proceed with this one so any help is appreciated.
Currently using this query based on Strawberry answer
SELECT x.*
FROM my_table x
LEFT OUTER JOIN my_table y
ON y.student = x.student
AND y.date < x.date
WHERE x.date <= LAST_DAY('2018-10-01')
GROUP BY student

DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(Student INT NOT NULL, Date DATE NOT NULL, Classroom INT NOT NULL,PRIMARY KEY(student,classroom));
INSERT INTO my_table VALUES
(1,'2018-01-01',101),
(2,'2018-01-01',102),
(3,'2018-01-01',103),
(1,'2018-03-01',104),
(2,'2018-06-01',103),
(3,'2018-09-01',104),
(1,'2018-11-01',106),
(2,'2018-12-01',101);
SELECT x.*
FROM my_table x
JOIN
( SELECT student
, MAX(date) date
FROM my_table
WHERE date <= LAST_DAY('2018-10-01')
GROUP
BY student
) y
ON y.student = x.student
AND y.date = x.date;
+---------+------------+-----------+
| Student | Date | Classroom |
+---------+------------+-----------+
| 1 | 2018-03-01 | 104 |
| 2 | 2018-06-01 | 103 |
| 3 | 2018-09-01 | 104 |
+---------+------------+-----------+

Here's a go at it (snippet to go in a stored procedure; assumes table called example & output to table months). It produces a row per student for each month of the range.
drop table months;
create table months (month date, student integer, classroom integer);
set #month = (select min(date) from example);
start_loop: LOOP
insert into months select #month, s1.student, classroom from
(select student, max(date) as maxdate from example where date <= #month group by student) s1
join example s2 on s1.student = s2.student and maxdate = date;
if #month = (select max(date) from example) then
leave start_loop;
end if;
set #month = #month + interval 1 month;
END LOOP start_loop;

Let's break the problem into two parts. Firstly, find all the rooms which have been allocated to student A so far and sort them using the date. Next, find the record which is just before or equal to the required month.
For example:
Consider student 1. We get
+-------+------------+-----------+
|Student| Date | Classroom |
+-------+------------+-----------+
| 1 | 2018-01-01 | 101 |
| 1 | 2018-03-01 | 104 |
| 1 | 2018-11-01 | 106 |
+-------+------------+-----------+
Now, let's say for month June we try to find month just less than or equal to 2018-06-01 to get the required room number. I hope this will help.

Related

How to select only available time and date for doctor appointment booking system in PHP

I have only one table named appointment_master in MySQL in which I store the booking date and time entered by the patient. Appointment time is fixed means patient can book an appointment every 10 minutes means if one patient has booked appointment at 10:00 AM then other can book at 10:10 AM and so on. Now if an appointment is booked at 10:00 AM and when other patient try to book an appointment the date and time slot option 10:00 AM should not be displayed to other patient in the form select option tag.
My table is as follows:
name lastname age gender email phone_no app_date app_time
ramesh ahir 30 Male rames#gmail.com 9824758745 2019-01-18 10:20:00
jitesh thacker 35 Male jitesh#gmail.com 9824758745 2019-01-21 10:30:00
Consider the following, very simplified example:
DROP TABLE IF EXISTS slots;
CREATE TABLE slots
(id SERIAL PRIMARY KEY,slot VARCHAR(12) NOT NULL UNIQUE);;
INSERT INTO slots VALUES
(1,'Slot 1'),
(2,'Slot 2'),
(3,'Slot 3'),
(4,'Slot 4'),
(5,'Slot 5');
DROP TABLE IF EXISTS bookings;
CREATE TABLE bookings
(booking_id SERIAL PRIMARY KEY
,user_id INT NOT NULL
,slot_id INT NOT NULL UNIQUE
);
INSERT INTO bookings VALUES
(1,101,3);
To show available slots, we can do something like this...
SELECT s.*
FROM slots s
LEFT
JOIN bookings b
ON b.slot_id = s.id
WHERE b.booking_id IS NULL;
+----+--------+
| id | slot |
+----+--------+
| 1 | Slot 1 |
| 2 | Slot 2 |
| 4 | Slot 4 |
| 5 | Slot 5 |
+----+--------+
...or this...
SELECT s.*
, CASE WHEN b.booking_id IS NULL THEN 'yes' ELSE 'no' END available
FROM slots s
LEFT
JOIN bookings b
ON b.slot_id = s.id;
+----+--------+-----------+
| id | slot | available |
+----+--------+-----------+
| 1 | Slot 1 | yes |
| 2 | Slot 2 | yes |
| 3 | Slot 3 | no |
| 4 | Slot 4 | yes |
| 5 | Slot 5 | yes |
+----+--------+-----------+
To make sure two users can't book the same slot simultaneously, we can do something like this...
INSERT INTO bookings (user_id,slot_id)
SELECT 102,1
FROM (SELECT 1) x
LEFT
JOIN bookings y
ON y.slot_id = 1
WHERE y.booking_id IS NULL;
SELECT * FROM bookings;
+------------+---------+---------+
| booking_id | user_id | slot_id |
+------------+---------+---------+
| 1 | 101 | 3 |
| 2 | 102 | 1 |
+------------+---------+---------+
...which prevents another user booking the same slot...
INSERT INTO bookings (user_id,slot_id)
SELECT 103,1
FROM (SELECT 1) x
LEFT
JOIN bookings y
ON y.slot_id = 1
WHERE y.booking_id IS NULL;
SELECT * FROM bookings;
+------------+---------+---------+
| booking_id | user_id | slot_id |
+------------+---------+---------+
| 1 | 101 | 3 |
| 2 | 102 | 1 |
+------------+---------+---------+
Everything else can be handled in your application code (PHP).
use the below query to get the last appointment time
select max(app_time) from appointment_master;
Save this time in a PHP variable $date1 (or any name)
Save the new appointment time as $date2 variable
get the difference between the two variables
Use if condition to prevent the appointment if the time difference is less than 10 minutes

query to display information based on month and year

I am having a table having following information
I want to display information some what like that
Actually i am using php to display a google chart for which i need a query which can give me this output. The system should check the month and based on the month it should prepare the report, E.g if its the month of April it should display April for both A and B users as zero. i am using the following query but it
SELECT name
,month as month
,count(*) as no_of_vists
FROM abc where name = 'A'
group by month
order by date(month);
try as below :
select x.name
,x.month
,count (y.*)
FROM
(SELECT b.name
,a.month
FROM (select month from abc) a
JOIN (select name from abc) b
ON (1=1)
group by 1,2 ) x
LEFT JOIN abc y
ON (x.month = y.month and x.name = y.name)
group by 1,2
order by 1,2;
Output :
| name | month | count |
|------|----------|-------|
| a | February | 1 |
| a | January | 2 |
| a | March | 1 |
| b | February | 0 |
| b | January | 1 |
| b | March | 0 |
Here is the sqlfiddle : http://sqlfiddle.com/#!15/3feb0/24

How to get a value from MySQL n days before from CURDATE()

I want to join 4 tables to list all the values from a table those have the duration from last updated to current date is more that the duration in other table, table are given below (my English not good to understand so am explaining with examble)
first table daily_tasks
+---------+---------+
| task_id | type_id |
+---------+---------+
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
+---------+---------+
Second Table daily_task_report
+-----------+---------+------------+
| report_id | task_id | task_date |
+-----------+---------+------------+
| 1 | 1 | 2015-09-10 |
| 2 | 3 | 2015-09-10 |
| 3 | 1 | 2015-09-11 |
| 4 | 3 | 2015-09-16 |
+-----------+---------+------------+
Third Table duration_types
+---------+---------------+------------------------+
| type_id | duration_type | duration_time(in days) |
+---------+---------------+------------------------+
| 1 | Daily Task | 1 |
| 2 | Weekly Task | 6 |
| 3 | Monthly Task | 26 |
| 4 | Yearly Task | 313 |
+---------+---------------+------------------------+
Fourth Table calendar
+--------+------------+---------+
| cal_id | cal_date | holiday |
+--------+------------+---------+
| 1 | 2015-09-10 | 0 |
| 2 | 2015-09-11 | 0 |
| 3 | 2015-09-12 | 0 |
| 4 | 2015-09-13 | 1 |
+--------+------------+---------+
Here daily_tasks.type_id is from duration_types.type_id and daily_task_report.task_id is from daily_tasks.task_id. I want to select all the task_id those task_date and current_date difference will greater than duration_time, also while calculating the duration i have to avoid the dates those have holiday=1 from calendar.
I tried queries but not proper, i got the values without including the calendar table, but that not a good way, query is taking more time to execute.
"SELECT dailyTasks.task_id FROM
(SELECT tab.* FROM (SELECT
tasks.task_type,report.*
FROM daily_tasks AS tasks
LEFT JOIN daily_task_reports AS report ON tasks.task_id=report.task_id
WHERE 1 ORDER BY report.task_date DESC) as tab GROUP BY tab.d_task_id) AS dailyTasks
LEFT JOIN duration_types AS type ON dailyTasks.task_type=type.type_id
WHERE DATEDIFF(CURDATE(),dailyTasks.task_date)>=type.duration_time"
Please someone help, I stuck in this section
According to given table You have some unexpected text or unknown columns in your query
Try this query
"SELECT dailyTasks.d_task_id FROM
(SELECT tab.* FROM
(SELECT tasks.type_id,report.* FROM daily_tasks AS tasks
LEFT JOIN daily_task_reports AS report ON tasks.task_id=report.task_id
ORDER BY report.task_date DESC)
as tab GROUP BY tab.task_id) AS dailyTasks
LEFT JOIN duration_types AS type ON dailyTasks.type_id=type.type_id
WHERE DATEDIFF(CURDATE(),dailyTasks.task_date)>=type.duration_time"
It can be also works in single query
SELECT tasks.task_id FROM daily_tasks AS tasks
LEFT JOIN daily_task_reports AS report ON tasks.task_id=report.task_id
LEFT JOIN duration_types AS type ON tasks.type_id = type.type_id and DATEDIFF(CURDATE(),report.task_date) >= type.duration_time
*I tried but not exclude that id which have holiday in calendar
You can create it on your coding side I gave you new query included with calendar
*
SELECT tasks.task_id,report.task_date,calendar.holiday FROM daily_tasks AS tasks
LEFT JOIN daily_task_reports AS report ON tasks.task_id=report.task_id
LEFT JOIN duration_types AS type ON tasks.type_id = type.type_id and DATEDIFF(CURDATE(),report.task_date) >= type.duration_time
LEFT JOIN calendar ON report.task_date=calendar.cal_date
where calendar.holiday = '0'
order By report.task_date desc

How to select data from multiple tables based on different time ranges?

I have two tables in MySQL:
visits
points
visits table looks like the following:
+-------+-------------------+-----------+
| id | date | user_id |
+-------+-------------------+-----------+
| 1 |2014-08-01 05:23:00| 43 |
| 2 |2014-08-01 14:41:00| 21 |
| 3 |2014-08-02 23:54:00| 43 |
| 4 |2014-08-03 03:21:00| 43 |
| 5 |2014-08-03 04:19:00| 34 |
| 6 |2014-08-03 11:33:00| 43 |
| 7 |2014-08-04 12:21:00| 43 |
| 8 |2014-08-05 01:55:00| 43 |
| 9 |2014-08-06 06:13:00| 43 |
| 10 |2014-08-07 19:47:00| 43 |
+-------+-------------------+-----------+
points table looks like the following:
+-------+-------------------+-----------+-------+----------+
| id | date | user_id | points| status |
+-------+-------------------+-----------+-------+----------+
| 1 |2014-08-01 04:33:00| 43 | 10 | 0 |
| 2 |2014-08-02 05:21:00| 21 | 23 | 0 |
| 3 |2014-08-02 09:01:00| 43 | 32 | 1 |
| 4 |2014-08-02 01:21:00| 43 | 21 | 0 |
| 5 |2014-08-03 23:23:00| 34 | 54 | 0 |
| 6 |2014-08-04 20:34:00| 43 | 11 | 0 |
| 7 |2014-08-04 17:54:00| 43 | 9 | 0 |
| 8 |2014-08-04 03:45:00| 43 | 34 | 0 |
| 9 |2014-08-06 08:23:00| 43 | 76 | 0 |
| 10 |2014-08-07 11:43:00| 43 | 52 | 0 |
+-------+-------------------+-----------+-------+----------+
I want execute only 1 query and achieve the following.
I'd like to count the rows in the visits table where the user_id = 43 and the date is between 2014-08-01 and 2014-08-03.
I also want to count the rows, sum the points in the points table where user_id = 43 and the date is between 2014-08-01 and 2014-08-03 and the status is 0.
After that, in the same query, I'd like to select the same as above, but in a different timeframe, like: 2014-08-04 and 2014-08-07.
Is there any query out there which can solve this problem for me?
(I'm actually doing this because I'd like to get data for one of my jQuery chart which is called: Morris.js. I'd like to get 12 datasets if the visitor selects a time range and divide it based on the days, the visitor selected. For example: if he selects: 2014-08-01 till 2014-08-01, I want to display him 12 datasets of that day. But if he selects for example: 2014-08-01 till 2014-08-06, then I'd want to display him the data for the 6 day divided by 12.)
If you don't understand something here, just let me know and I'll explain it better. The point is that I'd like to collet the datasets and draw the chart based on the time range to the visitor. Is that above MySQL logic a good solution for my issue?
EDIT:
As per request I'm showing the desired result in here:
+---------------------+-----------+-------+-------+-------------+
| date | user_id | points| visits| points_count|
+---------------------+-----------+-------+-------+-------------+
|2014-08-01-2014-08-03| 43 | 31 | 4 | 2 |
|2014-08-04-2014-08-07| 43 | 182 | 4 | 5 |
+---------------------+-----------+-------+-------+-------------+
I hope I've calculated everything correctly.
ok so this took a little work because you have to join the two tables independantly of eachother.. the reason being is because one can't have the status of 1 and the other can.. so with that in mind this query will return exactly what you want.
QUERY:
SELECT
t.join_date as 'Time Frame',
t1.user_id,
t.num_visits,
t1.num_points,
t1.total_points
FROM
( SELECT
CASE
WHEN DATE(date) >= '2014-06-01' AND DATE(date) <= '2014-07-10' THEN 1
WHEN DATE(date) >= '2014-08-05' AND DATE(date) <= '2014-08-07' THEN 2
ELSE 3
END AS grouping_col,
CONCAT(MIN(DATE(date)), ' - ', MAX(DATE(date))) as join_date,
COUNT(id) as num_visits
FROM visits
WHERE user_id = 43
GROUP BY grouping_col
)t
LEFT JOIN
( SELECT
CASE
WHEN DATE(date) >= '2014-06-01' AND DATE(date) <= '2014-07-10' THEN 1
WHEN DATE(date) >= '2014-08-05' AND DATE(date) <= '2014-08-07' THEN 2
ELSE 3
END AS grouping_col,
CONCAT(MIN(DATE(date)), ' - ', MAX(DATE(date))) as join_date,
user_id,
COUNT(id) as num_points,
SUM(points) as total_points
FROM points
WHERE user_id = 43
AND status = 0
GROUP BY grouping_col
)t1 ON t1.grouping_col = t.grouping_col
WHERE t.grouping_col IN(1, 2) OR t1.grouping_col IN(1, 2)
NOTE:
you can add as many timeframes to this by just adding more rows to the CASE statement...
SEE DEMO:
FIDDLE
A better option may be to actually use a stored procedure and set a beginning date param and an end date param so you can pick any dates you want
The only approach which comes to mind is to do two distinct query and use UNION ALL
SELECT *
FROM
(SELECT
count(v.id) AS visit_count,
count(p.id) AS point_count,
sum(p.points) AS points
FROM visits v1
JOIN points p1
ON v1.user_id = p1.user_id
WHERE v1.user_id = 43
AND DATE(v1.date) BETWEEN '2014-08-01' AND '2014-08-03'
AND p1.status = 0
UNION ALL
SELECT
count(v.id) AS visit_count,
count(p.id) AS point_count,
sum(p.points) AS points
FROM visits v2
JOIN points p2
ON v2.user_id = p2.user_id
WHERE v2.user_id = 43
AND DATE(v2.date) BETWEEN '2014-08-04' AND '2014-08-07'
AND p2.status = 0
) temp
You could try:
select count(id)
from visits
join points on points.user_id = visits.user_id
where date between 2014-08-04 00:00:00 and 2014-08-07 00:00:00
and visits.user_id = 43

Calculate overlapping durations in MySQL/PHP

This is making my head hurt! :P
I have an assignments table, and I'd like to calculate a member's duration based on their assignments. In its simplified form, this would be relatively straight forward.
-------------------------------------------------------------------------
| id | member_id | unit_id | start_date | end_date |
-------------------------------------------------------------------------
| 1 | 2 | 23 | 2013-01-01 | 2013-02-01 |
-------------------------------------------------------------------------
| 2 | 2 | 25 | 2013-02-01 | 2013-03-01 |
-------------------------------------------------------------------------
| 3 | 2 | 27 | 2013-03-01 | NULL |
-------------------------------------------------------------------------
This would just be a matter of doing a SUM() of the DATEDIFF() on start_date and end_date. The issue is that members have the potential to have concurrent assignments.
-------------------------------------------------------------------------
| id | member_id | unit_id | start_date | end_date |
-------------------------------------------------------------------------
| 1 | 2 | 23 | 2013-01-01 | 2013-02-01 |
-------------------------------------------------------------------------
| 2 | 2 | 25 | 2013-02-01 | 2013-03-01 |
-------------------------------------------------------------------------
| 3 | 2 | 30 | 2013-02-15 | 2013-03-01 |*
-------------------------------------------------------------------------
| 4 | 2 | 27 | 2013-03-01 | NULL |
-------------------------------------------------------------------------
Now I have to somehow realize that #3 occurred during the same time as #2, so I shouldn't add it to the SUM().
Going further, what if the member has gaps in their duration?
-------------------------------------------------------------------------
| id | member_id | unit_id | start_date | end_date |
-------------------------------------------------------------------------
| 1 | 2 | 23 | 2013-01-01 | 2013-02-01 |
-------------------------------------------------------------------------
| 2 | 2 | 25 | 2013-02-01 | 2013-02-05 |*
-------------------------------------------------------------------------
| 3 | 2 | 30 | 2013-02-15 | 2013-03-01 |*
-------------------------------------------------------------------------
| 4 | 2 | 27 | 2013-03-01 | NULL |
-------------------------------------------------------------------------
Also, NULL means "current" so that would be CURDATE().
Any ideas?
Here is the idea. Break each record into two to get a list of dates when assignments start and stop. Then determine how many assignments are active on a given date -- basically adding "1" for each start and "-1" for each end and taking the cumulative sum.
Next, you need to determine when the next date is to get periods before doing the final aggregation.
The first part is handled by this query:
select member_id, thedate,
#sumstart := if(#prevmemberid = memberid, #sumstart + isstart, isstart) as sumstart,
#prevmemberid := memberid
from (select member_id, start_date as thedate, 1 as isstart
from assignments
union all
select member_id, end_date, -1 as isstart
from assignments
order by member_id, thedate
) a cross join
(select #sumstart := 0, #prevmemberid := NULL) const;
The rest then uses more variables:
select member_id,
sum(case when sumstart > 0 then datediff(nextdate, thedate) end) as daysactive
from (select member_id, thedate, sumstart,
if(#prevmemberid = memberid, #nextdate, NULL) as nextdate,
#prevmemberid := memberid,
#nextdate = thedate
from (select member_id, thedate,
#sumstart := if(#prevmemberid = memberid, #sumstart + isstart, isstart) as sumstart,
#prevmemberid := memberid
from (select member_id, start_date as thedate, 1 as isstart
from assignments
union all
select member_id, coalesce(end_date, CURDATE()), -1 as isstart
from assignments
order by member_id, thedate
) a cross join
(select #sumstart := 0, #prevmemberid := NULL) const;
) a cross join
(select #nextmemberid := NULL, #nextdate := NULL) const
order by member_id, thedate desc;
) a
group by member_id;
I don't like using variables in this way, because MySQL does not guarantee the ordering of variable assignments in a given select. In practice, though, they are evaluated in the order written (which this query depends on). Although this could be written without variables, without the with statement, window functions, or even views that take subqueries in the from clause, the resulting SQL would be much uglier.
I think it's easier to perform filter out the overlapping assignments in the code rather than in SQL.
You can retrieve all the assignments for a certain member_id, ordered by start_date:
select * from assignments where member_id='2' order by start_date asc
You can then loop over these assignments and filter out the overlapping assignments.
Two assignments A and B are non-overlapping if A ends before B starts or if B ends before A starts.
Because we ordered the results according to start date, we can safely ignore the second case: B will never start before A, so it cannot end before A starts.
We then get something like:
for i=0..assignments.length
for j=i+1..assignments.length
if (assignments[j].start_date < assignments[i].end_date)
assignments[j] = null; // it overlaps -> get rid of it
Then loop over the assignments and sum the durations for the non-null assignments. This should be easy

Categories