SQL Counting Using Group By With Multiple Tables and Joins - sql

I have 3 tables (Schools, Students, Activities) such as simply like these:
School
Student
Activity
I want to get the list for each school the ALL activities and number of participants according to the gender in the 2020 ONLY like this table:
I did a query like this but it didn't worked as I wanted:
SELECT Sc.SchoolName, A.ActivityName, Sc.Year COUNT(Gender)
FROM School Sc
JOIN Student S ON Sc.SchoolID=S.SchoolID
JOIN Activity A ON S.ActivityID=A.ActivityID
GROUP BY Gender
How can I fix this? Can you give me solution?

You want conditional aggregation:
SELECT
Sc.SchoolName,
A.ActivityName,
Sc.Year,
SUM(CASE WHEN S.Gender = 'F' THEN 1 ELSE 0 ENd) F,
SUM(CASE WHEN S.Gender = 'M' THEN 1 ELSE 0 ENd) M
FROM School Sc
JOIN Student S ON Sc.SchoolID = S.SchoolID
JOIN Activity A ON S.ActivityID = A.ActivityID
GROUP BY
Sc.SchoolName,
A.ActivityName,
Sc.Year

SELECT
Sc.SchoolName,
A.ActivityName,
Sc.Year,
COUNT(CASE WHEN S.Gender = 'F' THEN StudentID END) as F,
COUNT(CASE WHEN S.Gender = 'M' THEN StudentID END) as M
FROM School Sc
INNER JOIN Student S ON Sc.SchoolID = S.SchoolID
INNER JOIN Activity A ON S.ActivityID = A.ActivityID
WHERE YEAR LIKE '2020-%'
GROUP BY
Sc.SchoolName,
A.ActivityName,
Sc.Year

Please use below query,
SELECT Sc.SchoolName, Sc.Year, A.ActivityName,
case when Sc.Gender = 'M' then count(1) end as M,
case when Sc.Gender = 'F' then count(1) end as F
FROM School Sc
JOIN Student S ON Sc.SchoolID=S.SchoolID
JOIN Activity A ON S.ActivityID=A.ActivityID
GROUP BY Sc.SchoolName, Sc.Year, A.ActivityName;

Related

How can I use multiple HAVING or similar thing?

I have some troubles where I want to show only teachers who is teaching 3 or more internal or external courses (not together). I guess my code now would be happy with 2 internal and 1 external etc. So how can I make sure it will actually count them individually and not together?
SELECT t.pnr, t.tname
FROM teacher t
JOIN teaches s ON t.pnr = s.pnr
JOIN course c ON s.coursecode = c.coursecode
WHERE c.coursetype = 'intern' OR c.coursetype = 'extern'
GROUP BY t.pnr, t.tname
HAVING COUNT(c.coursetype) > 2
You could use conditional aggregation to obtain counts of internal and external courses that the teacher teaches:
SELECT t.pnr, t.tname,
SUM(CASE WHEN c.coursetype = 'intern' THEN 1 ELSE 0 END) AS intcourses,
SUM(CASE WHEN c.coursetype = 'extern' THEN 1 ELSE 0 END) AS extcourses
FROM teacher t
JOIN teaches s ON t.pnr = s.pnr
JOIN course c ON s.coursecode = c.coursecode
GROUP BY t.pnr, t.tname
HAVING intcourses > 2 or extcourses > 2
You could just count in where clause, instead of grouping:
SELECT t.pnr, t.tname
FROM teacher t
WHERE (select count(*) from teaches s
JOIN course c ON c.coursecode = s.coursecode
and c.coursetype='intern'
where t.pnr = s.pnr) > 2
OR
(select count(*) from teaches s
JOIN course c ON c.coursecode = s.coursecode
and c.coursetype='extern'
where t.pnr = s.pnr) > 2
Your use of the having makes sense. You can do this as:
SELECT t.pnr, t.tname
FROM teacher t JOIN
teaches s
ON t.pnr = s.pnr JOIN
course c
ON s.coursecode = c.coursecode
WHERE c.coursetype IN ('intern', 'extern')
GROUP BY t.pnr, t.tname
HAVING SUM(CASE WHEN c.coursetype = 'intern' THEN 1 ELSE 0 END) >= 3 OR
SUM(CASE WHEN c.coursetype = 'extern' THEN 1 ELSE 0 END) >= 3 ;

How to get multiple counts in one sql query across multiple tables

