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

标签 mysql sql join

更多的理论/逻辑问题,但我有两个表:linksoptions .链接是一个表,我在其中添加了代表产品 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/

相关文章:

php - 从 mysql select 语句获取计数然后解析为 JSON

MySql 按同一个表中的多个条目进行过滤

java - 从 ResultSet Java 获取主键列

mysql - 从嵌套规则生成器生成 MySQL 查询

apache-spark - Spark : Prevent shuffle/exchange when joining two identically partitioned dataframes

MySQL:解析和转换包含带单位数字的字符串

c# - "Error in your SQL syntax"创建新表时

c# - Linq orderby计算

mysql - 在 SQL 中跨多个连接表记录查找最近匹配的最有效方法

php - 将左连接限制为添加表中的一条记录