SQL JOIN查询返回我们在联接表中找不到匹配项的行

twi*_*xel 6 mysql sql join

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

我正在尝试做什么(但努力创建逻辑)是连接两个表,只返回表中没有选项链接的行links,因此表示哪些选项仍可用于添加到产品.

是否有SQL的功能可以帮助我?我对SQL还没有太大的经验.

spe*_*593 18

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

如果此查询返回id链接到特定"产品"的"选项" 的值...

SELECT k.option_id
  FROM links k
 WHERE k.product_id = 'foo'
Run Code Online (Sandbox Code Playgroud)

然后这个查询将获得与"产品"相关的所有选项的详细信息

SELECT o.id
     , o.name
  FROM options o
  JOIN links k
    ON k.option_id = o.id
 WHERE k.product_id = 'foo'
Run Code Online (Sandbox Code Playgroud)

请注意,我们实际上可以将"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'
Run Code Online (Sandbox Code Playgroud)

(不,这使得这里的任何区别,但它如果我们使用OUTER JOIN(WHERE子句中有所作为,这将否定的加入了"外部性",并使其等效于INNER JOIN. )

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

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

最有效的方法是(通常)反连接模式.

那是什么,我们将从"选项"获取所有行,以及"链接"中的任何匹配行(对于特定的product_id,在您的情况下).该结果集将包含"选项"中的行,这些行在"链接"中没有匹配的行.

"技巧"是过滤掉在"链接"中找到匹配行的所有行.这将使我们留下没有匹配的行.

我们过滤这些行的方式,我们在WHERE子句中使用谓词检查是否找到匹配.我们通过检查一个我们知道确定的列,如果找到匹配的行,那么它将是非NULL.而我们知道*肯定该列将是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
Run Code Online (Sandbox Code Playgroud)

"LEFT"关键字指定的"外部"联接操作,我们从"选项"中的所有行(该表上的"左"侧的JOIN),即使没有找到匹配的行.(正常的内连接会过滤掉没有匹配的行.)

"技巧"在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'
                  )
Run Code Online (Sandbox Code Playgroud)

那就是从选项表中获取所有行.但是对于每一行,运行查询,并查看链接表中是否存在匹配的行"存在".(在选择列表中返回的内容无关紧要,我们只测试它是否至少返回一行...我在选择列表中使用"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
                   )
Run Code Online (Sandbox Code Playgroud)

该子查询(在parens中)获取与产品关联的所有option_id值的列表.

现在,对于选项中的每一行(在外部查询中),我们可以检查id值以查看它是否在子查询返回的列表中.

如果我们保证option_id永远不会为NULL,我们可以省略测试的谓词"option_id IS NOT NULL".(在更一般的情况下,当NULL爬进结果集时,外部查询无法判断o.id是否在列表中,并且查询不返回任何行;所以我通常包括它,即使它不是必需的.也不GROUP BY是绝对必要的;特别是如果(product_id,option_id)元组有唯一约束(保证唯一性).

但是,NOT IN (subquery)除了测试之外,不要再使用它,除非有一些令人信服的理由(例如,它设法比反连接更好).

您不太可能注意到与小集合的任何性能差异,传输语句,解析它,生成访问计划以及返回结果的开销使计划的实际"执行"时间相形见绌.随着更大的集合,"执行"时间的差异变得明显.

EXPLAIN SELECT ... 是一个非常好的方法来处理执行计划,看看MySQL在你的声明中做了什么.

适当的索引,特别是覆盖索引,可以显着提高某些语句的性能.

  • 精湛的答案.我必须阅读几次才能完全理解我们在反连接方面做了什么,但现在它已经完全合理了.这就是我对Overflow的回答 - 不仅仅是"这里是如何修复它",而是"这就是为什么我们这样做来修复它".非常翔实.我几乎准备好用PHP中的几个foreach循环来处理性能,以处理过滤,非常感谢! (2认同)
  • @twistedpixel:虽然有时可以迅速获得快速解决方案,但我认为了解我们如何处理问题以及下次我们的"工具带"中的"工具"获得一些经验时更为重要遇到类似的问题.(我的答案有时因为过于冗长而被投票否决;我向"试试这个"爱好者道歉. (2认同)