【问题标题】:Find the sids of the suppliers who supply every part找到提供每个零件的供应商的 sid
【发布时间】:2015-03-12 14:27:51
【问题描述】:

3 个表:Suppliers(sid, sname, address), Parts(pid, pname, colour), Catalog(sid, pid, cost)

找到所有供应商的答案是:

SELECT  C.sid
  FROM  Catalog C
  WHERE NOT EXISTS (
     SELECT  P.pid
       FROM  Parts P
       WHERE NOT EXISTS (
         SELECT  C1.sid
           FROM  Catalog C1
           WHERE C1.sid = C.sid
             AND C1.pid = P.pid
         )
     )

有人可以向我解释这个答案吗?我只是有点失落!

我听说它被解释为“寻找不存在的供应商” 他们不卖的部分',但我正在努力看看如何

SELECT  C1.sid
           FROM  Catalog C1
           WHERE C1.sid = C.sid
             AND C1.pid = P.pid
         )

做到了。

如果我有一个

目录表

詹姆斯 |锤子

詹姆斯 |铁砧

詹姆斯 |扳手

亨利 |锤子

乐华 |铁砧

零件表

锤子

铁砧

扳手

那么在最里面的子句之后究竟返回了什么?

是吗

詹姆斯 |锤子

詹姆斯 |铁砧

詹姆斯 |扳手

亨利 |锤子

乐华 |铁砧

然后NOT EXISTS就可以了

詹姆斯 | --

亨利 |铁砧、扳手

乐华 |锤子、扳手?

然后零件表如何减去这些值?抱歉,如果这些问题不太清楚,我还是 SQL 新手。

【问题讨论】:

  • 你使用的是什么关系型数据库?
  • 抱歉,我不确定我是否理解这个问题。你是说 SQL 吗?
  • 不,你的数据库是什么? MSSQL 或 MySQL 或任何其他数据库?
  • 再仔细看看你的第二个查询。 C 在哪里定义?这不会像你想的那样工作
  • 在第一个子句中定义。那么它的功能是否类似于 for 循环?

标签: sql algebra relational


【解决方案1】:

这是一个双重嵌套的NOT EXISTS 查询(不是真的,这就是我通常看到的它的名称),它专门用于回答这种类型的问题,即“是否有任何 x 对所有 y ?”

Here's MySQL's page on EXISTS and NOT EXISTS,其中特别提到了这种技术。

首先,在最里面的SELECT 查询中,您正在选择每个商店携带的零件。然后,使用第一个NOT EXISTS 子句,您将选择每个商店不提供的零件。最后,在外部 NOT EXISTS 子句中,您选择为内部 NOT EXISTS 子句返回空集的商店,这意味着它们包含每个部分。

Here is a SQLFiddle 的此查询正在执行中。

警告:如果您正在使用 SQL,那么在集合中思考和工作总是好的,而以线性方式思考(如即将发生的事情)会让您很快陷入困境.不要养成习惯!

但是,有时当试图找出这样一个复杂的查询时,将这些事情视为循环会有所帮助。

所以,以小提琴中的数据为例,我们有:

suppliers:
sid, name
9, 'AAA'
8, 'BBB'
7, 'CCC'

parts:
pid, name
1, 'wood'
2, 'stone'
3, 'paper'

catalog:
cid, pid, sid
1,1,9
2,2,9
3,1,8
4,1,7
5,2,7
6,3,7

所以有了这个数据,AAA 承载木头和石头,BBB 只承载木头,CCC 承载木头、石头和纸。

现在让我们逐行执行查询。我们从suppliers 中进行选择,并且我们正在决定将哪些行包含在结果集中,因此从suppliers 中的第一行开始:9,'AAA'。我们将暂时将此行称为S。如果内部结果集中没有任何内容,我们只会包含这一行,所以让我们看一下。

suppliers:
sid, name
S => 9, 'AAA'
     8, 'BBB'
     7, 'CCC'

这个结果集是从parts 中选择的,我们将逐行查看它。当我们这样做时,S 仍然等于 9,'AAA'。所以从parts的第一行开始:1,'wood'。我们暂时将此行称为P。如果下一级结果集中没有任何内容,我们只会将这一行包含在第一个内部结果集中,所以让我们移到那里。请记住 S = 9,'AAA'P = 1,'wood'