I have 2 tables
Company & products
I need to get 2 counts. One is the total count of products and the secondly count of products for sale_flg=1
This SQL does not seem to work..Tried several other ways..not able to get the expected results
SELECT A.COMPANY_NAME, COUNT(B.PRODUCT_ID) AS TOTAL_COUNT_OF_PRODUCTS,
(CASE WHEN B.SALEFLG =1 THEN 1 END) AS COUNT_OF_SALES
FROM COMPANY A LEFT JOIN
PRODUCT B
ON B.COMPANY_ID = A.COMPANY_ID
GROUP BY A.COMPANY_NAME
I think you just need a sum for the case:
SELECT C.COMPANY_NAME, COUNT(P.PRODUCT_ID) AS TOTAL_COUNT_OF_PRODUCTS,
SUM(CASE WHEN P.SALEFLG = 1 THEN 1 ELSE 0 END) AS COUNT_OF_SALES
FROM COMPANY C LEFT JOIN
PRODUCT P
ON P.COMPANY_ID = C.COMPANY_ID
GROUP BY C.COMPANY_NAME ;
If you have B.SALEFLG = 1 or 0 for you may try
Sum(B.SALEFLG) AS COUNT_OF_SALES
Or use UNION
If you use count then in else you should consider null because null is not consider in count aggregation and if you have B.SALEFLG =1 or 0 then use sum aggregation.
You can try below code:
SELECT A.COMPANY_NAME, COUNT(B.PRODUCT_ID) AS TOTAL_COUNT_OF_PRODUCTS,
count(CASE WHEN B.SALEFLG =1 THEN 1 else null END) AS COUNT_OF_SALES
FROM COMPANY A LEFT JOIN
PRODUCT B
ON B.COMPANY_ID = A.COMPANY_ID
GROUP BY A.COMPANY_NAME
OR try this:
SELECT A.COMPANY_NAME, COUNT(B.PRODUCT_ID) AS TOTAL_COUNT_OF_PRODUCTS,
sum(B.SALEFLG) AS COUNT_OF_SALES
FROM COMPANY A LEFT JOIN
PRODUCT B
ON B.COMPANY_ID = A.COMPANY_ID
GROUP BY A.COMPANY_NAME
This SQL server query is working.
Company Table and Product Table
Company Table CompanyID is join with Product table and sales_flg add in product table .
sales_flg = 1 record display in CntSalesflg
select Comp.CompID as CompID, COUNT(Pro.ProductID) as CntProdustID,
SUM(CASE WHEN Pro.SalesflagID = 1 THEN 1 ELSE 0 END) as CntSalesflg
from Product as Pro
inner join Company as Comp on Pro.CompID = Comp.CompID
GROUP by Comp.CompID

How to use group by in SQL Server

