AVG and Count from one columns - sql

I Have a table by this Columns :
[Student_ID],[Class_ID],[Techer_ID],[Course_ID],[Marks]
and for range of marks exist name for example:
between 0 to 5 = D
between 6 to 10 = C
between 11 to 15 = B
between 16 to 20 = A
Now i need create T-Sq l Query for Return this result message columns:
Teacher_ID|Course_ID|Count(Marks)|Count(A)| Count(B)|Count(C)|Count(D)
Very thanks for your help

select Teacher_ID
, Course_ID
, count(*)
, sum(case when Marks between 16 and 20 then 1 end) as SumA
, sum(case when Marks between 11 and 15 then 1 end) as SumB
, sum(case when Marks between 6 and 10 then 1 end) as SumC
, sum(case when Marks between 0 and 5 then 1 end) as SumD
from YourTable
group by
Teacher_ID
, Course_ID

I would use the same approach as Andomar, only change sum to count like this:
select Teacher_ID
, Course_ID
, count(*)
, count(case when Marks between 16 and 20 then 1 end) as countA
, count(case when Marks between 11 and 15 then 1 end) as countB
, count(case when Marks between 6 and 10 then 1 end) as countC
, count(case when Marks between 0 and 5 then 1 end) as countD
from YourTable
group by
Teacher_ID
, Course_ID
In my opinion, the query looks more natural this way.

You can do this with PIVOT. (Note also that this formulation of the Marks-to-Letter calculation is a bit safer than one where both ends of each range must be typed.)
with T as (
select
Teacher_ID,
Course_ID,
case
when Marks <= 5 then 'countD'
when Marks <= 10 then 'countC'
when Marks <= 15 then 'countB'
else 'countA' end as Letter
from T
)
select
Teacher_ID,
Course_ID,
countD+countC+countB+countA as countMarks,
countA,
countB,
countC,
countD
from T pivot (
count(Letter) for Letter in ([countA],[countB],[countC],[countD])
) as P

Related

How to aggregate functions with groupby in SQL

I have tables like this.
I would like to groupbyand aggregate this in diffrent aggregate functions.
product sex age
A M 10
B F 20
A F 30
C M 40
my desired result is like below.
Now I can group in productkey, but in this case, I must group them byproductandsex.
Are there any way to achieve it?
count(M) count(F) avarage(M) average(F)
A 1 1 10 30
B 0 1 NA 20
C 1 0 40 NA
Thanks
With conditional aggregation:
select product,
sum(case when sex = 'M' then 1 else 0 end),
sum(case when sex = 'F' then 1 else 0 end),
avg(case when sex = 'M' then age end),
avg(case when sex = 'F' then age end)
from tablename
group by product
You can simply use the PIVOT as follows:
select * from your_Table
pivot
(count(1) as cnt, avg(age) as average for sex in ('M','F'))

Trouble ordering GROUP BY, ORDER BY AND JOIN

