How to Fetch Monthly Attendance report by student? - mysql

I got student_id=14 through form post and I need to fetch attendance report for the student_id as below
CLASS STUDENT_ID YEAR MONTH TOTAL_CLASSES TOTAL_PRESENT
11 14 2016 April 21 20
11 14 2016 May 25 25
11 14 2016 June 30 29
11 14 2016 July 18 18
11 14 2017 January 28 28
Here TOTAL_CLASSES represents total number days school is open and TOTAL_PRESENT represents total number of says a student is present out of TOTAL_CLASSES.
From HTML Form I GOT only student_id=14 and I need to fetch and show record as above.
Please see sqlfiddle here to support my answer http://sqlfiddle.com/#!9/63b6a/3
In my table remarks represents 1,2,3 for present and 0 for absent.

You're counting attendance numbers for each year, month, class and student. The key step in this query is to use GROUP BY to indicate the columns which you're grouping, then use the COUNT and SUM aggregation functions to compute the attendance columns you're looking for:
SELECT
class_id,
student_id,
YEAR(att_date) as year,
MONTH(att_date) as month,
COUNT(remarks) AS total_classes,
SUM(remarks > 0) AS total_present
FROM attendance
WHERE student_id = 15
GROUP BY YEAR(att_date), MONTH(att_date), class_id, student_id;
http://sqlfiddle.com/#!9/63b6a/11
You can remove the WHERE clause to show attendance for all students.

Related

Get range/count of days based off a single date field

