使用 INNER JOIN 排除 MYSQL 个查询结果

Excluding MYSQL query results with an INNER JOIN

我有两个 table。第一个是满满的书,每本书都有 book_id。第二个 table 是 book_idkeyword_id 的关系 table.

SELECT b.* FROM books_table b 
INNER JOIN keywords_table k 
ON b.book_id = k.book_id AND k.keyword_id NOT IN(1,2,3)
WHERE b.is_hardcover = 1 
GROUP BY b.book_id

期望的结果

没有任何书籍附有 keyword_id 1、2 或 3。

实际结果

图书可以包含关键字 1、2 或 3,只要它们附加了 keyword_id 并且 不在 排除列表中。

我试过的

上面的查询是我最接近实现它的查询,但是在这方面它失败了。

我怎样才能以最优化的方式达到预期的结果?

你可以这样做

SELECT b.* 
FROM books_table b 
INNER JOIN keywords_table k 
ON b.book_id = k.book_id
WHERE b.is_hardcover = 1 
GROUP BY b.book_id
HAVING SUM(k.keyword_id = 1) =0
AND SUM(k.keyword_id = 2) =0
AND SUM(k.keyword_id = 3) =0

如您所述,此查询将生成至少有一个关键字不是 1、2 或 3 的任何书籍,这不是您想要的。相反,您希望明确排除包含这些关键字的图书。 join 并不是真正适合这里的工作。相反,您可以使用 exists 运算符:

SELECT b.* 
FROM   books_table b 
WHERE  b.is_hardcover = 1 AND
       NOT EXISTS (SELECT * 
                   FROM   keywords_table k 
                   WHERE  b.book_id = k.book_id AND 
                          k.keyword_id IN (1,2,3))

您可以使用以下查询:

SELECT *
FROM books_table
WHERE is_hardcover = 1 AND
      book_id NOT IN (SELECT book_id
                      FROM keywords_table
                      GROUP BY book_id
                      HAVING COUNT(CASE WHEN keyword_id IN (1,2,3) THEN 1 END) <> 0)

Demo here

你要的是"anti join"的味道。有几种方法可以实现它;这是一个:

SELECT b.* FROM books_table b 
LEFT JOIN keywords_table k 
  ON b.book_id = k.book_id AND k.keyword_id IN (1,2,3)
WHERE k.book_id IS NULL AND b.is_hardcover = 1 

左连接将左边 table (books_table) 的每一行与右边 table 满足条件 b.book_id = k.book_id AND k.keyword_id IN (1,2,3) 的行相匹配,and 为左侧 table 的每一行包含一个结果行,该行与右侧 table 的任何行都不匹配。过滤条件k.book_id IS NULL与join条件冲突,所以只能满足左边行不匹配右边行的行。

请注意,连接谓词和过滤谓词的条件分配对于外部连接(例如这个连接)至关重要。另请注意,在这种情况下不需要 GROUP BY 子句,除非 books_table 可能包含重复的 book_id

这种方法在实践中可能比基于 WHERE 子句中的相关子查询的方法执行得更好。但是,如果性能很重要,那么建议您测试您正在考虑的替代方案。