How can I use multiple HAVING or similar thing? - sql

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 ;

Related

SQL Counting Using Group By With Multiple Tables and Joins

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;

I need to pull unique patients that meet certain criteria

I pulled person_nbrs that have never had an EventType1 before or after an EventType2. I need to pull person_nbrs that have not had an EventType1 prior to having an EventType2. If they had an EventType1 after an EventType2, than it is to be ignored. Here is my query that pulls person_nbrs that have never had an EventType1 before or after EventType2.
SELECT
person_nbr, enc_nbr, enc_timestamp
FROM
person p
JOIN
patient_encounter pe ON p.person_id = pe.person_id
JOIN
patient_procedure pp ON pe.enc_id = pp.enc_id
WHERE
enc_timestamp >= '20170101'
--EventType2
AND code_id LIKE '2'
-- EventType1
AND person_nbr NOT IN (SELECT person_nbr
FROM person p
JOIN patient_encounter pe ON p.person_id = pe.person_id
JOIN patient_procedure pp ON pe.enc_id = pp.enc_id
WHERE code_id LIKE '1')
GROUP BY
person_nbr, enc_nbr, enc_timestamp
ORDER BY
person_nbr ;
You can do this with aggregation and a HAVING clause:
SELECT p.person_nbr
FROM person p JOIN
patient_encounter pe
ON p.person_id = pe.person_id JOIN
patient_procedure pp
ON pe.enc_id = pp.enc_id
GROUP BY p.person_nbr
HAVING SUM(CASE WHEN pp.code_id = 2 THEN 1 ELSE 0 END) > 0 AND -- has code 2
(MAX(CASE WHEN pp.code_id = 1 THEN pe.timestamp END) IS NULL OR
MAX(CASE WHEN pp.code_id = 1 THEN pe.timestamp END) < MIN(CASE WHEN pp.code_id = 2 THEN pe.timestamp END)
) ;
The HAVING clause has two parts:
The first specifies that the person has a code = 2.
The second specifies one of two conditions. The first is that there is no code = 1. The second alternative is that the latest c = 1 timestamp is less than the earliest code = 2 timestamp.

How to write queries where there are more than 2 conditions to extract info in postgresql?

I have tables as below
guides users offers reservations manager_crm_issues
id id id offer_id issuable_id
user_id username guide_id issuable_type issuable_type
What I Would like to extract is
guide.id,
guide.username,
manager_crm_issues.count(issuuable_id)
The issuable_type's distinct values are {Reservation, Offer, Guide}, and it corresponds to issuable_id.
i.e. if issuable_type = 'Reservation' then the issuable_id = reservation.id
Question is, I Would like to count all the issues happened on Guide, and Guide is linked to Offer, Offer is linked to Reservation.
SELECT
a.guideId,
a.guideName,
count(case when cr.issuable_type = 'Reservation' and cr.issuable_id = a.rID THEN cr.id else 0 END),
count(case when co.issuable_type = 'Offer' AND co.issuable_id = a.offerId THEN co.id else 0 END),
count(case when cg.issuable_type = 'Guide' AND cg.issuable_id = a.guideId THEN cg.id else 0 END)
FROM
(SELECT
g.id AS guideId,
u.username AS guideName,
o.id as offerId,
r.id as rId
FROM guides g
INNER JOIN users u on u.id = g.user_id
INNER JOIN offers AS o on o.guide_id = g.id
INNER JOIN reservations AS r on r.offer_id = o.id) a
INNER JOIN manager_crm_issues cg ON cg.id = a.guideId
INNER JOIN manager_crm_issues co ON co.id = a.offerId
INNER JOIN manager_crm_issues cr ON cr.id = a.rId
group by 1,2
I tried to join tables like above, but the outcome seems inaccurate.
Would really appreciate your help.
Don't know if this is related to your issue because you do not say what the issue is but this does not do what you think it does:
count(case
when cr.issuable_type = 'Reservation' and cr.issuable_id = a.rID THEN cr.id
else 0
END),
count counts not nulls so your query will count everything. What you want is
count(case
when cr.issuable_type = 'Reservation' and cr.issuable_id = a.rID THEN cr.id
else null
END),

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)