I have requirement where i will need to get the number of days a role an employee was on.
Scenario 1
EmployeeId role effectiveFrom
1 A 1-Jan-2021
1 B 15-Jan-2021
No further roles are available for the month of Jan for role A therefore the number of days for role A would be 14.
Scenario 2
EmployeeId role effectiveFrom
1 A 1-Jan-2021
No further roles are available for the month of Jan therefore the number of days for role A would be 31 i.e the entire month of January. For the month of February i would expect to get 28 as the role would be effective for the entire month of february as well.
Scenario 3
EmployeeId role effectiveFrom
1 A 1-Jan-2021
1 B 15-Jan-2021
1 A 25-Jan-2021
To get the number of days for role A the logic would be
1 to 15th is 14 days.
25th to 31st(31st of Jan) would be 6 days.
14 + 6 = 20 days
The query i have come up with so far is this,
SELECT
DATEDIFF(MAX(effectiveFrom),
IF(MIN(effectiveFrom) = MAX(effectiveFrom),
MIN(effectiveFrom),
MIN(effectiveFrom))) + 1 daysWorked
FROM
EmployeeRoles
WHERE grade = 'A'
GROUP BY `employeeId`,effectiveFrom;
which would only give the result as 1 day for Scenario 1. Could someone guide me on the practical way of handling the scenarios. I have looked at loops, window functions but i am at a loss on the best way to proceed.
dbfiddle
When scenario2 has 31 days from 1-jan, until the end of the month, I would suspect that from 25-jan, until the end of the month, is 7 days, and not 6, as you write in scenario3.
The number of days, using above calculation:
SELECT
employeeID,
grade,
effectiveFrom,
DATEDIFF(COALESCE(LEAD(effectiveFrom)
OVER (PARTITION BY employeeID, grade ORDER By effectiveFrom),
DATE_ADD(LAST_DAY(effectiveFrom),INTERVAL 1 DAY)),
effectiveFrom) as '#Days'
FROM EmployeeRole;
This can be grouped, and summed giving:
SELECT
employeeID,
grade,
SUM(`#Days`)
FROM (
SELECT
employeeID,
grade,
effectiveFrom,
DATEDIFF(COALESCE(LEAD(effectiveFrom)
OVER (PARTITION BY employeeID, grade ORDER By effectiveFrom),
DATE_ADD(LAST_DAY(effectiveFrom),INTERVAL 1 DAY)),
effectiveFrom) as '#Days'
FROM EmployeeRole
) x
GROUP BY
employeeID,
grade;
output:
employeeID
grade
SUM(#Days)
1
A
14
1
B
17
2
A
31
3
A
21
3
B
10
see: DBFIDDLE
EDIT: The results were incorrect because the next effectiveFrom date was determined using OVER (PARTITION BY employeeID ORDER By effectiveFrom). this is not correct, because the grade should be taken into account too.
I corrected it to OVER (PARTITION BY employeeID, grade ORDER By effectiveFrom)
P.S. I also corrected this in the piece above the EDIT!
see: DBFIDDLE

SQL 4 week period in year grouping

I have an SQL table that contains order data by date. I'm trying to combine the data across years in 4 weeks buckets so that I can compare year on year periods. Luckily the table contains year and week number columns so that I can sum the data to show order totals by week number, for example:
By using SELECT order_year, order_week_number, sum(order_total) from f2l4d1a2ta_237_floodlight_order_data_v1 group by order_week_number ORDER BY order_week_number, order_year
I get:
order_year order_week_number sum(order_total)
2017 1 96.40879041
2017 2 33.34092216
2017 3 97.79772267
2017 4 28.05668819
2017 5 75.79034382
2017 6 41.59171513
2017 7 3.754344347
2017 8 66.27940579
2016 1 65.81290635
2016 2 71.17703765
2016 3 65.95184929
2016 4 90.42108213
2016 5 44.32837015
2016 6 19.9644766
2016 7 53.46359297
2016 8 7.059479081
However what I'm really after is to see the order total for the 4 week period in the year, i.e.
order_year 4 week period sum(order_total)
2017 1 255.6041234
2017 2 187.4158091
2016 1 293.3628754
2016 2 124.8159188
Does anyone know how to group data with SQL in this way?
Thanks,
Matt
Add 3 to the week number then integer divide by 4 (whole number result)
Eg (1+3) DIV 4 = 1, (4+3) DIV 4 = 1
So GROUP BY (weekno + 3) DIV 4

With a GROUP BY select, can you sort by the projection's SUM?

I have a SELECT that uses GROUP BY X. It uses SUM in the projection. Is there any way to sort on that SUM? The group result with the highest value should be first in results table. I suspect there is no way to sort on this SUM since as each group is completed it's output and gone; there isn't any "collection" to sort. There is something totally different I have to do here. Do you have any hints?
Thank you.
(beginner)
If I understand your question right, the answer is quite simple:
SELECT customer, SUM(amount) FROM mytable GROUP BY customer ORDER BY SUM(amount);
I'm not sure you meant this but here is an example
assume that there is data in a table which shows payments people have made to a bank.
But people pay different money in different times.
*id* *payment* *time*
1 10 01 04 2016
2 20 01 02 2016
1 35 14 03 2016
3 22 21 01 2016
2 50 01 04 2016
Now you want to calculate who has paid max money.
SELECT person_id, MAX(payment) AS totalPayment
from payments
group by person_id
ORDER BY totalPayment DESC

How to pass parameters in subquery mysql?

So, I have a mysql table with user id(id) and date of transaction(dot) that looks like:
id dot
-------------------------------
101 2015-06-12 12:18:42 UTC
102 2015-06-12 12:18:40 UTC
103 2015-06-12 12:18:42 UTC
101 2015-07-12 12:18:42 UTC
and so on.
(Output for this data should be:
Year Month Num of users
-----------------------------
2015 06 0
2015 07 2
)
It logs all the transactions that are made. For each month m, I want to find out the count of users by month and year who transacted in m-1 month but not in m month. The results need to be grouped by year and month. Ideally, table should look like (http://sqlfiddle.com/#!9/b80f49/1)
Year Month Num of users
-----------------------------
2015 05 0
2015 06 2
2015 07 1
2015 08 4
Now for a single month(E.g. 05/2015), I can hardcode:
SELECT "2015" AS Year,"05" AS Month, "COUNT(DISTINCT id) FROM table WHERE
MONTH(dot)=4 AND YEAR(dot)=2015
AND id NOT IN
(SELECT id FROM table WHERE MONTH(dot)=5 AND YEAR(dot)=2015)
To group the count of users using GROUP BY, the query would look like:
SELECT YEAR(dot) as Year,MONTH(dot),COUNT(DISTINCT id) as Month FROM table
WHERE id NOT IN(SELECT id FROM table
WHERE DATEDIFF(dot_parent,dot_this_table)<30 AND DATEDIFF(dot_parent,dot_this_table)>=0)
Here dot_parent is the dot of the parent query and dot_this_table is the dot of the subquery. Now the problem here is that I can't pass the dot_parent inside the subquery. Is there a way to do that or frame the query in another way such that its logical structure remains similar, since I would have to make similar queries for multiple date ranges.
You must query the same table thrice: once for the months to show, once to find the users in the previous months, once for user matches in the months in question. You'd select distinct users per month, as you are not interested in whether a user had more than one transaction in a month or not.
Here is the complete query:
select
this_month.year,
this_month.month,
count(prev_month_users.user) - count(this_month_users.user) as users
from
(
select distinct year(timing) as year, month(timing) as month
from transactions
) this_month
left join
(
select distinct
year(timing) as year, month(timing) as month, id as user,
year(date_add(timing, interval 1 month)) as next_month_year,
month(date_add(timing, interval 1 month)) as next_month_month
from transactions
) prev_month_users
on prev_month_users.next_month_year = this_month.year
and prev_month_users.next_month_month = this_month.month
left join
(
select distinct year(timing) as year, month(timing) as month, id as user
from transactions
) this_month_users
on this_month_users.user = prev_month_users.user
and this_month_users.year = prev_month_users.next_month_year
and this_month_users.month = prev_month_users.next_month_month
group by this_month.year, this_month.month;
Result:
year month users
2015 5 0
2015 6 2
2015 7 1
2015 8 3
Note that I show three users for August (users 101, 102, 104). User 101 had two transactions in July, but it is still three users who had transactions in July but not in August.
Here is your SQL fiddle back: http://sqlfiddle.com/#!9/b80f49/13

Get personal and total worked hours in the same query

I have the following table (simplified):
user_id date hours
1 2012-03-01 5
2 2012-03-01 8
3 2012-03-01 6
1 2012-03-02 3
3 2012-03-02 7
What I want is to get the the sum of hours worked for a given user id (ex. 1), and the total hours worked regardless of what user (for a given time period) in a single query.
So for user_id = 1, and time period: 2012-03-01 - 2012-03-02 the query should return: own=8, total=29.
I can do it in two separate queries, but not in a single one.
Use CASE:
SELECT SUM(
CASE user_id
WHEN 1 THEN hours
ELSE 0
END) as Own,
SUM(hours) as Total
FROM HoursWorked
WHERE date BETWEEN '2012-03-01' AND '2012-03-02';
I think I have something that works using the following schema:
CREATE TABLE hoursWorked
(
id int,
date date,
hours int
);
INSERT INTO hoursWorked
(id, date, hours)
VALUES
('1','2012-03-01','5'),
('2','2012-03-01','8'),
('3','2012-03-01','6'),
('1','2012-03-02','3'),
('3','2012-03-02','7');
And this query:
select parent.id, parent.date, parent.hours, (select sum(hours)
from hoursWorked child
where child.id = parent.id) as totalHours
from hoursWorked parent
I was able to get these results:
ID DATE HOURS TOTALHOURS
1 March, 01 2012 00:00:00-0800 5 8
2 March, 01 2012 00:00:00-0800 8 8
3 March, 01 2012 00:00:00-0800 6 13
1 March, 02 2012 00:00:00-0800 3 8
3 March, 02 2012 00:00:00-0800 7 13
Diego's answer albeit procedural is a great way to get the answer you are looking for. Of course for your date range you would need to add a WHERE date BETWEEN 'startdate' AND 'enddate'. The dates need to be in a format that mysql recognizes, typically 'yyyy-mm-dd'
Another solution that doesn't get you the results in one row, but in a result set would be to do a UNION
SELECT user_id, SUM(hours) as hours FROM table WHERE date BETWEEN 'startdate' AND 'enddate' WHERE user_id = 3
UNION
SELECT null as user_id, SUM(hours) as hours FROM table WHERE date BETWEEN 'startdate' AND 'enddate'