Select 只属于特定部门的用户

Select users belonging only to particular departments

我有以下 table 两个字段,即 a 和 b,如下所示:

create table employe
(
    empID varchar(10),
    department varchar(10)
);

正在插入一些记录:

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

注意:现在我想显示唯一且只属于部门ZY的员工。 所以根据条件,应该显示唯一的员工 A103 因为他只属于 到 ZY 部门。但是员工 A101 不应该出现,因为他属于 Z,X, and Y.

预期结果

如果条件是:ZY 那么结果应该是:

empID
------
A103

如果条件是:ZX 那么结果应该是:

empID
------
A102

如果条件是:ZXY 那么结果应该是:

empID
------
A101

注意:我只想在 where 子句中这样做(不想使用 group byhaving 个子句),因为我要把这个也包含在另一个 where 中。

对于条件 1:z 和 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' ) 

对于条件 1:z 和 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' )

对于条件 1:z,y 和 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

当您希望部门 'Y' 和 'Z' 而非 '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') ;

对于第二种情况,只需将最后一个条件中的 not in 替换为 in

试试这个

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') ;

您可以像这样将 GROUP BYhaving 一起使用。 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

没有GROUP BYHaving

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')

如果您想将上述任何查询与其他 table 一起使用,您可以使用 CTE 或像这样派生的 table。

;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

这是一个没有余数的关系除法 (RDNR) 问题。请参阅 Dwain Camps 的 article,它为此类问题提供了许多解决方案。

第一个解决方案

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
    )

第二种解法

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)

不使用 GROUP BYHAVING:

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
    )

试试这个,

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

计数必须基于条件数。

如果条件为:Z 和 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'))

我知道这个问题已经有人回答了,但这是一个有趣的问题,我尝试以一种其他人没有的方式来做。我的好处是你可以输入任何字符串列表,只要每个值后面都有一个逗号,你不必担心检查计数。

注意:值必须按字母顺序列出。

XML 使用 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,'

结果:

empID
----------
A103

也可以 使用 GROUP BYHAVING — 您只需要在子查询中执行即可。

例如,让我们从一个简单的查询开始,查找部门 XY 中的所有员工(而不是任何其他部门中的员工) ):

SELECT empID,
  GROUP_CONCAT(DISTINCT department ORDER BY department ASC) AS depts
FROM emp_dept GROUP BY empID
HAVING depts = 'X,Y'

我在这里使用 MySQL 的 GROUP_CONCAT() 函数作为一个方便的快捷方式,但是没有它你也可以得到相同的结果,例如像这样:

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

现在,要将此与其他查询条件结合起来,只需采用包含其他条件的查询,然后 根据上面查询的输出加入您的员工 table ]:

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。你应该使用 JOIN 而不是 IN 子查询的原因是 MySQL is not so good at optimizing IN subqueries.

具体来说(至少从 v5.7 开始),MySQL 总是将 IN 子查询转换为 dependent 子查询,因此子查询必须是为外部查询的每一行重新执行,即使原始子查询是独立的。例如,以下查询(来自上面链接的文档):

SELECT ... FROM t1 WHERE t1.a IN (SELECT b FROM t2);

有效地转换为:

SELECT ... FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.b = t1.a);

可能仍然相当快,如果t2很小and/or有一个允许快速查找的索引。但是,如果(如上面的原始示例)执行子查询可能需要大量工作,性能可能会受到严重影响。相反,使用 JOIN 允许子查询只执行一次,因此通常会提供更好的性能。

自联接怎么样? (符合 ANSI - 工作超过 20 年)

SELECT * FROM employee e JOIN employee e2 ON e.empid = e2.empid
WHERE e.department = 'x' AND e2.department ='y'

这表明a101和a104都在两个部门工作。

使用 where 子句的解决方案:

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 检查是否有特定 EmpId 的记录总计数 department 等于仅匹配 department 的条件计数,并且它是也等于提供给 in 子句的 department 的数量。另外值得一提的是,这里我们在整个集合上应用 having 子句而不使用 group by 子句,但已经指定,只有一个 empID.

SQLFiddle

你可以在没有相关子查询的情况下实现这一点,但是使用 group by 子句:

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

您还可以对上面的查询使用 having 子句的另一种变体:

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

在 Postgres 中,这可以使用数组来简化:

select empid
from employee
group by empid
having array_agg(department order by department)::text[] = array['Y','Z'];

array_agg() 中的元素进行排序并将它们与按相同顺序排序的部门列表进行比较很重要。否则这不会 return 正确答案。

例如array_agg(department) = array['Z', 'Y'] 可能会 return 错误的结果。

这可以通过使用 CTE 为部门提供更灵活的方式来完成:

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);

这样一来,元素的排序始终由数据库完成,并且聚合数组中的值与它所比较的​​值之间将保持一致。


标准 SQL 的一个选项是检查是否至少有一行有不同的部门并计算所有行

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;

如果一个员工可能被分配到同一个部门两次,上述解决方案将不起作用!

having min (...) 可以在 Postgres 中使用聚合 bool_and() 进行简化。

当应用标准 filter() 条件进行条件聚合时,这也可以用于员工可以两次分配到同一部门的情况

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')) 仅 return 为真。


标准 SQL 的另一种解决方案是使用至少拥有这两个部门的员工与恰好分配给两个部门的员工之间的交集:

-- 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;