How to combine group by, where and logical operators? - sql

I have the following data structure
| Name | Contract Type |
|:------|--------------:|
| Frank | Student |
| Frank | Staff |
| Frank | Staff |
| John | Internship |
| John | Student |
| John | Staff |
| Tim | Internship |
| Tim | Staff |
I want to get the Names of employees who have had a student contract and another contract in theire history. My approach was
Select Name, count(Name) from table
where ContractType = ('Student') AND (ContractType = ('Staff') OR ContractType = ('Internship') )
group by Name having count (Name) > 1
The problem here is that I still get all employees. Anyone who can help me get this accomplished?

Group by the Name and take only those groups having at least once the Student contract and in total more than 1 contract
Select Name
from your_table
group by Name
having sum(case when ContractType = 'Student' then 1 else 0 end) > 0
and count(distinct ContractType) > 1

Using GROUP BY:
SELECT Name
FROM dbo.YourTable
GROUP BY Name
HAVING SUM(CASE WHEN [Contract Type] = 'Student' THEN 1 ELSE 0 END) >= 1
AND SUM(CASE WHEN [Contract Type] <> 'Student' THEN 1 ELSE 0 END) >= 1;
But you can also use EXISTS:
SELECT DISTINCT A.Name
FROM dbo.YourTable A
WHERE EXISTS(SELECT 1 FROM dbo.YourTable
WHERE Name = A.Name
AND [Contract Type] = 'Student')
AND EXISTS( SELECT 1 FROM dbo.YourTable
WHERE Name = A.Name
AND [Contract Type] <> 'Student');

You can also use a GROUP BY and then IN:
SELECT name
FROM table
GROUP BY name
HAVING COUNT(*) > 1
AND name IN
(SELECT name
FROM table
WHERE contracttype = 'student')
Tested here: http://sqlfiddle.com/#!9/38fa36/1

Related

display the names who passed in subject 'M' but fail in subject 'P'

create table Student
(
Name varchar(20),
Subject varchar(20),
Mark int
)
insert into Student values('A','M',30),
('A','P',45),
('B','M',47),
('B','P',70)
SELECT * FROM Student
above is the code for the table. Consider the pass mark=35
need to display the name who pass in subject 'P' but fail in subject 'M'
SELECT Name
FROM Student
group by Name
having
min(case when Subject = 'M' then Mark end) < 35
and
min(case when Subject = 'P' then Mark end) >= 35
According to what you say, you need the student who pass in subject 'P' and in the same time fail in subject 'M'
SELECT Name
FROM Student
WHERE Subject = 'P' AND Mark >= 35
AND Name IN (SELECT Name FROM Student WHERE Subject = 'M' AND Mark < 35);
Results:
+----+------+
| | Name |
+----+------+
| 1 | A |
+----+------+
Or even
SELECT *
FROM Student S1
WHERE EXISTS
(
SELECT 1
FROM Student S2
WHERE S1.Name = S2.Name AND
((S1.Mark > 35 AND S2.Mark < 35) OR (S2.Mark > 35 AND S1.Mark < 35))
)
Results:
+----+------+---------+------+
| | Name | Subject | Mark |
+----+------+---------+------+
| 1 | A | M | 30 |
| 2 | A | P | 45 |
+----+------+---------+------+
Demo
You can use Exists
select * from Student s where exists(select 1 from Student s1
where s1.Name=s.Name
group by s1.Name
having sum(case when s1.Subject='P' and s1.Mark>=35 then 1 else 0 end)>0
and sum(case when s1.Subject='M' and s1.Mark<35 then 1 else 0 end)>0
)
Name Subject Mark
A M 30
A P 45
Demo
Please check this query
SELECT Name, M, P
FROM
(SELECT Name, Subject, Mark
FROM Student) AS SourceTable
PIVOT
(
AVG(Mark)
FOR Subject IN (M, P)
) AS PivotTable
WHERE M < 35 and P >= 35

SQL statement select columns with specific value

