Select users belonging only to particular departments - sql

I have the following table with two fields namely a and b as shown below:
create table employe
(
empID varchar(10),
department varchar(10)
);
Inserting some records:
insert into employe values('A101','Z'),('A101','X'),('A101','Y'),('A102','Z'),('A102','X'),
('A103','Z'),('A103','Y'),('A104','X'),('A104','Y'),('A105','Z'),('A106','X');
select * from employe;
empID department
------------------
A101 Z
A101 X
A101 Y
A102 Z
A102 X
A103 Z
A103 Y
A104 X
A104 Y
A105 Z
A106 X
Note: Now I want to show the employee who is only and only belongs to the department Z and Y.
So according to the condition the only employee A103 should be displayed because of he only belongs
to the department Z and Y. But employee A101 should not appear because he belong to Z,X, and Y.
Expected Result:
If condition is : Z and Y then result should be:
empID
------
A103
If condition is : Z and X then result should be:
empID
------
A102
If condition is : Z,X and Y then result should be:
empID
------
A101
Note: I want to do it in the where clause only (don't want to use the group by and having clauses), because I'm going to include this one in the other where also.

This is a Relational Division with no Remainder (RDNR) problem. See this article by Dwain Camps that provides many solution to this kind of problem.
First Solution
SQL Fiddle
SELECT empId
FROM (
SELECT
empID, cc = COUNT(DISTINCT department)
FROM employe
WHERE department IN('Y', 'Z')
GROUP BY empID
)t
WHERE
t.cc = 2
AND t.cc = (
SELECT COUNT(*)
FROM employe
WHERE empID = t.empID
)
Second Solution
SQL Fiddle
SELECT e.empId
FROM employe e
WHERE e.department IN('Y', 'Z')
GROUP BY e.empID
HAVING
COUNT(e.department) = 2
AND COUNT(e.department) = (SELECT COUNT(*) FROM employe WHERE empID = e.empId)
Without using GROUP BY and HAVING:
SELECT DISTINCT e.empID
FROM employe e
WHERE
EXISTS(
SELECT 1 FROM employe WHERE department = 'Z' AND empID = e.empID
)
AND EXISTS(
SELECT 1 FROM employe WHERE department = 'Y' AND empID = e.empID
)
AND NOT EXISTS(
SELECT 1 FROM employe WHERE department NOT IN('Y', 'Z') AND empID = e.empID
)

I know that this question has already been answered, but it was a fun problem to do and I tried to do it in a way that no one else has. Benefits of mine is that you can input any list of strings as long as each value has a comma afterwards and you don't have to worry about checking counts.
Note: Values must be listed in alphabetic order.
XML Solution with CROSS APPLY
select DISTINCT empID
FROM employe A
CROSS APPLY
(
SELECT department + ','
FROM employe B
WHERE A.empID = B.empID
ORDER BY department
FOR XML PATH ('')
) CA(Deps)
WHERE deps = 'Y,Z,'
Results:
empID
----------
A103

For condition 1:z and y
select z.empID from (select empID from employe where department = 'z' ) as z
inner join (select empID from employe where department = 'y' ) as y
on z.empID = y.empID
where z.empID Not in(select empID from employe where department = 'x' )
For condition 1:z and x
select z.empID from (select empID from employe where department = 'z' ) as z
inner join (select empID from employe where department = 'x' ) as x
on z.empID = x.empID
where z.empID Not in(select empID from employe where department = 'y' )
For condition 1:z,y and x
select z.empID from (select empID from employe where department = 'z' ) as z
inner join (select empID from employe where department = 'x' ) as x
on z.empID = x.empID
inner join (select empID from employe where department = 'y' ) as y on
y.empID=Z.empID

You can use GROUP BY with having like this. SQL Fiddle
SELECT empID
FROM employe
GROUP BY empID
HAVING SUM(CASE WHEN department= 'Y' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN department= 'Z' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN department NOT IN('Y','Z') THEN 1 ELSE 0 END) = 0
Without GROUP BY and Having
SELECT empID
FROM employe E1
WHERE (SELECT COUNT(DISTINCT department) FROM employe E2 WHERE E2.empid = E1.empid and department IN ('Z','Y')) = 2
EXCEPT
SELECT empID
FROM employe
WHERE department NOT IN ('Z','Y')
If you want to use any of the above query with other tables using a join you can use CTE or a derived table like this.
;WITH CTE AS
(
SELECT empID
FROM employe
GROUP BY empID
HAVING SUM(CASE WHEN department= 'Y' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN department= 'Z' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN department NOT IN('Y','Z') THEN 1 ELSE 0 END) = 0
)
SELECT cols from CTE join othertable on col_cte = col_othertable

try this
select empID from employe
where empId in (select empId from employe
where department = 'Z' and department = 'Y')
and empId not in (select empId from employe
where department = 'X') ;

for If condition is : Z and Y
SELECT EMPID FROM EMPLOYE WHERE DEPARTMENT='Z' AND
EMPID IN (SELECT EMPID FROM EMPLOYE WHERE DEPARTMENT ='Y')AND
EMPID NOT IN(SELECT EMPID FROM EMPLOYE WHERE DEPARTMENT NOT IN ('Z','Y'))

The following query works when you want employees from departments 'Y' and 'Z' and not 'X'.
select empId from employe
where empId in (select empId from employe
where department = 'Z')
and empId in (select empId from employe
where department = 'Y')
and empId not in (select empId from employe
where department = 'X') ;
For your second case, simply replace not in with in in the last condition.

Try this,
SELECT a.empId
FROM employe a
INNER JOIN
(
SELECT empId
FROM employe
WHERE department IN ('X', 'Y', 'Z')
GROUP BY empId
HAVING COUNT(*) = 3
)b ON a.empId = b.empId
GROUP BY a.empId
Count must based on number of conditions.

You can too use GROUP BY and HAVING — you just need to do it in a subquery.
For example, let's start with a simple query to find all employees in departments X and Y (and not in any other departments):
SELECT empID,
GROUP_CONCAT(DISTINCT department ORDER BY department ASC) AS depts
FROM emp_dept GROUP BY empID
HAVING depts = 'X,Y'
I've used MySQL's GROUP_CONCAT() function as a convenient shortcut here, but you could get the same results without it, too, e.g. like this:
SELECT empID,
COUNT(DISTINCT department) AS all_depts,
COUNT(DISTINCT CASE
WHEN department IN ('X', 'Y') THEN department ELSE NULL
END) AS wanted_depts
FROM emp_dept GROUP BY empID
HAVING all_depts = wanted_depts AND wanted_depts = 2
Now, to combine this with other query condition, simply take a query that includes the other conditions, and join your employees table against the output of the query above:
SELECT empID, name, depts
FROM employees
JOIN (
SELECT empID,
GROUP_CONCAT(DISTINCT department ORDER BY department ASC) AS depts
FROM emp_dept GROUP BY empID
HAVING depts = 'X,Y'
) AS tmp USING (empID)
WHERE -- ...add other conditions here...
Here's an SQLFiddle demonstrating this query.
Ps. The reason why you should use a JOIN instead of an IN subquery for this is because MySQL is not so good at optimizing IN subqueries.
Specifically (as of v5.7, at least), MySQL always converts IN subqueries into dependent subqueries, so that the subquery must be re-executed for every row of the outer query, even if the original subquery was independent. For example, the following query (from the documentation linked above):
SELECT ... FROM t1 WHERE t1.a IN (SELECT b FROM t2);
gets effectively converted into:
SELECT ... FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.b = t1.a);
This may still be reasonably fast, if t2 is small and/or has an index allowing fast lookups. However, if (like in the original example above) executing the subquery might take a lot of work, the performance can suffer badly. Using a JOIN instead allows the subquery to only be executed once, and thus typically offers much better performance.