suppliers:
sid, name
S => 9, 'AAA'
     8, 'BBB'
     7, 'CCC'

parts:
pid, name
P => 1, 'wood'
     2, 'stone'
     3, 'paper'

这个最里面的查询是从“目录”中选择的。我们正在寻找任何行,我们将其称为C,在catalogC.sid 等于S.sid AND C.pid 等于P.pid。这意味着当前供应商携带该零件。我们想要当前供应商不提供的零件,这就是我们反转结果集的原因。我们知道S 的 sid 是 9,我们知道 P 的 pid 是 1。C 中是否有任何行与此匹配? C 的第一行与此匹配,因此我们知道此结果集不为空。

suppliers:
sid, name
S => 9, 'AAA'
     8, 'BBB'
     7, 'CCC'

parts:
pid, name
P => 1, 'wood'
     2, 'stone'
     3, 'paper'

catalog:
cid, pid, sid
C => 1,1,9 --Match found! Don't include P in outer result set
     2,2,9
     3,1,8
     4,1,7
     5,2,7
     6,3,7

现在跳回到下一个最外面的循环。内部结果集不为空,因此我们知道1,'wood 不会成为此循环结果集的一部分。所以我们移动到parts2,'stone' 中的下一行。我们将P 的值更新为等于该行。我们应该在结果集中包含这一行吗?我们必须使用 P 的新值再次运行内部查询(S 仍未更改)。因此,我们在catalog 中查找任何行,其中sid 等于9,pid 等于2。第二行匹配,因此有一个结果集。

suppliers:
sid, name
S => 9, 'AAA'
     8, 'BBB'
     7, 'CCC'

parts:
pid, name
     1, 'wood'
P => 2, 'stone'
     3, 'paper'

catalog:
cid, pid, sid
     1,1,9 
C => 2,2,9 --Match found! Don't include P in outer result set
     3,1,8
     4,1,7
     5,2,7
     6,3,7

跳回到下一个最外面的循环。内部结果集不为空,因此2,'stone' 不会成为此循环结果集的一部分。

当我们再次检查3,'paper' 时,我们发现catalog 中没有包含sid = 9pid = 3 的行。最里面的结果集是空的,所以我们知道在下一个最外面的循环中包含P 的值。

suppliers:
sid, name
S => 9, 'AAA'
     8, 'BBB'
     7, 'CCC'

parts:
pid, name
     1, 'wood'
     2, 'stone'
P => 3, 'paper'

catalog:
cid, pid, sid
     1,1,9 
     2,2,9 
     3,1,8
     4,1,7
     5,2,7
     6,3,7
C => --No match found, include P in outer result set

此时我们已经遍历了整个parts表,所以我们有了第二个循环的最终结果集,并且它不为空,这意味着我们找到了S没有携带的部分,所以我们知道我们不能在最终外循环的结果集中包含S 的当前值。

所以我们转到suppliers 中的下一行并重新开始整个过程​​:

suppliers:
sid, name
     9, 'AAA'
S => 8, 'BBB'
     7, 'CCC'

一旦我们到达S = 7,'CCC',我们将遍历所有这些循环,并且将在内部循环中为提供的每个 P 值找到一个匹配项,这意味着第二个循环将有一个空集。我们找不到供应商不携带的任何零件,因此将 S 的值添加到结果集中,这意味着他们携带了所有东西!

【讨论】:

  • 我明白了。你能解释一下我强调的结尾部分是什么意思吗? (C1.sid = C.id AND C1.pid = P.pid) 与 (C.pid = P.pid) 有何不同。它似乎明显不同,但我看不出如何..
  • 事情有点长,请参阅我的扩展答案
  • 我想我可以更好地遵循它。但是在 Parts 中,WHERE NOT EXISTS.. 最内层的 SELECT 不应该是 Parts 表共享的属性吗?
  • 最里面的 SELECT 在 P.pid 和 C.cid 上连接。它通过在 WHERE 子句中引用来连接到内部子句和外部子句。您仍然有些困惑,我并不感到惊讶,有时我仍然很难理解这个特定的查询结构。
  • 是的,这确实令人困惑,但这很有帮助。我已经编辑了我的第一篇文章,以显示哪个部分仍然让我感到困惑。你能看一下吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-12
  • 2018-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多