至少有一个X但没有Ys查询

时间:2012-03-08 23:59:09

标签: mysql sql join subquery

我偶尔会遇到这种模式,我没有找到一种非常令人满意的解决方法。

假设我有一个employee表和一个review表。每位员工都可以进行多次审核。我想找到所有employee个至少有一个“好”评论但没有“坏评论”的人。

我还没有弄清楚如何在不知道员工ID的情况下让子选项工作,我还没有想出正确的连接组合来实现这一点。

有没有办法 WITHOUT 存储过程,函数或带数据服务器端?我已经让它与那些合作,但我相信还有另一种方式。

4 个答案:

答案 0 :(得分:7)

由于您尚未发布数据库结构,我做了一些假设和简化(关于rating列,可能是数字而不是字符字段)。相应调整。

解决方案1:使用联接

select distinct e.EmployeeId, e.Name
from employee e
left join reviews r1 on e.EmployeeId = r1.EmployeeId and r1.rating = 'good'
left join reviews r2 on e.EmployeeId = r2.EmployeeId and r1.rating = 'bad'
where r1.ReviewId is not null --meaning there's at least one
and r2.ReviewId is null --meaning there's no bad review

解决方案2:使用条件计数进行分组和过滤

select e.EmployeeId, max(e.Name) Name
from employee e
left join reviews r on e.EmployeeId = r.EmployeeId
group by e.EmployeeId
having count(case r.rating when 'good' then 1 else null end) > 0
and  count(case r.rating when 'bad' then 1 else null end) = 0

这两种解决方案都是SQL ANSI兼容的,这意味着它们都可以使用完全支持SQL ANSI标准的任何RDBMS风格(大多数RDBMS都适用)。

正如@onedaywhen指出的那样,代码在MS Access中不起作用(没有经过测试,我相信他在这方面的专业知识)。

但我有一个说法(这可能会让一些人感到不安):我几乎不认为MS Access是一个RDBMS。我过去曾与它合作过。一旦你继续前进(Oracle,SQL Server,Firebird,PostGreSQL,MySQL,你的名字),你永远不想回来。严重。

答案 1 :(得分:5)

问题 - 基于B中不存在匹配的A侧返回行 - (没有“错误”评论的员工)描述了“反半连接”。有很多种方法可以完成这种查询,至少有5种我在MS Sql 2005及以上版本中发现过。

我知道这个解决方案适用于 MSSQL 2000及以上版本,并且是我在MS Sql 2005和2008中尝试的5种方法中效率最高的。我不确定它是否有效在MySQL中,它应该,因为它反映了一个相当常见的集合操作。

注意,IN子句使子查询能够访问外部作用域中的employee表。

SELECT EE.*
FROM   employee EE
WHERE
    EE.EmpKey IN (
      SELECT RR.EmpKey
      FROM   review RR
      WHERE  RR.EmpKey = EE.EmpKey
        AND  RR.ScoreCategory = 'good'
    ) 
  AND
    EE.EmpKey NOT IN (
      SELECT  RR.EmpKey 
      FROM    review RR
      WHERE   RR.EmpKey = EE.EmpKey
        AND   RR.ScoreCategory = 'bad'
    )

答案 2 :(得分:1)

这是可能的。具体的语法取决于您如何存储“好”和“坏”的评论。

假设您在classification中有review列,其值为'good'且'bad'。

然后你可以这样做:

SELECT employee.*
FROM employee
JOIN review 
 ON employee.id=review.employee_id
GROUP BY employee.id
HAVING SUM(IF(classification='good',1,0))>0 -- count up #good reviews, > 0
   AND SUM(IF(classification='bad',1,0))=0  -- count up #bad  reviews, = 0.

答案 3 :(得分:1)

SELECT ???? FROM employee,review
WHERE employees.id = review.id
GROUP BY employees.id
HAVING SUM(IF(review='good',1,0)) > 1 AND SUM(IF(review='bad',1,0)) = 0