i'm having trouble ordering a query.
I have this table (AttendanceLog);
ClassID | StudentPin | Status
69 1 YES
8 2 NO
10 2 NO
17 3 NO
43 5 YES
58 6 YES
and this table (Students):
STUDENTPIN | FNAME | LNAME | INTERNATIONAL
1 X X NO
2 X X YES
3 X X NO
4 X X YES
I want to find out the which INTERNATIONAL students (Fname, Lname and StudentPIN) have missed 10 or more classes (attendancelog status being no).
Currently I have this (below) which tells me the studentPIN and the number of classes attended and no attended by each student, however I am unable to join the two tables together.
SELECT
ATTENDANCELOG.studentpin,
SUM(CASE WHEN status = 'YES' THEN 1 ELSE 0 END) AS number_of_yes,
SUM(CASE WHEN status = 'NO' THEN 1 ELSE 0 END) AS number_of_no
FROM attendancelog
GROUP BY ATTENDANCELOG.studentpin
ORDER BY ATTENDANCELOG.studentpin
Thanks!
you could use a join
SELECT
ATTENDANCELOG.studentpin,
Students.FNAME,
Students.LNAME,
SUM(CASE WHEN status = 'YES' THEN 1 ELSE 0 END) AS number_of_yes,
SUM(CASE WHEN status = 'NO' THEN 1 ELSE 0 END) AS number_of_no
FROM attendancelog
INNER JOIN Students ON Students.STUDENTPIN = attendancelog.StudentPin
and INTERNATIONAL='YES'
GROUP BY ATTENDANCELOG.studentpin, Students.FNAME, Students.LNAME
ORDER BY ATTENDANCELOG.studentpin
Join on student pin, put your international = 'YES' filter in the where clause, and filter for more than 10 misses in a having clause. You can also shorten the case expressions a little:
select a.studentpin
, s.fname, s.lname, s.international
, count(case a.status when 'YES' then 1 end) as attended
, count(case a.status when 'NO' then 1 end) as missed
from attendancelog a
join students s on s.studentpin = a.studentpin
where international = 'YES'
group by s.fname, s.lname, s.international, a.studentpin
having count(case a.status when 'NO' then 1 end) > 10
order by s.fname, s.lname, a.studentpin;

SQL Query to fetch employee Attendence

I need to write query on employee table to fetch the employee with employee ID & how many days he is present absent & half-day for given date range.
Employee
AID EmpID Status Date
1 10 Present 17-03-2015
2 10 Absent 18-03-2015
3 10 HalfDay 19-03-2015
4 10 Present 20-03-2015
5 11 Present 21-03-2015
6 11 Absent 22-03-2015
7 11 HalfDay 23-03-2015
Expected Output will be :
EmpID Present Absent HalfDay
10 2 1 1
11 1 1 1
Can you please help me with the Sql query ?
Here Is the query I tried
SELECT EMP.EMPID,
(CASE WHEN EMP.STATUS = 'Present' THEN COUNT(STATUS) ELSE 0 END) Pres,
(CASE WHEN EMP.STATUS = 'Absent' THEN COUNT(STATUS) ELSE 0 END) ABSENT,
(CASE WHEN emp.status = 'HalfDay' THEN Count(status) ELSE 0 END) HalfDay
FROM EMPLOYEE EMP GROUP BY emp.empid
The COUNT() function tests if the value is NOT NULL. Therefore it will always increment for both sides of a CASE statement like this:
COUNT(CASE Status WHEN 'Present' THEN 1 ELSE 0) AS Present
So we need to use SUM() ...
select empid,
sum(case when status='Present' then 1 else 0 end) present_tot,
sum(case when status='Absent' then 1 else 0 end) absent_tot,
sum(case when status='HalfDay' then 1 else 0 end) halfday_tot
from employee
group by empid
order by empid
/
... or use COUNT() with a NULL else clause. Both produce the same output, perhaps this one is clearer:
SQL> select empid,
2 count(case when status='Present' then 1 end) present_tot,
3 count(case when status='Absent' then 1 end) absent_tot,
4 count(case when status='HalfDay' then 1 end) halfday_tot
5 from employee
6 group by empid
7 order by empid
8 /
EMPID PRESENT_TOT ABSENT_TOT HALFDAY_TOT
---------- ----------- ---------- -----------
10 2 1 1
11 1 1 1
SQL>
Note that we need to use ORDER BY to guarantee the order of the result set. Oracle introduced a hashing optimization for aggregations in 10g which meant GROUP BY rarely returns a predictable sort order.
Replace 0 with null because it would be also come in count and added the where clause for date range, check the example below:
select empID,
count(case when status='Present' then 1 else null end) Present_Days,
count(case when status='Absent' then 1 else null end) Absent_Days,
count(case when status='HalfDay' then 1 else null end) HalfDays
from Employee
where date >= to_date('17mar2015') and date <= to_date('23mar2015')
group by empID

Use of AVG function to determine percentages in a SQL query

