Student absent for 5 consecutive days excluding Holidays - mysql

I am using code igniter and attendance table as below:
attendance ID timestamp student_id status
1 01-01-20 1 P
2 01-01-20 2 P
3 02-01-20 1 P
4 02-01-20 2 A
5 03-01-20 1 P
6 03-01-20 2 A
7 04-01-20 1 H
8 04-01-20 2 H
9 05-01-20 1 P
10 05-01-20 2 A
My target is to get the student id who is absent for 3 consecutive days for the period of the last 1 month from today excluding the holidays in the middle like in the above table the student id 2 should be the one with 3 consecutive absence excluding the holiday on the 4th Jan.
I am using Apache/2.4.23 (Win32) OpenSSL/1.0.2h PHP/5.6.28 with mysql. I have been able to get the consecutive 3 absent but when there is a holiday in the middle, I fail to find a work around there. here is my existing code:
SELECT *,
CASE
WHEN (#studentID = student_id) THEN #absentRun := IF(status = A, 0, #absentRun + 1)
WHEN (#studentID := student_id) THEN #absentRun := IF(status = A, #absentRun + 1, 0)
END AS absentRun
FROM attendance
Where timestamp BETWEEN (CURRENT_DATE() - INTERVAL 1 MONTH) AND CURRENT_DATE() AND year='2019-2020'
ORDER BY student_id, timestamp;
I would really really appreciate a quick answer from someone to help me with this. I am really hope to have a solution since I have posted here for help for the first time. Thanks in advance.

I understand this as a variant of the gaps and island problem. Here is one way to solve it using row_number() (available in MySQL 8.0) - the difference between the row numbers gives you the group each record belongs to.
select
student_id,
min(timestamp) timestamp_start,
max(timestramp) timestamp_end
from (
select
t.*,
row_number() over(partition by student_id order by timestamp) rn1,
row_number() over(partition by student_id, status order by timestamp) rn2
from mytable t
) t
where status = 2
group by student_id, rn1 - rn2
having count(*) >= 5
This will give you one record for each streak of at least 5 consecutive days of absence for each student. As a bonus, the query also displays the starting and ending date of each streak.

Related

Select max 1 record per year from a MySQL table

I need to extract data from a MySQL table, but am not allowed to include a record if there's a previous record less than a year old.
Given the following records, only the records 1, 3 and 5 should be included (because record 2 was created 1 month after record 1, and record 4 was created 1 month after record 3):
1 2019-12-21
2 2020-01-21
3 2021-12-21
4 2022-01-21
5 2023-12-21
I came up with the following non-functional solution:
SELECT
*
FROM
table t
WHERE
(created_at > DATE_ADD(
(SELECT
created_at
FROM
table t2
WHERE
t2.created_at < t.created_at
ORDER BY
t2.created_at
DESC LIMIT 1), INTERVAL 1 YEAR)
But this only returns the first and the last record, but not the third:
1 2019-12-21
5 2023-12-21
I know why: the third record gets excluded because record 2 is less than a year old. But record 2 shouldn't be taken into account, because it won't make the list itself.
How can I solve this?
Using lag, assuming your MySql supports it, you can calculate the difference in months using period_diff
with d as (
select * ,
period_diff(extract(year_month FROM date),
extract(year_month from lag(date,1,date) over (order by date))
) as m
from t
)
select id, date
from d
where m=0 or m>12
Demo Fiddle

Selecting the latest row for each customer that matches these params

I have an SQL table that stores reports. Each row has a customer_id and a building_id and when I have the customer_id, I need to select the latest row (most recent create_date) for each building with that customer_id.
report_id customer_id building_id create_date
1 1 4 1553561789
2 2 5 1553561958
3 1 4 1553561999
4 2 5 1553562108
5 3 7 1553562755
6 3 8 1553570000
I would expect to get report id's 3, 4, 5 and 6 back.
How do I query this? I have tried a few sub-selects and group by and not gotten it to work.
If you are using MySQL 8+, then ROW_NUMBER is a good approach here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY customer_id, building_id
ORDER BY create_date DESC) rn
FROM yourTable
)
SELECT
report_id,
customer_id,
building_id,
create_date
FROM cte
WHERE rn = 1;
If there could be more than one customer/building pair tied for the latest creation date, and you want to capture all ties, then replace ROW_NUMBER with RANK, and use the same query.
Another variation:
SELECT a.*
FROM myTable a
WHERE a.create_date = (SELECT MAX(create_date)
FROM myTable b
WHERE b.customer_id = a.customer_id
AND b.building_id = a.building_id)
Can try doing a search for "effective dated records" to see various approaches.

Cumulative sum query on foreign key

I want to write a query for cumulative sum in MYSQL. I have a foreign key in my table and I want to add their hours as a cumulative sum.
Table 1
id(not primary key) Hours
1 4
2 4
1 5
I have tried this query
select spent_hours := spent_hours + hours as spent
from time
join (select spent_hours := 0) s
I am getting this
id(not primary key) hours spent
1 4 4
2 4 8
1 5 13
But I want this result:
id(not primary key) Hours spent
1 4 4
2 4 4
1 5 9
Since you have an autoincrement field (let's assume for this case its called record_id) you can use this little trick to achieve what you want:
SELECT Main.id, Main.spentHours,
(
SELECT SUM(spentHours)
FROM Table1 WHERE Table1.id = Main.id
AND Table1.record_id >= Main.record_id
) as totalSpentHours
FROM Table1 Main
ORDER BY Main.record_id ASC
This will fetch the id, current spent hours, along using a subselect, all hours from the current ID and above for that user.
You need additional an variable to keep track of the cumulative sum within each id:
select t.id, t.hours,
(#h := if(#i = id, #h + spent_hours,
if(#i := id, spent_hours, spent_hours)
)
) as spent
from time cross join
(select #h := 0, #i := 0) params
order by id, ??;
Note: you need an additional column to specify the order for the cumulative sum (indicated by ?? in the order by clause. Remember that SQL tables represent unordered sets, so you need a column to explicitly represent ordering.

need to fetch all the students who have not paid for the current month

I have two tables
Students table :
id studentname admissionno
3 test3 3
2 test2 2
1 test 1
2nd table is fee :
id studentid created
1 3 2015-06-06 22:55:34
2 2 2015-05-07 13:32:48
3 1 2015-06-07 17:47:46
I need to fetch the students who haven't paid for the current month,
I'm performing the following query:
SELECT studentname FROM students
WHERE studentname != (select students.studentname from students
JOIN submit_fee
ON (students.id=submit_fee.studentid)
WHERE MONTH(CURDATE()) = MONTH(submit_fee.created)) ;
and I'm getting error:
'#1242 - Subquery returns more than 1 row'
Can you tell me what the correct query is to fetch all the students who haven't paid for the current month?
Use not in, please try query below :
SELECT s.*
FROM students s
WHERE s.id NOT IN ( SELECT sf.studentid FROM studentfees sf WHERE month(sf.created) = EXTRACT(month FROM (NOW())) )
You want to use not exists or a left join for this:
select s.*
from students s
where not exists (select 1
from studentfees sf
where s.id = sf.studentid and
sf.created >= date_sub(curdate(), interval day(curdate) - 1) and
sf.created < date_add(date_sub(curdate(), interval day(curdate) - 1), 1 month)
)
Note the careful construction of the date arithmetic. All the functions are on curdate() rather than on created. This allows MySQL to use an index for the where clause, if one is appropriate. One error in your query is the use of MONTH() without using YEAR(). In general, the two would normally be used together, unless you really want to combine months from different years.
Also, note that paying or not paying for the current month may not really answer the question. What if a student paid for the current month but missed the previous month's payment?

query to sum a column until get defined value

i've tried some other topics for this but couldn't get answers that meet my requirement so posting a new question. sorry bout this.
i'm trying to query on mysql to get a 'sum' data until it reaches the defined value. like
from my table 'purchase', for each 'sid' starting from the last row, i need sum of 'pqty' until the result equals a value from string (but to try i've given a certain value).
let me define with the rows from my table---
the rows for 'sid=1' from 'purchase' are like this---
date pqty prate pamt
2014/04/29 5 38000 190000
2014/05/04 1 38000 38000
2014/05/13 20 35000 700000
2014/05/19 1 38000 38000
from this row, starting from the last row i want to 'sum(pqty) until it reaches 19(for now). it is achieved from adding last 2 rows(for 19). and stop sum here and return valus or sum of 'pqty', 'prate' and 'pamt'. to achieve this i tried the following according to example found on this forum.
SELECT date, pqty, #total := #total + pqty AS total
FROM (purchase, (select #total :=0) t)
WHERE #total<19 AND sid = $sid ORDER BY date DESC
but it's not working for me. please guide me through this. also suggest something else if this is not the good technique for my purpose.
thankz in advance.....
Not 100% certain, but I think both of these work...
SELECT x.*, SUM(y.pqty) FROM purchase x
JOIN purchase y
ON y.date >= x.date
GROUP
BY x.date
HAVING 19 BETWEEN SUM(y.pqty)-x.pqty AND SUM(y.pqty)
OR 19 >= SUM(y.pqty);
SELECT a.*
FROM
( SELECT x.*, #i := #i+pqty i
FROM purchase x
, (SELECT #i:= 0) var
ORDER
BY x.date DESC
) a
WHERE 19 BETWEEN a.i-a.pqty AND a.i
OR 19 >= a.i;