【问题标题】:Select users belonging only to particular departments选择仅属于特定部门的用户
【发布时间】:2015-05-07 04:54:38
【问题描述】:

我有下表,其中包含两个字段,即 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

如果条件是:Z,XY,那么结果应该是:

empID
------
A101

注意:我只想在where 子句中执行(不想使用group byhaving 子句),因为我要去将这个包含在另一个 where 中。

【问题讨论】:

  • 为什么不想使用GROUP BYHAVING?使用JOIN 可能会导致性能问题。
  • @wewestthemenace,因为我想通过使用AND 将这个条件与另一个条件加入。
  • 为什么不在派生表或cte中使用这个sql呢?
  • @MAK 这个方法怎么样select empID from ( select empID,string_agg(department,',') dep from employe group by empID)t where dep in ('Z,Y') ??
  • @MAK 是的,但是您标记了 PostgreSQL,这就是为什么:D,仅标记相关数据库

标签: sql sql-server postgresql sql-server-2008-r2


【解决方案1】:

这是一个无余数 (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
    )

【讨论】:

  • 我仍然建议你使用带有GROUP BY的那些。
  • @wewestthemenace,实际上我正在为 where 子句查询准备一个字符串。在那个字符串中我不能使用group by,因为我有很多其他条件该字符串。我只想把这个条件和那个连接起来。
【解决方案2】:

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

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

使用 CROSS APPLY 的 XML 解决方案

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

【讨论】:

  • 不错!对于那些无法拥有CROSS APPLY 的人,请使用GROUP_CONCAT 或类似名称。
【解决方案3】:

对于条件 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

【讨论】:

    【解决方案4】:

    您可以像这样使用GROUP BYhavingSQL 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')
    

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

    ;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
    

    【讨论】:

      【解决方案5】:

      试试这个

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

      【讨论】:

      • 没有一行在一个单元格中同时具有两个不同的值。永远。
      • where department = 'Z' and department = 'Y' 永远不会是真的
      【解决方案6】:

      如果条件是: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'))
      

      【讨论】:

        【解决方案7】:

        当您想要来自部门“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

        【讨论】:

          【解决方案8】:

          试试这个,

          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
          

          计数必须基于条件的数量。

          【讨论】:

            【解决方案9】:

            也可以使用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
            

            现在,要将其与其他查询条件相结合,只需执行包含其他条件的查询,然后将您的员工表与上述查询的输出相结合

            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.


            附言。你应该使用JOIN 而不是IN 子查询的原因是因为MySQL is not so good at optimizing IN subqueries.

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

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

            【讨论】:

              【解决方案10】:

              自我加入呢? (符合 ANSI 标准 - 工作了 20 年以上)

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

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

              【讨论】:

                【解决方案11】:

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

                【讨论】:

                  【解决方案12】:

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

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

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

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

                  这可以使用 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')) 仅在组中所有行的条件为真时返回真。


                  另一种使用标准 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;
                  

                  【讨论】:

                    猜你喜欢
                    • 2012-12-10
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2018-12-17
                    • 2015-12-30
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多