Query:
SELECT
dbo.tblDivision.DivisionName, dbo.tblDistrict.DistrictName,
case
when Gender = 'Male'
then count(Gender)
end as male,
case
when Gender = 'female'
then count(Gender)
end as female,
UnitEName
FROM
dbo.tblDistrict
INNER JOIN
dbo.tblThana ON dbo.tblDistrict.DistrictNo = dbo.tblThana.DistrictNo
INNER JOIN
dbo.tblDivision ON dbo.tblDistrict.DivisionNo = dbo.tblDivision.DivisionNo
INNER JOIN
dbo.vw_EmpInfo ON dbo.tblThana.ThanaNo = dbo.vw_EmpInfo.PerThanaNo
GROUP BY
Gender, DistrictName, DivisionName, UnitEName, UnitEAddress
ORDER BY
DivisionName, DistrictName, UnitEName
This results like below:
but I want every unit's result in one single row. may be I have problem in my group by.
How should I refactor my query?
If you want both genders in one row, you should not group by gender.
Instead you should add up how many male and how many female there are using sum():
SELECT dbo.tblDivision.DivisionName, dbo.tblDistrict.DistrictName,
SUM(case when Gender='Male' then 1 else 0 end) as male,
SUM(case when Gender='Female' then 1 else 0 end) as female,
UnitEName
FROM dbo.tblDistrict INNER JOIN
dbo.tblThana ON dbo.tblDistrict.DistrictNo = dbo.tblThana.DistrictNo INNER JOIN
dbo.tblDivision ON dbo.tblDistrict.DivisionNo = dbo.tblDivision.DivisionNo INNER JOIN
dbo.vw_EmpInfo ON dbo.tblThana.ThanaNo = dbo.vw_EmpInfo.PerThanaNo
group by DistrictName,DivisionName,UnitEName,UnitEAddress
order by DivisionName,DistrictName,UnitEName
You have to leave Gender column out of the grouping clause:
SELECT dbo.tblDivision.DivisionName, dbo.tblDistrict.DistrictName,
COUNT(CASE Gender WHEN 'Male' THEN 1 END) AS male,
COUNT(CASE Gender WHEN 'female' THEN 1 END) AS female,
UnitEName
FROM dbo.tblDistrict
INNER JOIN dbo.tblThana ON dbo.tblDistrict.DistrictNo = dbo.tblThana.DistrictNo
INNER JOIN dbo.tblDivision ON dbo.tblDistrict.DivisionNo = dbo.tblDivision.DivisionNo
INNER JOIN dbo.vw_EmpInfo ON dbo.tblThana.ThanaNo = dbo.vw_EmpInfo.PerThanaNo
GROUP BY DistrictName,DivisionName,UnitEName,UnitEAddress
ORDER BY DivisionName,DistrictName,UnitEName
SELECT dbo.tblDivision.DivisionName
, dbo.tblDistrict.DistrictName
,COUNT(case when Gender='Male' then 1 end) as male
,COUNT(case when Gender='female' then 1 end) as female
,UnitEName
FROM dbo.tblDistrict
INNER JOIN dbo.tblThana ON dbo.tblDistrict.DistrictNo = dbo.tblThana.DistrictNo
INNER JOIN dbo.tblDivision ON dbo.tblDistrict.DivisionNo = dbo.tblDivision.DivisionNo
INNER JOIN dbo.vw_EmpInfo ON dbo.tblThana.ThanaNo = dbo.vw_EmpInfo.PerThanaNo
GROUP BY DivisionName
,DistrictName
,UnitEName
Remove Gender from Group by clause
SELECT dbo.tblDivision.DivisionName, dbo.tblDistrict.DistrictName,
sum (case when Gender='Male' then 1 else 0 end) as male
sum (case when Gender='female' then 1 else 0 end) as female,
UnitEName
FROM dbo.tblDistrict
INNER JOIN dbo.tblThana ON dbo.tblDistrict.DistrictNo = dbo.tblThana.DistrictNo
INNER JOIN dbo.tblDivision ON dbo.tblDistrict.DivisionNo = dbo.tblDivision.DivisionNo
INNER JOIN dbo.vw_EmpInfo ON dbo.tblThana.ThanaNo = dbo.vw_EmpInfo.PerThanaNo
GROUP BY DistrictName,DivisionName,UnitEName,UnitEAddress
ORDER BY DivisionName,DistrictName,UnitEName

Same values being returned for multiple rows in sql but not all columns

i'm currently running the following query (see below.)
However when i do the same values is returned for multiple rows in totalusers, activeusers and suspendedusers.
However when it comes to total login the values are unique.
Is their a reason why this could be happening and is their a way to solve the problem. If it helps im using the tool sql workben with postgre sql driver.
Cheers
SELECT
company.companyStatus,
company.CompanyId,
company.companyName,
select
count(distinct UserID)
From Users
inner join company
on Users.CompanyID = Company.CompanyId
where Users.Companyid = company.Companyid
as TotalUsers,
select sum(case when userstatusid =2 then 1 else 0 end)
from users
inner join company
on users.companyid = company.companyid
where users.companyid = company.companyid)
as ActiveUsers,
select sum(case when userstatusid = 3 then 1 else 0 end)
from users
inner join company
on users.companyid = company.companyid
where users.companyid = company.companyid)
as SuspendedUsers,
(Select COUNT (distinct usersessionid)
From UserSession
inner join users
on usersession.UserID=users.UserID
where usersession.UserID=users.UserID
and users.companyid= company.CompanyID)
as TotalLogin,
from Company
Its because your TotalUsers, ActiveUsers and SuspendedUsers queries are all using their own (unrestricted) copy of the Company table, whereas your TotalLogin is using the main instance from which you're selecting. This means that the TotalLogin numbers you're seeing are for that particular company, but the other fields are across ALL companies.
Presumably you wanted something more like:
SELECT
company.companyStatus,
company.CompanyId,
company.companyName,
count(distinct u.UserID) TotalUsers,
sum(case when u.userstatusid =2 then 1 else 0 end) ActiveUsers,
sum(case when u.userstatusid = 3 then 1 else 0 end) SuspendedUsers,
count(distinct u.usersessionid) TotalLogin
from Company
inner join Users on Users.CompanyID = Company.CompanyId
The reason is because you have company in the subqueries for those calculations.
I much prefer having table references in the from clause where possible, and you can write this query moving everything to the from clause:
SELECT c.companyStatus, c.CompanyId, c.companyName,
uc.Totalusers, uc.Activeusers, uc.Suspendedusers, ucs.TotalLogin
from Company c left outer join
(select u.companyid,
COUNT(distinct userid) as Totalusers,
SUM(case when userstatusid = 2 then 1 else 0 end) as ActiveUsers,
sum(case when userstatusid = 3 then 1 else 0 end) as Suspendedusers
from users u
group by u.companyid
) uc
uc.companyid = c.companyId left outer join
(select u.companyid, COUNT(distinct usersessionid) as TotalLogin
from UserSession us inner join
users u
on us.UserID = u.UserID
) ucs
on ucs.companyid = c.companyid;
This should also speed up the query because it doesn't have to do the same work multiple times.

