更多的理论/逻辑问题,但我有两个表:links
和 options
.链接是一个表,我在其中添加了代表产品 ID(在单独的 products
表中)和选项之间的链接的行。 options
表包含所有可用选项。
我想要做的(但努力为其创建逻辑)是连接两个表,仅返回 links
中没有选项链接的行。表,因此表示哪些选项仍可添加到产品中。
是否有 SQL 的功能可以帮助我?我对 SQL 还不是很有经验。
最佳答案
你的 table 设计听起来不错。
如果此查询返回 id
链接到特定“产品”的“选项”的值...
SELECT k.option_id
FROM links k
WHERE k.product_id = 'foo'
然后此查询将获得与“产品”相关的所有选项的详细信息
SELECT o.id
, o.name
FROM options o
JOIN links k
ON k.option_id = o.id
WHERE k.product_id = 'foo'
请注意,我们实际上可以移动
"product_id='foo'"
从 WHERE 子句到 JOIN 的 ON 子句的谓词,以获得等效的结果,例如SELECT o.id
, o.name
FROM options o
JOIN links k
ON k.option_id = o.id
AND k.product_id = 'foo'
(并不是说它在这里有什么不同,但如果我们使用 OUTER JOIN 会有所不同(在 WHERE 子句中,它会否定连接的“外部性”,并使其等效于 INNER JOIN。 )
但是,这些都没有回答您的问题,它只是为回答您的问题奠定了基础:
我们如何从未链接到特定产品的“选项”中获取行?
最有效的方法是(通常)反连接图案。
也就是说,我们将从“选项”中获取所有行,以及“链接”中的任何匹配行(对于特定的 product_id,在您的情况下)。该结果集将包括“选项”中在“链接”中没有匹配行的行。
“技巧”是过滤掉在“链接”中找到匹配行的所有行。这将留给我们 只有没有匹配的行。
我们过滤这些行的方式是在 WHERE 子句中使用一个谓词来检查是否找到了匹配项。我们通过检查我们确定将是 的列来做到这一点。非空 如果找到匹配的行。我们肯定知道*该列将是 空 如果匹配的行是 不是 成立。
像这样的东西:
SELECT o.id
, o.name
FROM options o
LEFT
JOIN links k
ON k.option_id = o.id
AND k.product_id = 'foo'
WHERE k.option_id IS NULL
"LEFT"
关键字指定“外部”连接操作,即使没有找到匹配的行,我们也会从“选项”(连接“左侧”的表)中获取所有行。 (普通的内连接会过滤掉没有匹配的行。)“技巧”在 WHERE 子句中……如果我们从链接中找到匹配的行,我们就知道
"option_id"
从 "links"
返回的列不会为NULL。如果它“等于”某些东西,它就不能为 NULL,而且我们知道它必须“等于”某些东西,因为 ON 子句中的谓词。因此,我们知道没有匹配项的选项行将具有该列的 NULL 值。
让你的大脑围绕它需要一点时间,但反连接很快就变成了一种熟悉的模式。
“反连接”模式不是获得结果集的唯一方法。还有其他几种方法。
一种选择是使用带有
"NOT EXISTS"
的查询带有相关子查询的谓词。这更容易理解,但通常效果不佳:SELECT o.id
, o.name
FROM options o
WHERE NOT EXISTS ( SELECT 1
FROM links k
WHERE k.option_id = o.id
AND k.product_id = 'foo'
)
这表示从选项表中获取所有行。但是对于每一行,运行一个查询,并查看链接表中是否“存在”匹配的行。 (选择列表中返回什么并不重要,我们只是测试它是否至少返回一行......我在选择列表中使用“1”来提醒我我正在寻找“1行” ”。
这通常不如反连接执行得好,但有时它确实运行得更快,特别是如果外部查询的 WHERE 子句中的其他谓词几乎过滤掉了每一行,并且子查询只需要运行几个行。 (也就是说,当我们只需要检查干草堆中的几根针时。当我们需要处理整个干草堆时,反连接模式通常更快。)
您最有可能看到的初学者查询是
NOT IN (subquery)
.我什至不打算举一个例子。如果你有一个文字列表,那么一定要使用 NOT IN。但是对于子查询,它很少是表现最好的,尽管它似乎是最容易理解的。哦,干草,我也会给出一个演示(不是我鼓励你这样做):
SELECT o.id
, o.name
FROM options o
WHERE o.id NOT IN ( SELECT k.option_id
FROM links k
WHERE k.product_id = 'foo'
AND k.option_id IS NOT NULL
GROUP BY k.option_id
)
该子查询(在括号内)获取与产品关联的所有 option_id 值的列表。
现在,对于 options 中的每一行(在外部查询中),我们可以检查 id 值以查看它是否在子查询返回的列表中。
如果我们保证 option_id 永远不会为 NULL,我们可以省略测试
"option_id IS NOT NULL"
的谓词。 . (在更一般的情况下,当 NULL 进入结果集时,外部查询无法判断 o.id 是否在列表中,并且查询不返回任何行;所以我通常包括,即使不是必需的。GROUP BY
也不是绝对必要的;特别是如果 (product_id,option_id) 元组上有唯一约束(保证唯一性)。但是,同样,不要使用
NOT IN (subquery)
,除了测试,除非有一些令人信服的理由(例如,它设法比反连接执行得更好。)您不太可能注意到小集合的任何性能差异,传输语句、解析它、生成访问计划和返回结果的开销使计划的实际“执行”时间相形见绌。 “执行”时间的差异在更大的集合中变得明显。
EXPLAIN SELECT ...
是处理执行计划的一种非常好的方法,可以查看 MySQL 对您的语句真正执行的操作。适当的索引,尤其是覆盖索引,可以显着提高某些语句的性能。
关于mysql - SQL JOIN 查询返回我们在连接表中没有找到匹配项的行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22975556/