What about a self join? (ANSI Compliant - worked for 20+ years)
SELECT * FROM employee e JOIN employee e2 ON e.empid = e2.empid
WHERE e.department = 'x' AND e2.department ='y'
This shows that a101 and a104 both work in both departments.

Solution using where clause:
select distinct e.empID
from employe e
where exists( select *
from employe
where empID = e.empID
having count(department) = count(case when department in('Y','X','Z') then department end)
and count(distinct department) = 3)
exists checks if there are records for specific EmpId that have total count of departments equal to conditional count of only matching departments and that it is also equal to the number of departments provided to the in clause. Also worth mentioning that here we apply having clause without the group by clause, on the whole set, but with already specified, only one empID.
SQLFiddle
You can achieve this without the correlated subquery, but with the group by clause:
select e.empId
from employe e
group by e.empID
having count(department) = count(case when department in('Y','X','Z') then department end)
and count(distinct department) = 3
SQLFiddle
You can also use another variation of having clause for the query above:
having count(case when department not in('Y','X', 'Z') then department end) = 0
and count(distinct case when department in('Y','X','Z') then department end) = 3
SQLFiddle

In Postgres this can be simplified using arrays:
select empid
from employee
group by empid
having array_agg(department order by department)::text[] = array['Y','Z'];
It's important to sort the elements in the array_agg() and compare them to a sorted list of departments in the same order. Otherwise this won't return correct answers.
E.g. array_agg(department) = array['Z', 'Y'] might potentially return wrong results.
This can be done in a more flexible manner using a CTE to supply the departments:
with depts_to_check (dept) as (
values ('Z'), ('Y')
)
select empid
from employee
group by empid
having array_agg(department order by department) = array(select dept from depts_to_check order by dept);
That way the sorting of the elements is always done by the database and will be consistent between the values in the aggregated array and the one to which it is compared.
An option with standard SQL is to check if at least one row has a different department together with counting all rows
select empid
from employee
group by empid
having min(case when department in ('Y','Z') then 1 else 0 end) = 1
and count(case when department in ('Y','Z') then 1 end) = 2;
The above solution won't work if it's possible that a single employee is assigned twice to the same department!
The having min (...) can be simplified in Postgres using the aggregate bool_and().
When applying the standard filter() condition to do conditional aggregation this can also be made to work with situation where an employee can be assigned to the same department twice
select empid
from employee
group by empid
having bool_and(department in ('Y','Z'))
and count(distinct department) filter (where department in ('Y','Z')) = 2;
bool_and(department in ('Y','Z')) only returns true if the condition is true for all rows in the group.
Another solution with standard SQL is to use the intersection between those employees that have at least those two departments and those that are assigned to exactly two departments:
-- employees with at least those two departments
select empid
from employee
where department in name in ('Y','Z')
group by empid
having count(distinct department) = 2
intersect
-- employees with exactly two departments
select empid
from employee
group by empid
having count(distinct department) = 2;

