Select users belonging only to particular departme

2020-07-02 00:03发布

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.

12条回答
Explosion°爆炸
2楼-- · 2020-07-02 00:23

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
    )
查看更多
冷血范
3楼-- · 2020-07-02 00:26

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'))
查看更多
Fickle 薄情
4楼-- · 2020-07-02 00:31

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;
查看更多
孤傲高冷的网名
5楼-- · 2020-07-02 00:34

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.

查看更多
smile是对你的礼貌
6楼-- · 2020-07-02 00:34

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.

查看更多
甜甜的少女心
7楼-- · 2020-07-02 00:36

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.

查看更多
登录 后发表回答