4

我想知道这个查询是如何工作的:

SELECT empname FROM Employee WHERE not exists (
    SELECT projid FROM Project WHERE not exists (
        SELECT empid, projid FROM Assigned WHERE empid = Employee.empid and projid = Project.projid
    )
)

它应该返回分配给每个项目的所有员工的姓名,并且它确实有效,但是我对它如何/为什么正确工作感到困惑。

架构是:

员工(empID INT,empName VARCHAR(100),job VARCHAR(100),deptID INT,salary INT);
已分配(empID INT,projID INT,角色 VARCHAR(100));
项目(projID INT,title VARCHAR(100),budget INT,funds INT);

我是 SQL 新手,因此不胜感激。

4

2 回答 2

4

当我需要尝试了解正在发生的事情时,我会寻找最内部的查询并向外工作。在你的情况下,让我们从:

SELECT empid, projid 
FROM Assigned 
WHERE empid = Employee.empid and projid = Project.projid

这匹配已分配表中的所有记录,其中 empid 和 projid 在之前的表中(因此是 Employee.empid 和 Project.projid)。

假设 Projects 表中有 5 个项目,并为每个项目分配了 Employee1。这将返回 5 条记录。还假设 Employee2 被分配到这些项目中的 1 个,因此返回 1 条记录。

接下来看:

SELECT projid FROM Project WHERE not exists (
        ...
    )

现在这表示对于在上一个查询中找到的记录(Employee1 有 5 个项目,Employee2 有 1 个项目),从上一个查询中没有任何匹配项(不存在)的 Project 表中选择任何 projid。换句话说,Employee1 不会从这个查询中返回任何项目,但 Employee2 会返回 4 个项目。

最后,看看

 SELECT empname FROM Employee WHERE not exists (
        ...
    )

与第二个查询一样,对于在前一个查询中找到的任何记录(没有记录将这些员工与所有项目(如 Employee1)匹配,如果员工未分配到每个项目(如 Employee2)则有一些记录),从没有任何匹配项的 Employee 表(同样,不存在)。换句话说,Employee1 会返回,因为之前的查询没有返回任何项目,而 Employee2 不会返回,因为之前的查询返回了 1 个或多个项目。

希望这可以帮助。以下是有关 EXISTS 的一些附加信息:

http://dev.mysql.com/doc/refman/5.0/en/exists-and-not-exists-subqueries.html

从那篇文章中:

所有城市都有什么样的商店?

SELECT DISTINCT store_type FROM stores s1   WHERE NOT EXISTS (
    SELECT * FROM cities WHERE NOT EXISTS (
      SELECT * FROM cities_stores
       WHERE cities_stores.city = cities.city AND cities_stores.store_type = stores.store_type));

最后一个示例是双嵌套的 NOT EXISTS 查询。也就是说,它在 NOT EXISTS 子句中有一个 NOT EXISTS 子句。形式上,它回答了“一个城市是否存在一个没有商店的商店”的问题?但更容易说嵌套的 NOT EXISTS 回答了“x 对所有 y 都为真吗?”的问题</p>

祝你好运。

于 2013-02-01T04:50:08.007 回答
1

NOT EXISTS (subquery)子查询的结果集没有行时,谓词将返回 TRUE。当找到匹配的行时,它将返回 FALSE。

本质上,查询是在询问

对于 Employee 中的每一行...检查 Project 表中的每一行,以查看 Assigned 表中是否有一行的 empid 与 Employee 行上的 empid 和 projid 匹配项目表。

仅当未找到匹配行时,才会返回来自 Employee 的行。

注意,子查询的 SELECT 列表中的表达式并不重要;正在检查的只是该子查询是否返回一(或多)行。通常,我们在 SELECT 列表中使用文字 1;这提醒我们,我们正在检查的是是否找到了一行。)

我通常会以如下所示的样式编写该查询:

SELECT e.empname 
  FROM Employee e 
 WHERE NOT EXISTS
       ( SELECT 1
           FROM Project p
          WHERE NOT EXISTS
                ( SELECT 1 
                    FROM Assigned a 
                   WHERE a.empid = e.empid
                     AND a.projid = p.projid
                )
       )

我将“ SELECT 1”读为“选择一行”)

该查询的结果集本质上等同于该查询的结果集(通常效率低得多):

SELECT e.empname
  FROM Employee e
 WHERE e.empid NOT IN
       ( SELECT a.empid
           FROM Assigned a
           JOIN Project p
             ON a.projid = p.projid
         WHERE a.empid IS NOT NULL
          GROUP
             BY a.empid
       )

NOT IN查询可能更容易理解,因为您可以运行该子查询并查看它返回的内容。(NOT EXISTS 子查询可能有点令人困惑的是,在 SELECT 列表中返回什么表达式并不重要;重要的是是否返回一行。)NOT IN 有一些“陷阱”子查询除了性能很差;您需要小心确保子查询不返回 NULL 值,因为这样 NOT IN (NULL,...) 将永远不会返回 true。

也可以使用反连接模式返回等效的结果集:

SELECT e.empname
  FROM Employee e
  LEFT
  JOIN ( SELECT a.empid
           FROM Assigned a
           JOIN Project p
             ON a.projid = p.projid
         WHERE a.empid IS NOT NULL
          GROUP
             BY a.empid
       ) o
    ON o.empid = e.empid
WHERE o.empid IS NULL

在该查询中,我们正在 empid 上查找“匹配项”。LEFT 关键字告诉 MySQL 从 Employee(JOIN 左侧的表)返回任何不匹配的行。对于这些行,将返回 NULL 值来代替如果存在匹配行时将返回的列的值。然后,“技巧”是丢弃所有匹配的行。我们通过检查如果存在匹配则不会为 NULL 的列中的 NULL 来做到这一点。

如果我要使用NOT EXISTS谓词编写这个查询,我可能实际上更喜欢这样写:

SELECT e.empname
  FROM Employee e
 WHERE NOT EXISTS
       ( SELECT 1
           FROM Assigned a
           JOIN Project p
             ON a.projid = p.projid
          WHERE a.empid = e.empid
       )
于 2013-02-01T04:52:02.510 回答