Related

SQL count occurrences of an id from another table in multiple rows

TABLE 1 employee:
employee_id, first_name, last_name
2 John Appleseed
TABLE 2 performance_review:
employee_id, reviewer_id
2 1
2 3
2 4
1 2
3 2
QUESTION: print the first_name and last_name in a single row, then how many times that id is found in the employee_id column, then how many times that same id is found in the reviewer_id column.
Example output:
Name Employee_id count Received_review count
-------------------------------------------------------------
John Appleseed 3 2
What I got so far (it doesn't work)
SELECT
CONCAT([employee_first_name], ' ' , [employee_last_name]) AS employee_full_name,
(SELECT COUNT(employee.employee_id)
FROM performance_review AS received_review
LEFT JOIN performance_review ON employee.employee_id = performance_review.employee_id) AS received_reviews
FROM
employee
Since this involves separate aggregation over two different columns you need two subqueries, one for each.
Here is an example [edit] left joins should be used here because the inner joins would fail for example if the performance review table has all rows with null reviewer for a particular employee.
with
emp as (select employee_id,count(*) employee_count
from performance_review
group by employee_id),
rev as (select reviewer_id,count(*) reviewer_count
from performance_review
group by reviewer_id)
select
first_name,
last_name,
employee_count,
reviewer_count
from
employee
left join emp on employee.employee_id=emp.employee_id
left join rev on employee.employee_id=rev.reviewer_id;
The result
first_name
last_name
employee_count
reviewer_count
John
Appleseed
3
2
Robert's answer is the clearest way to do it but I thought I would show another way to do it with a join -- here you use a trick of doing a test and sum to count certain items. I join both cases
SELECT e.first_name, e.last_name,
SUM(CASE WHEN e.employee_id = p.employee_id THEN 1 ELSE 0 END) as employee_count,
SUM(CASE WHEN e.employee_id = p.reviewer_id THEN 1 ELSE 0 END) as reviewer_count
FROM employee e
LEFT JOIN performance_review p on e.employee_id = p.reviewer_id
or e.employee_id = p.employee_id
GROUP BY e.first_name, e.last_name

Selecting the Id's that have the same EmailAddress column value

What I need:
I am looking for a solution that can give me all the Employee Id's that have the same EmailAddress Column (the filter needs to be by EmailAddress).
I want to know what are the Id's correspondent to the duplicated Email Addresses and retrieve that information.
Table Employee:
Id | PlNumber | EmailAddress | EmployeeBeginingDate | EmployedEndDate | Name UserId(FK) | CreatedBy | CreatedOn
SELECT a.Id,a.EmailAddress
FROM Employee a
INNER JOIN (SELECT
Employee.Id as EmployeeId,
Employee.EmailAddress as EmailAddress,
FROM Employee
GROUP BY Employee.Id,Employee.EmailAddress
HAVING count(Employee.EmailAddress) > 1
) b
ON a.Id= b.EmployeeId
ORDER BY a.Id
I am always getting an error:
the multi-part identifier could not be bound.
I know why the error is happening but I couldn't solve this.
UPDATE: After a few changes the query is returning 0 rows but I know it should return at least 3 rows that I have duplicate values.
Try the below query as you have an aliased table Employee as a. So in place of Employee, you have to use a.
SELECT a.Id, a.EmailAddress
FROM Employee a
INNER JOIN (SELECT
Employee.EmailAddress as EmailAddress
FROM Employee
GROUP BY Employee.EmailAddress
HAVING count(Employee.EmailAddress) > 1
) b
ON a.EmailAddress = b.EmailAddress
ORDER BY a.Id
Live db<>fiddle demo.
Assuming the ids are different on each row, I would go for exists:
SELECT e.Id, e.EmailAddress
FROM Employee e
WHERE EXISTS (SELECT 1
FROM Employee e2
WHERE e2.EmailAddress = e.EmailAddress AND
e2.Id <> e.Id
)
ORDER BY e.EmailAddress;
Or, if you want to know the number of matches, use window functions:
SELECT e.Id, e.EmailAddress, cnt
FROM (SELECT e.*, COUNT(*) OVER (PARTITION BY e.EmailAddress) as cnt
FROM Employee e
) e
WHERE cnt >= 2;

List the department name with the least number of employees

There are two tables, employee and dept:
How can I join the two tables and select the department with the least number of employees?
It would have been better to answer if you provided the table structure.
But give this a try:
SELECT deptname
FROM dept
WHERE deptid = (SELECT distinct deptid
FROM employee
ORDER BY COUNT(dept) limit 1);
Please try the following...
SELECT deptid,
deptname,
employeeCount
FROM
(
SELECT dept.deptid AS deptid,
dept.deptname,
COUNT( dept.deptid ) AS employeeCount
FROM dept
JOIN employee ON dept.deptid = employee.deptid
GROUP BY dept.deptid,
dept.deptname
)
GROUP BY deptid
HAVING employeeCount = MIN( employeeCount );
This statement starts with the inner query joining dept to employee on the shared field deptid. It then groups the resulting rows by department and returns to the outer query the department's id and name along with a count of employees for that department.
The outer query groups the data by department again, then selects the details of the department(s) having a count of employees equal to the minimum count of employees.
If you have any questions or comments, then please feel free to post a Comment accordingly.
I'm only answering because MIN isn't needed and LIMIT isn't Oracle:
select d.*
from (select deptid, count(*) as cnt
from employees e
group by deptid
order by count(*) asc
) d
where rownum = 1;
In Oracle 12C+, you don't need the subquery:
select deptid, count(*) as cnt
from employees e
group by deptid
order by count(*) asc
fetch first 1 row only;
Hi :) so my answer is a bit ugly, and full of nested queries. but I tested it and it worked for me...
-- First I created a couple of test tables and added a few records
drop table dept;
drop table employee;
create table dept (deptid number primary key, deptname varchar(20));
create table employee(employee_id number primary key, names varchar(20),
deptid number,foreign key (deptid) references dept(deptid));
insert into dept values(1,'HR');
insert into dept values(2,'Finance');
insert into dept values(3,'IT');
insert into employee values(1,'Tina',1);
insert into employee values(2,'Rob',1);
insert into employee values(3,'Lisa',1);
insert into employee values(4,'Will',2);
insert into employee values(5,'Lina',2);
insert into employee values(6,'Ethel',2);
insert into employee values(7,'Trevor',1);
insert into employee values(8,'Alanea',1);
insert into employee values(9,'Matthew',1);
insert into employee values(10,'Maddie',3);
insert into employee values(11,'Anna',1);
-- According to the added records, the answer we are looking for should be
the department name IT
-- select the department name from department table
select d.deptname from dept d,
/* This is where it gets ugly - basically, it counts the number of
employees in each department, then finds the id of the department that had
the smallest count */
(select deptid from
(select count(deptid) as counter, deptid from employee group by deptid)
where counter =( select min(counter)from
(select count(deptid) as counter, deptid from employee group by deptid))) minid
-- join the tables using deptid
where d.deptid = minid.deptid;
This query gave the correct answer for me even when I changed the records to make finance the correct answer.
If you have any questions give me a yell through the comments :)
select department_name, count(employee_id)
from department d
inner join employee e
on d.employee_id = e.employee_id
having count(employee_id) =
(
select min(count(employee_id)) /*This query returns minimum count*/
from department d
inner join employee e
on d.employee_id = e.employee_id
group by department_name
)
group by department_name;
WITH temp
AS
(SELECT e1.department_id, count(e1.employee_id) emp_count
FROM hr.employees e1
GROUP BY e1.department_id)
SELECT d1.department_name, t1.emp_count employee_count
FROM temp t1
,hr.departments d1
WHERE t1.department_id = d1.department_id(+)
AND NOT EXISTS
(SELECT 1
FROM temp t2
WHERE t2.emp_count < t1.emp_count)
ORDER BY 2,1 ;

