这是一个双重嵌套的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,在catalog 中C.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 不会成为此循环结果集的一部分。所以我们移动到parts、2,'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 = 9 和pid = 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 的值添加到结果集中,这意味着他们携带了所有东西!