Mysql Pivot table for attendance per weekdays of week - mysql

for a date interval, I want to display the attendance per weekdays of students records, recorded in three tables [Attendance, Students, Person].
The schema (relevant fields) of tables:
Attendance Table
---------------------
Attendance_Identifier
Student_Identifier
Classroom_Identifier
Attendance_Datetime
Attendance_Value
...
Student
------------------
Student_Identifier
Person_Identifier
Classroom_Identifier
...
Person
-----------------
Person_Identifier
Frist_Name
Last_Name
Gender
...
Expected Report Output:
Gender Student Mon Tue Wed Thu Fri WeeklyTotal
------------------------------------------------------------------------
Male Ab Stain 2/10 3/12 1/9 1/10 0/10 7/51
Male Pre Senter 10/10 12/12 9/9 9/10 10/10 50/51
...
Female Al Ways 10/10 12/12 9/9 10/10 10/10 51/51
Female Not Often 5/10 5/12 4/9 4/10 5/10 23/51
...
I also have a function to get the count of particular day of week from a date interval say for total Mondays in date interval #s, #e, just do: select get_weekday(#s,#e,0).
So my query for my stored procedure is :
set #s = '2016-01-01';
set #e = '2016-12-07';
SELECT
concat(p.Frist_Name, ' ',p.Last_Name) as Student, p.Gender
,GROUP_CONCAT(CASE WHEN DATE_FORMAT(Attendance_Datetime, '%a') = 'Mon'
THEN CONCAT(COUNT(Attendance_Value),'/',get_weekday(#s,#e,0)) ELSE NULL END) AS Mon
,GROUP_CONCAT(CASE WHEN DATE_FORMAT(Attendance_Datetime, '%a') = 'Tue'
THEN CONCAT(COUNT(Attendance_Value),'/',get_weekday(#s,#e,1)) ELSE NULL END) AS Tue
,GROUP_CONCAT(CASE WHEN DATE_FORMAT(Attendance_Datetime, '%a') = 'Wed'
THEN CONCAT(COUNT(Attendance_Value),'/',get_weekday(#s,#e,2)) ELSE NULL END) AS Wed
,GROUP_CONCAT(CASE WHEN DATE_FORMAT(Attendance_Datetime, '%a') = 'Thu'
THEN CONCAT(COUNT(Attendance_Value),'/',get_weekday(#s,#e,3)) ELSE NULL END) AS Thu
,GROUP_CONCAT(CASE WHEN DATE_FORMAT(Attendance_Datetime, '%a') = 'Fri'
THEN CONCAT(COUNT(Attendance_Value),'/',get_weekday(#s,#e,4)) ELSE NULL END) AS Fri
, SUM(COUNT(Attendance_Value)) as WeeklyTotal
FROM Attendance a JOIN Student s ON s.Student_Identifier=a.Student_Identifier
JOIN Person p ON p.Person_Identifier=s.Person_Identifier
WHERE date(Attendance_Datetime) BETWEEN #s AND #e AND a.Classroom_Identifier = '363'
AND (Attendance_Value = 'Present' OR Attendance_Value = 'Late') AND (p.Gender = 'Male' OR p.Gender = 'Female')
AND DATE_FORMAT(Attendance_Datetime, '%a') !='Sat' AND DATE_FORMAT(Attendance_Datetime, '%a')!='Sun'
GROUP BY p.Gender, Student, WeeklyTotal ORDER BY p.Gender, Student;
And I get the following group function error each time even with many tweaks to the code.
Error Code: 1111. Invalid use of group function
Edit
Based on #Solarflare 's first suggestion (I don't know how to try the second one), I got the query refined to:
SELECT
concat(p.Frist_Name, ' ',p.Last_Name) as Student, p.Gender
,CONCAT(CASE WHEN DATE_FORMAT(Attendance_Datetime, '%a') = 'Mon'
THEN COUNT(Attendance_Value) ELSE 0 END,'/',get_weekday(#s,#e,0) ) AS Mon
,CONCAT(CASE WHEN DATE_FORMAT(Attendance_Datetime, '%a') = 'Tue'
THEN COUNT(Attendance_Value) ELSE 0 END,'/',get_weekday(#s,#e,1) ) AS Tue
,CONCAT(CASE WHEN DATE_FORMAT(Attendance_Datetime, '%a') = 'Wed'
THEN COUNT(Attendance_Value) ELSE 0 END,'/',get_weekday(#s,#e,2) ) AS Wed
,CONCAT(CASE WHEN DATE_FORMAT(Attendance_Datetime, '%a') = 'Thu'
THEN COUNT(Attendance_Value) ELSE 0 END,'/',get_weekday(#s,#e,3) ) AS Thu
,CONCAT(CASE WHEN DATE_FORMAT(Attendance_Datetime, '%a') = 'Fri'
THEN COUNT(Attendance_Value) ELSE 0 END,'/',get_weekday(#s,#e,4) ) AS Fri
, COUNT(Attendance_Value) as WeeklyTotal
FROM Attendance a JOIN Student s ON s.Student_Identifier=a.Student_Identifier
JOIN Person p ON p.Person_Identifier=s.Person_Identifier
WHERE date(Attendance_Datetime) BETWEEN #s AND #e AND a.Classroom_Identifier = '363'
AND (Attendance_Value = 'Present' OR Attendance_Value = 'Late') AND (p.Gender = 'Male' OR p.Gender = 'Female')
AND DATE_FORMAT(Attendance_Datetime, '%a') !='Sat' AND DATE_FORMAT(Attendance_Datetime, '%a')!='Sun'
GROUP BY Student,Gender,DATE_FORMAT(Attendance_Datetime, '%a') ORDER BY p.Gender, Student,Mon DESC;
and the result is as below:

There were just some minor things left to make it work, try this:
SELECT
concat(p.Frist_Name, ' ',p.Last_Name) as Student, p.Gender
,concat(cast(COUNT(CASE WHEN weekday(Attendance_Datetime) = 0
then 1 END) as char), '/', get_weekday(#s,#e,0)) as Mon
,concat(cast(COUNT(CASE WHEN weekday(Attendance_Datetime) = 1
then 1 END) as char), '/', get_weekday(#s,#e,1)) as Tue
,concat(cast(COUNT(CASE WHEN weekday(Attendance_Datetime) = 2
then 1 END) as char), '/', get_weekday(#s,#e,2)) as Wed
,concat(cast(COUNT(CASE WHEN weekday(Attendance_Datetime) = 3
then 1 END) as char), '/', get_weekday(#s,#e,3)) as Thu
,concat(cast(COUNT(CASE WHEN weekday(Attendance_Datetime) = 4
then 1 END) as char), '/', get_weekday(#s,#e,4)) as Fri
,COUNT(Attendance_Value) as WeeklyTotal
FROM Attendance a
JOIN Student s ON s.Student_Identifier=a.Student_Identifier
JOIN Person p ON p.Person_Identifier=s.Person_Identifier
WHERE date(Attendance_Datetime) BETWEEN #s AND #e
AND a.Classroom_Identifier = '363'
AND (Attendance_Value = 'Present' OR Attendance_Value = 'Late')
AND (p.Gender = 'Male' OR p.Gender = 'Female')
AND weekday(Attendance_Datetime) not in (5,6)
GROUP BY Student, Gender ORDER BY p.Gender, Student, Mon DESC;
The pivot is done with the case, it counts when the date has the correct weekday. Otherwise the value is null (not 0), only then it doesn't count (else null has not to be written).

Related

Workaround for Subquery returns more than 1 error

Please help me out.
I'm creating a query to list down all the Principal (SLE_CODE = 11) and Interest (SLE_CODE = 23) Payment made by the client's loan (REF_NO field) in a columnar way.
I tried this code, but it returns an error of "subquery returns more than 1 row".
SELECT DATE_FORMAT(TR_DATE, '%M %d, %Y'), SL_CLIENTID, REF_NO, SLE_CODE, (select amt from sldtl where REF_NO = 1958
AND SL_CLIENTID = 1782 AND SLC_CODE = 12 AND SLE_CODE = 23) as interest,
(select amt from sldtl sd where REF_NO = 1958
AND SL_CLIENTID = 1782 AND SLC_CODE = 12 AND SLE_CODE = 11) as principal
FROM sldtl
WHERE REF_NO = 1958
AND SL_CLIENTID = 1782 AND SLC_CODE = 12
Please refer to this sqlfiddle:
http://sqlfiddle.com/#!9/3ac32/2
Thank you in advance.
Sample data and desired results would really help. I'm pretty sure you want conditional aggregation. Perhaps this:
SELECT DATE_FORMAT(TR_DATE, '%M %d, %Y'),
SL_CLIENTID, REF_NO,
SUM(CASE WHEN SLE_CODE = 23 THEN amt END) as interest,
SUM(CASE WHEN SLE_CODE = 11 THEN amt END) as principal
FROM sldtl
WHERE REF_NO = 1958 AND SL_CLIENTID = 1782 AND SLC_CODE = 12
GROUP BY DATE_FORMAT(TR_DATE, '%M %d, %Y'),
SL_CLIENTID, REF_NO;
You need a case.. when expression :
SELECT DATE_FORMAT(TR_DATE, '%M %d, %Y'),
SL_CLIENTID, REF_NO, SLE_CODE,
case when SLE_CODE = 23 then AMT else null end as interest,
case when SLE_CODE = 11 then AMT else null end as principal
FROM sldtl
WHERE REF_NO = 1958
AND SL_CLIENTID = 1782
AND SLC_CODE = 12;
Demo

SQL JOIN, GROUP BY on Four tables to get Record By Month

I have the following DB design. Tables are:
auth_user
----------
first_name
last_name
staffuser
----------
phone_number
user_id
billing_customerservicebill
-----------------------------
bill_id
service_provider_id
discounted_price
billing_billmanagement
------------------------
creation_date
My query return Sum of discounted_price each user by month row wise. I need every month record show in column.
The following query gives me This record
select a.service_provider_id, first_name, Sum(a.discounted_price), EXTRACT(MONTH FROM c.creation_date)
from billing_customerservicebill a
left outer join users_staffuser b
on a.service_provider_id = b.id
left outer join billing_billmanagement c
on a.bill_id = c.id
left outer join auth_user d
on d.id = b.user_id
where c.creation_date between '2017-11-01' AND '2017-12-31'
group by service_provider_id, first_name, EXTRACT(MONTH FROM c.creation_date)
order by 1
My data show in Table Currently
service_provider_id | first_name | Sum | Month
5 | suneel 31500 | 11
5 | Suneel | 900 | 12
Expected data is
service_provider_id | first_name | Nov | December
5 | suneel | 31500 | 900
The most flexible approach is to use conditional aggregation...
select
a.service_provider_id,
first_name,
SUM(CASE WHEN c.creation_date >= '2017-11-01' AND c.creation_date < '2017-12-01' THEN a.discounted_price END) AS nov,
SUM(CASE WHEN c.creation_date >= '2017-12-01' AND c.creation_date < '2018-01-01' THEN a.discounted_price END) AS dec
from billing_customerservicebill a
left outer join users_staffuser b
on a.service_provider_id = b.id
left outer join billing_billmanagement c
on a.bill_id = c.id
left outer join auth_user d
on d.id = b.user_id
where c.creation_date between '2017-11-01' AND '2017-12-31'
group by service_provider_id, first_name
order by 1
This shows that you need to know in advance which columns you're going to calculate.
Please, try with below solution it's near to your answer:
Where month as column and group by users:
select B.service_provider_id, B.first_name,
(case when month=1 then discounted_price else 0 end) as JAN,
(case when month=2 then discounted_price else 0 end) as FEB,
(case when month=3 then discounted_price else 0 end) as MAR,
(case when month=4 then discounted_price else 0 end) as APR,
(case when month=5 then discounted_price else 0 end) as MAY,
(case when month=6 then discounted_price else 0 end) as JUN,
(case when month=7 then discounted_price else 0 end) as JULY,
(case when month=8 then discounted_price else 0 end) as AUG,
(case when month=9 then discounted_price else 0 end) as SEP,
(case when month=10 then discounted_price else 0 end) as OCT,
(case when month=11 then discounted_price else 0 end) as NOV,
(case when month=12 then discounted_price else 0 end) as DEC
from(
select a.service_provider_id, first_name, Sum(a.discounted_price) as discounted_price, EXTRACT(MONTH FROM c.creation_date) as month
from billing_customerservicebill a
left outer join users_staffuser b
on a.service_provider_id = b.id
left outer join billing_billmanagement c
on a.bill_id = c.id
left outer join auth_user d
on d.id = b.user_id
where c.creation_date between '2017-11-01' AND '2017-12-31'
group by service_provider_id, first_name, EXTRACT(MONTH FROM c.creation_date)
) as B
group by B.service_provider_id, B.first_name

Case in where condition to calculate individual totals

select
Count(*)
from gl
join TRVMAINDATA msit
on gl.TRANSACTIONID = msit.CHARGETRANSACTIONID
where gl.CREATEDDATETIME >= DATEADD(MONTH,-2,getdate()) AND
datediff(DAY,gl.BUSINESSPROCESSDATE,gl.CREATEDDATETIME)>=4 AND
gl.MARKETCODE In('535','532','056','050','039','036','034','033','030','029','027','023','022','021','018','015','012','011','010','009','007','006','005','002','001' ) and DATEADD(MONTH,DATEDIFF(MONTH,0,gl.CREATEDDATETIME),0) = '2/1/2017 12:00:00 AM'
The above code gives the total for market code mentioned.
But i also want the market code for other code which doesn't belog here. for example
select
Count(*)
from gl
join TRVMAINDATA msit
on gl.TRANSACTIONID = msit.CHARGETRANSACTIONID
where gl.CREATEDDATETIME >= DATEADD(MONTH,-6,getdate()) AND
datediff(DAY,gl.BUSINESSPROCESSDATE,gl.CREATEDDATETIME)>=0
and
DATEADD(MONTH,DATEDIFF(MONTH,0,gl.CREATEDDATETIME),0) = '2/1/2017 12:00:00 AM'
AND
(case
when gl.MARKETCODE In('535','532','056','050','039','036','034','033','030','029','027','023','022','021','018','015','012','011','010','009','007','006','005','002','001') then 'Proprietary'
when gl.MARKETCODE='037' then 'US'
else 'partner'
end )
Is it possible to calculate totals of other market codes in one sql query or do i have to calculate individually?
I think you would like to do count with case when in select statement like this:
SELECT COUNT(CASE
WHEN gl.marketcode IN ( '535', '532', '056', '050',
'039', '036', '034', '033',
'030', '029', '027', '023',
'022', '021', '018', '015',
'012', '011', '010', '009',
'007', '006', '005', '002', '001' ) THEN 1
ELSE NULL
END) AS `Proprietary`,
COUNT(CASE
WHEN gl.marketcode = '037' THEN 1
ELSE NULL
END) AS `US`,
COUNT(CASE
WHEN gl.marketcode NOT IN ( '535', '532', '056', '050',
'039', '036', '034', '033',
'030', '029', '027', '023',
'022', '021', '018', '015',
'012', '011', '010', '009',
'007', '006', '005', '002',
'001', '037' ) THEN 1
ELSE NULL
END) AS `partner`
FROM gl
JOIN trvmaindata msit
ON gl.transactionid = msit.chargetransactionid
WHERE gl.createddatetime >= DATEADD(month, -2, GETDATE())
AND DATEDIFF(day, gl.businessprocessdate, gl.createddatetime) >= 4
AND DATEADD(month, DATEDIFF(month, 0, gl.createddatetime), 0) = '2/1/2017 12:00:00 AM'

Nested SQL Query for count of months

Locked. There are disputes about this question’s content being resolved at this time. It is not currently accepting new answers or interactions.
I am new to SQL and would like to know how to approach writing a query for this question.
Lets say we have these fields:
date_created date_unsubscribed subscriberid
How to write a SQL query that lists, by month, how many people subscribed to the list, unsubscribed from the list, and how many net subscribers there were (new subscribers minus unsubscribers).
All in a single query...
Here's one option using conditional aggregation and union all:
select month(dt),
count(case when subscribe = 1 then 1 end) subscribecount,
count(case when subscribe = -1 then 1 end) unsubscribecountt,
sum(subscribe) overallcount
from (
select date_created as dt, 1 as subscribe
from yourtable
union all
select date_unsubscribed, -1
from yourtable
where date_unsubscribed is not null
) t
group by month(dt)
The subquery creates a list of dates with a flag for subscribe or unsubscribe. Then you can use count with case to determine the appropriate number of subscribers/unsubscribers.
SQL Fiddle Demo
You could write a sum(case) (a sum with conditions) to aggregate - assuming the date_created column is never null. For instance:
ORACLE:
SELECT
TO_CHAR(DATE_CREATED,'MM-YYYY') CREATE_MONTH
,SUM(CASE WHEN date_unsubscribed is not null then 1 else 0 end) unsubscribed
,SUM(CASE WHEN date_unsubscribed is null then 1 else 0 end) subscribed
,COUNT(SUBSCRIBER_ID)
FROM
--YOURTABLENAME
--WHERE
--WHATEVER OTHER CONDITIONS YOU HAVE APPLY
GROUP BY TO_CHAR(DATE_CREATED,'MM-YYYY')
MYSQL:
SELECT
DATE_FORMAT(DATE_CREATED,'%m-%Y') CREATE_MONTH
,SUM(CASE WHEN date_unsubscribed is not null then 1 else 0 end) unsubscribed
,SUM(CASE WHEN date_unsubscribed is null then 1 else 0 end) subscribed
,COUNT(SUBSCRIBER_ID)
FROM
--YOURTABLENAME
--WHERE
--WHATEVER OTHER CONDITIONS YOU HAVE APPLY
GROUP BY DATE_FORMAT(DATE_CREATED,'%m-%Y')
Oracle solution
Here is a query using the PIVOT operator, which was created exactly for this kind of work, and ROLLUP to get the net number. This is just for illustration; I assume the year is a user or application input (bind variable :year, set to 2015 for the output), and I show the summary for January through June.
with
test_data ( date_created, date_unsubscribed, subscriber_id ) as (
select date '2015-05-10', null , 330053448 from dual union all
select date '2015-04-28', null , 330053457 from dual union all
select date '2015-05-10', null , 330053466 from dual union all
select date '2015-04-28', null , 220053475 from dual union all
select date '2015-04-28', date '2015-05-10', 330053484 from dual
),
prep ( type, val, mth ) as (
select 'Subscribed' , 1, extract(month from date_created) from test_data
where extract(year from date_created) = :year
union all
select 'Unsubscribed', -1, extract(month from date_unsubscribed) from test_data
where extract(year from date_unsubscribed) = :year
)
select nvl(type, 'Net Subscr') as description,
nvl(sum(jan), 0) as jan, nvl(sum(feb), 0) as feb, nvl(sum(mar), 0) as mar,
nvl(sum(apr), 0) as apr, nvl(sum(may), 0) as may, nvl(sum(jun), 0) as jun
from prep
pivot (
sum(val)
for mth in (1 as jan, 2 as feb, 3 as mar, 4 as apr, 5 as may, 6 as jun)
)
group by rollup(type)
order by case type when 'Subscribed' then 1 when 'Unsubscribed' then 2 else 3 end
;
DESCRIPTION JAN FEB MAR APR MAY JUN
------------ ---------- ---------- ---------- ---------- ---------- ----------
Subscribed 0 0 0 3 2 0
Unsubscribed 0 0 0 0 -1 0
Net Subscr 0 0 0 3 1 0
3 rows selected.

add sum of all values mysql query

select(sum(
if (lower(st.c_gender) = 'male', 1, 0)) - ifnull(le.male, 0)) as male, (sum(
if (lower(st.c_gender) = 'female', 1, 0)) - ifnull(le.female, 0)) as female, FROM_UNIXTIME(st.c_admissionDate, '%Y') as year, le.male, le.female
from(SELECT s.*, zone.id as zoneid FROM groups s left join groups as zone ON s.parent_id = zone.id where s.id != s.parent_id) g, group_to_user u, student st
left join(SELECT ifnull(sum(
if (lower(l.c_gender) = 'male', 1, 0)), 0) as male, ifnull(sum(
if (lower(l.c_gender) = 'female', 1, 0)), 0) as female, FROM_UNIXTIME(l.c_leavingDate, '%Y') as year from student l
where l.c_leavingDate is not null group by FROM_UNIXTIME(l.c_leavingDate, '%Y')) as le on le.year = FROM_UNIXTIME(st.c_admissionDate, '%Y')
where g.id = u.groupid and st.c_userId = u.userId group by FROM_UNIXTIME(st.c_admissionDate, '%Y')
the above query returns the following output ,
male female year male female
1860 657 1970 NULL NULL
2 4 2012 NULL NULL
3 0 2013 NULL NULL
470 370 2014 0 1
0 365 2015 NULL NULL
0 367 2016 NULL NULL
1 260 2017 1 2
from the above output need to sum the before values like the following output
male female year male female
1860 657 1970 NULL NULL
1862 661 2012 NULL NULL
1865 661 2013 NULL NULL
2335 1031 2014 0 1
2335 1396 2015 NULL NULL
2335 1763 2016 NULL NULL
2336 2023 2017 1 2
anyway to make the output like the above help me
set #msum := 0;
set #fsum := 0;
select (#msum := #msum + T.male_sum) as male_sum, (#fsum := #fsum + T.female_sum) as female_sum, T.year, T.male, T.female
from (
select(sum(
if (lower(st.c_gender) = 'male', 1, 0)) - ifnull(le.male, 0)) as male_sum, (sum(
if (lower(st.c_gender) = 'female', 1, 0)) - ifnull(le.female, 0)) as female_sum, FROM_UNIXTIME(st.c_admissionDate, '%Y') as year, le.male, le.female
from(SELECT s.*, zone.id as zoneid FROM groups s left join groups as zone ON s.parent_id = zone.id where s.id != s.parent_id) g, group_to_user u, student st
left join(SELECT ifnull(sum(
if (lower(l.c_gender) = 'male', 1, 0)), 0) as male, ifnull(sum(
if (lower(l.c_gender) = 'female', 1, 0)), 0) as female, FROM_UNIXTIME(l.c_leavingDate, '%Y') as year from student l
where l.c_leavingDate is not null group by FROM_UNIXTIME(l.c_leavingDate, '%Y')) as le on le.year = FROM_UNIXTIME(st.c_admissionDate, '%Y')
where g.id = u.groupid and st.c_userId = u.userId group by FROM_UNIXTIME(st.c_admissionDate, '%Y')
)T;
Simplify this query but this will give you a general idea. Cannot be done in a non procedural query.
You really need a column for a reliable ordering.
But a method in MySQL is to use # variables like this.
SELECT
#male = #male + male as male
, #female = #female + female as female
, ordering_column
FROM (
<< your existing query here >>
)
CROSS JOIN ( SELECT #male := 0 m, #female := 0 f ) vars
ORDER BY
ordering_column