SELECT specific information

My tables are structured like this (there are more values in the tables but I only wrote the ones relevant to this):
Department(dep_id, dep_name)
Employee(dep_id)
I need to display dep_name and the number of employees in every department, except once specific department (let's call it DepX) and only the departments with more than one employee.
I tried multiple methods to solve this but none of them worked.
Some methods I tried:
SELECT department.dep_name, COUNT(employee.dep_id) AS NumberOfEmployees FROM employee
INNER JOIN department ON employee.dep_id=department.dep_id
WHERE dep_name<>'DepX'
GROUP BY dep_id
HAVING COUNT(employee.dep_id) > 1;
SELECT dep_name FROM department
WHERE dep_name <>'DepX'
UNION
SELECT COUNT(*) FROM employee
WHERE COUNT(*) > 1
GROUP BY dep_id;
I can't figure this out. Thanks!
The first example does now work because you're including dep_name in your results without an aggregation but not grouping on it.
You can either use the department name in your grouping instead of the ID:
SELECT department.dep_name, COUNT(employee.dep_id) AS NumberOfEmployees FROM employee
INNER JOIN department ON employee.dep_id=department.dep_id
WHERE dep_name<>'DepX'
GROUP BY department.dep_name
HAVING COUNT(employee.dep_id) > 1;
or do the COUNT in a subquery:
SELECT department.dep_name,
e.NumberOfEmployees
FROM department
INNER JOIN (SELECT dep_id,
COUNT(*) NumberOfEmployees
FROM employee
GROUP BY dept_id
HAVING COUNT(dept_id) > 1
) e
ON department.dep_id = e.dep_id
WHERE dep_name<>'DepX'
SELECT department.dep_name, COUNT(employee.dep_id) AS NumberOfEmployees FROM employee
INNER JOIN department ON employee.dep_id=department.dep_id
WHERE department.dep_name not in('DepX')
GROUP BY department.dep_name
HAVING COUNT(employee.dep_id) > 1;
update your table alias per your need
TEST this. This query help you return not only dept_name, it can return all fields from Department if you want:
SELECT d.*, A.numOfEmployees
FROM Department d,
(
SELECT e.dep_id, COUNT(*) numOfEmployees
FROM Employee e
GROUP BY e.dep_id
HAVING COUNT(*) > 1
) A
WHERE d.dep_id = A.dep_id
AND d.dep_name != 'DepX'

Is there a way to make this SQL more efficient?

Consider the following tables:
department
deptid (type:INT)
deptname (type: TEXT)
hours (type:INT)
active (type:BIT)
employee
empid (type:INT)
empname (type: TEXT)
deptid (type: INT)
designation (type: TEXT)
salary (type: INT)
Write a query to return the columns empname and deptname of the employees belonging to those
departments that have a head count of 4 or more. The records should be returned in alphabetical order of empname
This was my take:
SELECT e1.empname, d.deptname from employee AS e1
FULL JOIN department AS d on e1.deptid = d.deptid
WHERE e1.deptid IN(
SELECT deptid FROM(
SELECT e2.deptid, COUNT(e2.empid)
FROM employee AS e2
GROUP BY e2.deptid
HAVING COUNT(e2.empid) >= 4
)
)
ORDER BY empname;
How would you improve on this?
This is shorter and probably performs faster too
SELECT e1.empname, d.deptname
from (
SELECT e2.deptid
FROM employee AS e2
GROUP BY e2.deptid
HAVING COUNT(e2.empid) >= 4
) G
inner join employee AS e1 on e1.deptid = G.deptid
INNER JOIN department AS d on d.deptid = G.deptid
ORDER BY e1.empname;
Start with the grouping. You don't need COUNT from the inner query.
Then, join to both tables just to get the names.
INNER JOIN is used because once the count is complete, we already know that
the employees exist
the department exists
Try this query, it will work properly.
select empname,deptname from employee,department
where
employee.deptid=department.deptid and employee.deptid
in
(
select deptId from employee group by deptid having count(*)>=4
)
order by empname