Reputation: 529
I have two tables; student
and attendance
:
Student table:
sid name
----- --------
s1 nam1
s2 nam2
s3 nam3
s4 nam4
s5 nam5
Attendance table:
sid status date sub_id
----- -------- --------- ------
s1 present 2017-05-16 ms100
s2 present 2017-05-16 ms100
s3 absent 2017-05-16 ms100
s4 present 2017-05-16 ms100
s5 present 2017-05-16 ms100
s1 present 2017-05-17 ms100
s2 present 2017-05-17 ms100
s3 absent 2017-05-17 ms100
s4 present 2017-05-17 ms100
s5 absent 2017-05-17 ms100
s1 present 2017-05-16 ms101
s2 present 2017-05-16 ms101
s3 absent 2017-05-16 ms101
s4 present 2017-05-16 ms101
s5 absent 2017-05-16 ms101
Now I want to show on which date students were present or absent also want to count the total attended class, consecutive absent and percentage of attendance for every student for the subject id ms100.
For consecutive missing/absent i want to to consider only last consecutive missing/absent.For example if among 10 days s1 was present on day 1, 6, 7 then his con_missing will be 3
not 5
. If s1 was present on day 9 then his con_missing will be 0 as i want to consider consecutive missing only when a student is absent for more than 1 days.
For example students with sid s1,s2,s4
were present in all the 2 classes of the subject ms100
so their total number of attended class
will be 2, as the were present in all classes so their total number of consecutive absent
will be 0
and percentage
will be {(total attended class/total class)*100}
which is 100 %
in this case. On the other hand s3
was absent in all classes so his total number of attended class
and percentage
will be 0
and total number of consecutive absent
will be 2.
For student id s5 the consecutive absent
will be 0
as he is absent just for one day.
I am expecting result like following pattern where each individual class date for the subject ms100
will be shown as column and attendance status(present/absent) of an individual student on that particular date will be shown as value of that column:
sid name 2017-05-16 2017-05-17 consecutive_absnt total_atn %
----- ----- ---------- --------- ---------------- --------- ----
s1 nam1 present present 0 2 100
s2 nam2 present present 0 2 100
s3 nam3 absent absent 2 0 0
s4 nam4 present present 0 2 100
s5 nam5 present absent 0 1 50
I am using Angularjs
as font end and php as back end.This is what i have tried so far
php
$query="
SELECT atn.sid
, atn.date
, atn.status
, s.name
FROM attendance atn
join student s
on atn.sid = s.sid
where atn.sub_id = 'ms100'
ORDER
BY atn.date
, atn.sid
";
$result = $mysqli->query($query) or die($mysqli->error.__LINE__);
$arr = array();
if($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
$arr[] = $row;
}
}
# JSON-encode the response
$json_response = json_encode($arr);
// # Return the response
echo $json_response;
angularjs
<table class="table table-striped table-bordered">
<thead>
<th>sid</th>
<th>name</th>
<th ng-repeat="data in list | unique: 'date'">{{data.date}}</th>
<th>consecutive missing</th>
<th>total attended </th>
<th>%</th>
</thead>
<tbody>
<tr ng-repeat="data in filtered = (list | filter:search | orderBy : predicate :reverse) | startFrom:(currentPage-1)*entryLimit | limitTo:entryLimit">
<td>{{data.sid}}</td>
<td>{{data.name}}</td>
<td>{{data.status}}</td>
<td>{{data.consecutive}}</td>
<td>{{data.total_atn}}</td>
<td>{{data.percentage}}</td>
</tbody>
</table>
Getting result like this
sid name 2017-05-16 2017-05-17 con_missing totl_atend %
----- -------- --------- ---------- -------- -------- ---
s1 nam1 present
s2 nam2 present
s3 nam3 absent
s4 nam4 present
s5 nam5 present
s1 nam1 present
s2 nam2 present
s3 nam3 absent
s4 nam4 present
s5 nam5 absent
So how can I achieve my expected result through mysql query?
Upvotes: 0
Views: 2040
Reputation: 1946
Ok this took a while to get together :)
First off we setup a function in the mysql database to get the consecutive number of days:
CREATE FUNCTION `getConsecutive`( _subid varchar(45), _sid varchar(45) ) RETURNS int(11)
BEGIN
declare ret int;
select max(consecutive) into ret from (
select q.date,
q.status,
@consecutive := CASE WHEN @stop = 1 THEN 0 WHEN q.status = 'absent' THEN @consecutive +1 ELSE 0 END as consecutive,
@started := CASE WHEN @consecutive > 0 OR @started > 0 THEN 1 ELSE 0 END as started,
@stop := CASE WHEN @consecutive = 0 AND @started > 0 THEN 1 ELSE @stop END as stop
from (
select date, status from Attendance where sub_id = _subid and sid = _sid order by date desc
) q,
(select @consecutive := 0) r,
(select @started := 0) r2,
(select @stop := 0) r3
) as z;
RETURN CASE WHEN ret = 1 THEN 0 ELSE ret END;
END
Then we build the sql, but hardcoded to test for 2 columns:
SELECT atn.sid, s.name,
MAX(IF(atn.date = '2017-05-16', atn.date, null)) `2017-05-16`,
MAX(IF(atn.date = '2017-05-17', atn.date, null)) `2017-05-17`,
getConsecutive(atn.sub_id, atn.sid) consecutive_absnt,
SUM(CASE WHEN atn.status = 'present' THEN 1 ELSE 0 END) total_atn,
ROUND(100*(SUM(CASE WHEN atn.status = 'present' THEN 1 ELSE 0 END)/count(1)), 2) '%'
FROM Attendance atn
join Student s
on atn.sid = s.sid
where atn.sub_id = 'ms100'
GROUP BY atn.sid, s.name;
Then we know it works but the main problem is that it is a dynamically number of columns.. so you will need to do is divide this up in parts.
We need a stored procedure that will build and execute our dynamic sql.
CREATE PROCEDURE `getData`(_subId VARCHAR(45))
BEGIN
select GROUP_CONCAT(CAST(CONCAT('MAX(IF(atn.date = \'', dd, '\', atn.date, null)) `', dd, '`\n') AS CHAR)) INTO @builtSql
from (
select distinct str_to_date(date, '%Y-%m-%d') dd from Attendance where sub_id = _subId
) q
;
SET @builtSql = CONCAT('SELECT atn.sid,
s.name,', @builtSql, ',
getConsecutive(atn.sub_id, atn.sid) consecutive_absnt,
SUM(CASE WHEN atn.status = \'present\' THEN 1 ELSE 0 END) total_atn,
ROUND(100*(SUM(CASE WHEN atn.status = \'present\' THEN 1 ELSE 0 END)/count(1)), 2) \'%\'
FROM Attendance atn
join Student s on atn.sid = s.sid
where atn.sub_id = \'', _subId,'\'
GROUP BY atn.sid, s.name');
PREPARE stmt FROM @builtSql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
To build a dynamic sql is usually a bad idea because of the risk of sql injection, so i advise you to do some checks on the data sent into the stored procedure.
Then you can just call the Stored Procedure to get the result you wanted above.
call getData('ms100')
Upvotes: 1