I want to know what percentage of records have a given value, where percentage is defined as the number of records that match the value divided by the total number of records. i.e. if there are 100 records, of which 10 have a null value for student_id and 20 have a value of 999999, then the percentage_999999 should be 20%. Can I use the AVG function to determine this?
Option 1:
SELECT year, college_name,
sum(case when student_id IN ('999999999') then 1 else 0 end) as count_id_999999999,
count_id_999999999/total_id as percent_id_999999999,
sum(case when student_id IS NULL then 1 else 0 end) as count_id_NULL,
count_id_NULL/total_id as percent_id_NULL
count(*) as total_id
FROM enrolment_data ed
GROUP BY year, college_name
ORDER BY year, college_name;
Option 2:
SELECT year, college_name,
sum(case when student_id IN ('999999999') then 1 else 0 end) as count_id_999999999,
avg(case when student_id IN ('999999999') then 1.0 else 0 end) as percent_id_999999999,
sum(case when student_id IS NULL then 1 else 0 end) as count_id_NULL,
avg(case when student_id IS NULL then 1.0 else 0 end) as percent_id_NULL
count(*) as total_id
FROM enrolment_data ed
GROUP BY year, college_name
ORDER BY year, college_name;
I created a similar table with 100 records, 20 999999999s, 10 nulls, and 70 1s. This worked for me on SQL Server:
select count(*), StudentID
from ScratchTbl
group by StudentID;
(No column name) StudentID
10 NULL
70 1
20 999999999
select avg(case when StudentID = '999999999' then 1.0 else 0.0 end) as 'pct_9s',
sum(case when StudentID = '999999999' then 1 else 0 end) as 'count_9s',
avg(case when StudentID is null then 1.0 else 0.0 end) as 'pct_null',
sum(case when StudentID is null then 1 else 0 end) as 'count_null'
from ScratchTbl
pct_9s count_9s pct_null count_null
0.200000 20 0.100000 10
I have a feeling that your use of the group by clause could be creating problems for you, perhaps select a specific year/college using the where clause (and get rid of the group by line) and see if you get the results you expect.

How to get average of the 'middle' values in a group?