I need some help making an sql statement; I don't really know how to aproach the situation. I have two tables, Departments and Employees
from which I want to select the Dpt_num and the Dpt_name of the departments that have at least one employee and that all their employees are from Barcelona
Case 1
== Departments =======
| Dpt_num | Dpt_name |
| 1 | A |
| 2 | B |
== Employees ===================
| E_num | Dpt_num | City |
| 1 | 1 | Barcelona |
| 2 | 1 | Barcelona |
The result in this case should be
Dpt_num Dpt_name
------------------
1 A
Case 2
== Departments =======
| Dpt_num | Dpt_name |
| 1 | A |
| 2 | B |
== Employees ==================
| E_num | Dpt_num | City |
| 1 | 1 | Barcelona |
| 2 | 1 | Madrid |
The result in this case should be empty.
I tried this for example but it seems very inefficient and it does not work in all the cases
select
num_dpt, nom_dpt
from
departements
where
1 = (select count(distinct e.ciutat_empl)
from empleats e
where e.num_dpt = num_dpt)
and not exists (select * from empleats e
where e.ciutat_empl != 'BARCELONA' and e.num_dpt = num_dpt);
I really appreciate any help. Thanks!
You want to go down the path of doing the filtering in the where clause. Then, use exists and not exists:
select d.num_dpt, d.nom_dpt
from departaments d
where exists (select 1
from empleats e
where e.num_dpt = d.num_dpt and e.ciutat_empl = 'BARCELONA'
) and
not exists (select 1
from empleats e
where e.num_dpt = d.num_dpt and e.ciutat_empl <> 'BARCELONA'
);
The first condition checks that at least one employee is from Barcelona. The second checks that no employees are from any other city.
One major problem in your version is your correlation clause:
e.num_dpt = num_dpt
You think this is doing:
e.num_dpt = departaments.num_dpt
But it is really doing:
e.num_dpt = e.num_dpt
Always qualify your column names. This is especially important when you have more than one table reference in the query.
Join the tables, group by the department and check if the count of employees in Barcelona is equal to the count of all employess of the department.
SELECT d.dpt_num,
d.dpt_name
FROM departments d
INNER JOIN employees e
ON e.dpt_num = d.dpt_num
GROUP BY d.dpt_num,
d.dpt_name
HAVING count(CASE
WHEN e.city = 'Barcelona' THEN
1
END) = count(*);
I believe this should work:
select d.dpt_num, d.dpt_name
from departments d
inner join employees e on
d.dpt_num = e.dpt_num
group by d.dpt_num, d.dpt_name
having count(*) = sum(case when e.city = 'Barcelona' then 1 else 0 end)
INNER JOIN makes sure there's at least 1 employee
HAVING count(*) = sum(case when e.city = 'Barcelona' then 1 else 0 end) makes sure that all employees are from Barcelona
demo: db<>fiddle
SELECT dpt_num, dpt_name
FROM (
SELECT d.dpt_num, d.dpt_name, array_agg(city) as cities
FROM dept d
JOIN empl e
ON d.dpt_num = e.dpt_num
GROUP BY d.dpt_num, d.dpt_name
) s
WHERE 'Barcelona' = ALL(cities)
Aggregate the cities and then you can filter with the ALL operator which checks if all array elements fit the condition.
Generally speaking, you compare COUNT(*) with COUNT(some condition) for such problems:
SELECT *
FROM Departments
WHERE EXISTS (
SELECT 1
FROM Employees
WHERE Employees.Dpt_num = Departments.Dpt_num
HAVING COUNT(*) > 0 -- it is possible to get a 0 if where did not match
AND COUNT(*) = COUNT(CASE WHEN Employees.City = 'Barcelona' THEN 1 END)
)
DB Fiddle
Pl try query below
select a.dpt_number,a.dpt_name from yy_department a
where exists (select 'x' from yy_employees y where y.dpt_number = a.dpt_number and y.city = 'Barcelona')
and not exists (select 'x' from yy_employees y where y.dpt_number = a.dpt_number and nvl(y.city,'x') <> nvl('Barcelona','y'))

SQL - Counting one attribute, grouping by another

I have a table staff
staff
pt | ward
P | 1
P | 1
T | 1
P | 2
T | 2
I want to produce a table that counts how many P's and T's there is for each ward like this:
staff
ward | P | T
1 | 2 | 1
2 | 1 | 1
I have tried this
WITH cte(ward, P, T) AS(
SELECT ward,
(SELECT COUNT(PT) FROM staff WHERE PT = 'P' ),
(SELECT COUNT(PT) FROM staff WHERE PT = 'T' ) FROM staff GROUP BY ward)
SELECT * FROM cte
but then I get this table
staff
ward | P | T
1 | 3 | 2
2 | 2 | 2
Any help would be appreciated
Case statements will work here:
SELECT
ward,
SUM(CASE WHEN pt = P THEN 1 ELSE 0 END) AS P,
SUM(CASE WHEN pt = T THEN 1 ELSE 0 END) AS T
FROM
table
GROUP BY
ward
Use conditional aggregation:
select ward, sum( (pt = 'P')::int ) as p, sum ( (pt = 'T')::int ) as t
from t
group by ward;

SQL Finding multiple values difficulty

