SQL JOIN 查询返回我们在连接表中没有找到匹配项的行

Posted

技术标签:

【中文标题】SQL JOIN 查询返回我们在连接表中没有找到匹配项的行【英文标题】:SQL JOIN Query to return rows where we did NOT find a match in joined table 【发布时间】:2014-04-09 23:01:29 【问题描述】:

更多的是理论/逻辑问题,但我有两张表:linksoptions。 Links 是一个表,我在其中添加表示产品 ID(在单独的 products 表中)和选项之间的链接的行。 options 表包含所有可用选项。

我正在尝试做的(但难以为其创建逻辑)是加入两个表,仅返回 links 表中没有选项链接的行,因此表示哪些选项仍然可用添加到产品中。

SQL 的某个特性是否可以帮助我?我还没有丰富的 SQL 经验。

【问题讨论】:

查找LEFT JOINNOT EXISTS 您使用的是什么数据库平台?在问题中标记它。 【参考方案1】:

你的桌子设计听起来不错。

如果此查询返回链接到特定“产品”的“选项”的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 子句中,它会否定连接的“外部性”,并使其等同于内连接。)

但是,这些都不能回答你的问题,它只是为回答你的问题奠定了基础:

我们如何从“选项”中获取未链接到特定产品的行?

最有效的方法(通常)是anti-join模式。

也就是说,我们将从“options”中获取所有行,以及“links”中的任何匹配行(在您的情况下,对于特定的 product_id)。该结果集将包括“选项”中在“链接”中没有匹配行的行。

“技巧”是过滤掉在“链接”中找到匹配行的所有行。这将使我们没有匹配的行。

我们过滤这些行的方式是,我们在 WHERE 子句中使用谓词来检查是否找到匹配项。我们通过检查一个我们确定的列来做到这一点,如果找到匹配的行,我们肯定会NOT NULL。我们知道*如果找到匹配的行NOT,该列将是NULL

类似这样的:

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" 关键字指定了一个“外部”连接操作,即使没有找到匹配的行,我们也会从“选项”(JOIN 左侧的表)中获取所有行。 (普通的内连接会过滤掉没有匹配的行。)

“技巧”在 WHERE 子句中...如果我们从链接中找到匹配的行,我们知道从 "links" 返回的 "option_id" 列不会为 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 对你的语句做了什么。

适当的索引,尤其是覆盖索引,可以显着提高某些语句的性能。

【讨论】:

不错的答案!我可能会编辑“如果找到匹配项,检查我们知道不会为 NULL 的列”以明确您的意思是“如果找不到匹配项,我们知道将为 NULL” 绝妙的答案。我不得不读了几遍才能完全理解我们到底在用反连接做什么,但现在它完全有意义了。这就是我来溢出的那种答案 - 不仅仅是“这是解决它的方法”,而是“这就是我们这样做来修复它的原因”。信息量很大。我几乎已经准备好用 php 中的几个 foreach 循环来处理过滤了,非常感谢! @RobP:是的,这可能会令人困惑。我相当确定这个查询是正确的,我对它的作用的解释是乱码。 你说得对,我只是建议你的意图更清晰的措辞。 @twistedpixel:虽然有时快速解决问题会很快,但我认为了解我们如何处理问题并获得一些“工具带”中的“工具”经验确实更重要“下次遇到类似问题时。 (我的回答有时会因为过于冗长而被否决;我向“试试这个”的爱好者道歉。【参考方案2】:

是的,您可以执行LEFT JOIN(如果是 MySQL;其他方言中存在变体),其中将包含选项中不匹配的链接中的行。然后测试 options.someColumn IS NULL 是否会在链接中找到选项中没有“匹配”行的行。

【讨论】:

这很棒,很有道理。我很好地了解了连接之间的差异,现在我也更好地理解了它们。但是,我遇到的问题是链接表包含不同产品的链接。每个产品都可以有一个指向选项表中每个选项的链接。因此问题是左连接将返回所有产品的所有非链接。但我不能为产品 ID 做一个简单的 where ,因为那样它将排除非链接!我担心我可能不得不重新考虑我的表格结构。 @twistedpixel:无需重组表...只需将 product_id 上的谓词移动到 OUTER JOIN 的 ON 子句中,因此您只需匹配“链接”中特定的行产品编号。 WHERE 子句可以进行测试以查看是否找到匹配的行。这是一种熟悉的反连接模式;一旦你的大脑围绕着这个概念,它就会成为你的第二天性。我解释了我的答案。 +1。好答案。反连接模式(通常)是最有效的方法;还有其他一些替代方案,它们可能更容易理解,但通常表现不佳。【参考方案3】:

尝试类似的方法

数数

 SELECT Links.linkId, Count(*)
    FROM Link
    LEFT JOIN Options ON Links.optionId = Options.optionId
    Where Options.optionId IS NULL
    Group by Links.linkId

查看线条

SELECT Links.linkId
    FROM Link
    LEFT JOIN Options ON Links.optionId = Options.optionId
    Where Options.optionId IS NULL

【讨论】:

以上是关于SQL JOIN 查询返回我们在连接表中没有找到匹配项的行的主要内容,如果未能解决你的问题,请参考以下文章

sql server 表连接

SQL学习多表关联-join

SQL连接(join)

left joinright join和inner join

left joinright join和inner join

SQL JOIN 数据库表关联关系