I have a table that has values and group ids (simplified example). I need to get the average for each group of the middle 3 values. So, if there are 1, 2, or 3 values it's just the average. But if there are 4 values, it would exclude the highest, 5 values the highest and lowest, etc. I was thinking some sort of window function, but I'm not sure if it's possible.
http://www.sqlfiddle.com/#!11/af5e0/1
For this data:
TEST_ID TEST_VALUE GROUP_ID
1 5 1
2 10 1
3 15 1
4 25 2
5 35 2
6 5 2
7 15 2
8 25 3
9 45 3
10 55 3
11 15 3
12 5 3
13 25 3
14 45 4
I'd like
GROUP_ID AVG
1 10
2 15
3 21.6
4 45
Another option using analytic functions;
SELECT group_id,
avg( test_value )
FROM (
select t.*,
row_number() over (partition by group_id order by test_value ) rn,
count(*) over (partition by group_id ) cnt
from test t
) alias
where
cnt <= 3
or
rn between floor( cnt / 2 )-1 and ceil( cnt/ 2 ) +1
group by group_id
;
Demo --> http://www.sqlfiddle.com/#!11/af5e0/59
I'm not familiar with the Postgres syntax on windowed functions, but I was able to solve your problem in SQL Server with this SQL Fiddle. Maybe you'll be able to easily migrate this into Postgres-compatible code. Hope it helps!
A quick primer on how I worked it.
Order the test scores for each group
Get a count of items in each group
Use that as a subquery and select only the middle 3 items (that's the where clause in the outer query)
Get the average for each group
--
select
group_id,
avg(test_value)
from (
select
t.group_id,
convert(decimal,t.test_value) as test_value,
row_number() over (
partition by t.group_id
order by t.test_value
) as ord,
g.gc
from
test t
inner join (
select group_id, count(*) as gc
from test
group by group_id
) g
on t.group_id = g.group_id
) a
where
ord >= case when gc <= 3 then 1 when gc % 2 = 1 then gc / 2 else (gc - 1) / 2 end
and ord <= case when gc <= 3 then 3 when gc % 2 = 1 then (gc / 2) + 2 else ((gc - 1) / 2) + 2 end
group by
group_id
with cte as (
select
*,
row_number() over(partition by group_id order by test_value) as rn,
count(*) over(partition by group_id) as cnt
from test
)
select
group_id, avg(test_value)
from cte
where
cnt <= 3 or
(rn >= cnt / 2 - 1 and rn <= cnt / 2 + 1)
group by group_id
order by group_id
sql fiddle demo
in the cte, we need to get count of elements over each group_id by window function + calculate row_number inside each group_id. Then, if this count > 3 then we need to get middle of the group by dividing count by 2 and then get +1 and -1 element. If count <= 3, then we should just take all elements.
This works:
SELECT A.group_id, avg(A.test_value) AS avg_mid3 FROM
(SELECT group_id,
test_value,
row_number() OVER (PARTITION BY group_id ORDER BY test_value) AS position
FROM test) A
JOIN
(SELECT group_id,
CASE
WHEN count(*) < 4 THEN 1
WHEN count(*) % 2 = 0 THEN (count(*)/2 - 1)
ELSE (count(*) / 2)
END AS position_start,
CASE
WHEN count(*) < 4 THEN count(*)
WHEN count(*) % 2 = 0 THEN (count(*)/2 + 1)
ELSE (count(*) / 2 + 2)
END AS position_end
FROM test GROUP BY group_id) B
ON A.group_id=B.group_id
AND A.position >= B.position_start
AND A.position <= B.position_end
GROUP BY A.group_id
Fiddle link: http://www.sqlfiddle.com/#!11/af5e0/56
If you need to calculate the average values ​​for groups then you can do this:
SELECT CASE WHEN NUMBER_FIRST_GROUP <> 0
THEN SUM_FIRST_GROUP / NUMBER_FIRST_GROUP
ELSE NULL
END AS AVG_FIRST_GROUP,
CASE WHEN NUMBER_SECOND_GROUP <> 0
THEN SUM_SECOND_GROUP / NUMBER_SECOND_GROUP
ELSE NULL
END AS AVG_SECOND_GROUP,
CASE WHEN NUMBER_THIRD_GROUP <> 0
THEN SUM_THIRD_GROUP / NUMBER_THIRD_GROUP
ELSE NULL
END AS AVG_THIRD_GROUP,
CASE WHEN NUMBER_FOURTH_GROUP <> 0
THEN SUM_FOURTH_GROUP / NUMBER_FOURTH_GROUP
ELSE NULL
END AS AVG_FOURTH_GROUP
FROM (
SELECT
SUM(CASE WHEN GROUP_ID = 1 THEN 1 ELSE 0 END) AS NUMBER_FIRST_GROUP,
SUM(CASE WHEN GROUP_ID = 1 THEN TEST_VALUE ELSE 0 END) AS SUM_FIRST_GROUP,
SUM(CASE WHEN GROUP_ID = 2 THEN 1 ELSE 0 END) AS NUMBER_SECOND_GROUP,
SUM(CASE WHEN GROUP_ID = 2 THEN TEST_VALUE ELSE 0 END) AS SUM_SECOND_GROUP,
SUM(CASE WHEN GROUP_ID = 3 THEN 1 ELSE 0 END) AS NUMBER_THIRD_GROUP,
SUM(CASE WHEN GROUP_ID = 3 THEN TEST_VALUE ELSE 0 END) AS SUM_THIRD_GROUP,
SUM(CASE WHEN GROUP_ID = 4 THEN 1 ELSE 0 END) AS NUMBER_FOURTH_GROUP,
SUM(CASE WHEN GROUP_ID = 4 THEN TEST_VALUE ELSE 0 END) AS SUM_FOURTH_GROUP
FROM TEST
) AS FOO