SQL SELECT string Greater than using count()

I am trying to list groups that have more graduate than undergraduate student members. I feel I have the concept behind my idea, but making the query is a little more difficult then a simple translation. Below is my code, I currently am getting a missing right parenthesis error where COUNT(student.career = 'GRD'). Thanks.
SELECT studentgroup.name
COUNT(student.career = 'GRD') - COUNT(student.career = 'UGRD')
AS Gradnum FROM studentgroup
INNER JOIN memberof ON studentgroup.GID = memberof.GroupID
INNER JOIN student ON memberof.StudentID = student.SID
WHERE Gradnum > 1;
SELECT studentgroup.GID, max(studentgroup.name)
FROM studentgroup
INNER JOIN memberof ON studentgroup.GID = memberof.GroupID
INNER JOIN student ON memberof.StudentID = student.SID
GROUP BY studentgroup.GID
HAVING SUM(CASE WHEN student.career = 'GRD' THEN 1
WHEN student.career = 'UGRD'THEN -1
ELSE 0
END) >0
SELECT studentgroup.name
SUM(CASE WHEN student.career = 'GRD' THEN 1 ELSE 0 END) - SUM(CASE WHEN student.career = 'UGRD' THEN 1 ELSE 0 END)
AS Gradnum FROM studentgroup
INNER JOIN memberof ON studentgroup.GID = memberof.GroupID
INNER JOIN student ON memberof.StudentID = student.SID
WHERE Gradnum > 1
GROUP BY studentgroup.name;
I have used WITH As clause which is supported by most of DBMS like SQL Server, PostGresSQL except MySQL
With grpTbl As
(
SELECT studentgroup.name As StudentGroupName,
SUM( CASE WHEN student.career = 'GRD' THEN 1 ELSE 0 END ) AS 'TotalGraduate',
SUM( CASE WHEN student.career = 'UGRD' THEN 1 ELSE 0 END ) AS 'TotalUnderGraduate'
FROM studentgroup
INNER JOIN memberof ON studentgroup.GID = memberof.GroupID
INNER JOIN student ON memberof.StudentID = student.SID
)
SELECT StudentGroupName
FROM grpTbl
WHERE TotalGraduate > TotalUnderGraduate
For MySQL you can use Temporary table to store resultset from First query and filter out the GroupNames which have more Graduate than UnderGraduate in WHERE clause .
This method will work for other DBMS also difference being syntax of creating temporary table.
CREATE TEMPORARY TABLE grpTbl (
StudentGroupName varchar(255),
TotalGraduate INT,
TotalUnderGraduate INT
);
INSERT INTO grpTbl
SELECT studentgroup.name As StudentGroupName,
SUM( CASE WHEN student.career = 'GRD' THEN 1 ELSE 0 END ) ,
SUM( CASE WHEN student.career = 'UGRD' THEN 1 ELSE 0 END )
FROM studentgroup
INNER JOIN memberof ON studentgroup.GID = memberof.GroupID
INNER JOIN student ON memberof.StudentID = student.SID
SELECT StudentGroupName
FROM grpTbl
WHERE TotalGraduate > TotalUnderGraduate
DROP TABLE grpTbl
One more option
SELECT studentgroup.name
FROM studentgroup INNER JOIN memberof ON studentgroup.GID = memberof.GroupID
INNER JOIN student ON memberof.StudentID = student.SID
GROUP BY studentgroup.name
HAVING COUNT(CASE WHEN student.career = 'GRD' THEN student.career END)
> COUNT(CASE WHEN student.career = 'UGRD' THEN student.career END)