I'm trying to generate the correct SQL for a project.
Here is a sample dataset:
DateTime | EmpID | Function | Location
--------------------------------------------------
1/23/2015 2:00PM | 123 | 1 | 1
1/23/2015 2:10PM | 123 | 2 | 1
1/23/2015 2:20PM | 123 | 1 | 2
1/23/2015 2:40PM | 123 | 2 | 2
1/24/2015 2:00PM | 321 | 1 | 2
1/24/2015 2:15PM | 321 | 2 | 2
1/24/2015 2:30PM | 321 | 1 | 3
I need to pull a count of all records where functionid = 1 and location MUST EQUAL both 1 and 2. So the first row and the third row would be returned and considered a count of 1.
Hopefully I'm making sense with this. Basically I need to know how many times an employee was at two locations. Any help would be appreciated.
Group by EmpId and count locations.
SELECT *
FROM MyTable T1
WHERE Function = 1 AND
NOT EXISTS (SELECT 1
FROM MyTable T2
WHERE T1.EmpId = T2.EmpId AND
T1.Function = T2.Function AND
T2.Location NOT IN (1, 2))
GROUP BY EmpId
HAVING Count(DISTINCT Location) > 1
Have not tested it but think this will work
SELECT EmpID, COUNT(EmpID) AS NumOfTimes
From [Table Name]
WHERE FunctionID = 1 AND (Location = 1 OR Location = 2)
GROUP BY EmpID
HAVING NumOfTimes = 2
SELECT EmpID , COUNT(*) total, COUNT (CASE WHEN Location = 1 THEN 1 END) was_in_1,COUNT (CASE WHEN Location = 2 THEN 1 END) was_in_2
FROM table
WHERE Function = 1
GROUP BY EmpID
HAVING MAX(CASE WHEN Location = 1 THEN 1 ELSE 0 END) = MAX(CASE WHEN Location = 2 THEN 1 ELSE 0 END)
but if you would like to know if the employee was in any 2 locations then jarlh gave the right comment
group by EmpID
having count(distinct location) >= 2
SELECT A.EmpID, COUNT(*)
FROM YOURTABLENAME A
INNER JOIN YOURTABLENAME B
ON A.EmpID= B.EmpID
WHERE A.Location = 1 and B.Location = 2
and A.Function = B.Function and A.Function = 1
GROUP BY A.EmpID

How to group using exists

I have the following table
personid talent
1 swim
2 play
1 play
1 swim
2 play
3 swim
3 swim
2 play
So person 1 can both swim and play. Person 2 can only play. Person 3 can only swim.
I need to get the following result
personid talent
1 both
2 play
3 swim
How can I do this using exists ?
I tried
SELECT DISTINCT personid,
CASE WHEN (EXISTS(
SELECT * FROM mytable
-- I got stuck
PS : I have a long solution that works . . But i do not like it because it is long
SELECT DISTINCT dis2.personid , CASE WHEN talcount = 2 THEN 'both'
ELSE talent END AS talent
FROM
(
SELECT personid , COUNT(talent) talcount
FROM
(
SELECT DISTINCT personid , talent
FROM my_table
) AS dis
GROUP BY personid
) dis2
JOIN my_table dis3
ON dis2.personid = dis3.personid
Do you really need to use EXISTS?
SELECT
personid,
CASE
WHEN COUNT(DISTINCT talent) = 2 THEN 'both'
ELSE MIN (talent)
END
FROM talents
GROUP BY personid
You could use the WITH clause to achieve the same effect:
WITH
DISTINCT_TALENTS(PERSONID, TALENT) AS
(SELECT DISTINCT PERSONID, TALENT
FROM TALENTS)
SELECT DISTINCT PERSONID, TALENT
FROM
(SELECT A.PERSONID,
CASE WHEN TALENT_COUNT = 2 THEN 'BOTH' ELSE A.TALENT END
FROM
DISTINCT_TALENTS A
INNER JOIN
(SELECT PERSONID, COUNT(TALENT) TALENT_COUNT
FROM DISTINCT_TALENTS
GROUP BY PERSONID) B
ON A.PERSONID = B.PERSONID)
First you create a virtual DISTINCT_TABLES table:
+------------------+
| personid talent |
+------------------+
| 1 play |
| 1 swim |
| 2 play |
| 3 swim |
+------------------+
next you create a subquery b with the following
+------------------------+
| personid talent_count |
+------------------------+
| 1 2 |
| 2 1 |
| 3 1 |
+------------------------+
you join with original DISTINCT_TALENTS to obtain
+----------+--------+--------------+
| personid | talent | talent_count |
+----------+--------+--------------+
| 1 | both | 2 |
| 1 | both | 2 |
| 2 | play | 1 |
| 3 | swim | 1 |
+----------+--------+--------------+
you take the distinct personid, talent to obtain the final result.
A solution similar to using exists is:
SELECT DISTINCT PERSONID, TALENT
FROM
(
SELECT
B.PERSONID,
CASE
WHEN A.TALENT IS NULL THEN 'swim'
WHEN B.TALENT IS NULL THE 'play'
ELSE 'both'
END TALENT
FROM
TALENTS A
FULL OUTER JOIN
TALENTS B
ON A.PERSONID = B.PERSONID
AND A.TALENT='play'
AND B.TALENT='swim'
)
And finally, also with the EXISTS function used like a lookup function:
SELECT DISTINCT PERSONID, TALENT
FROM (
SELECT A.PERSONID,
CASE
WHEN A.TALENT = 'play' AND EXISTS (SELECT 1 FROM TALENTS B WHERE A.PERSONID = B.PERSONID AND B.TALENT = 'swim')
THEN 'both'
WHEN A.TALENT = 'swim' AND EXISTS (SELECT 1 FROM TALENTS B WHERE A.PERSONID = B.PERSONID AND B.TALENT = 'play')
THEN 'both'
ELSE
A.TALENT
END TALENT
FROM
TALENTS A)