使用 INNER JOIN 排除 MYSQL 个查询结果
Excluding MYSQL query results with an INNER JOIN
我有两个 table。第一个是满满的书,每本书都有 book_id
。第二个 table 是 book_id
到 keyword_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)
你要的是"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
子句中的相关子查询的方法执行得更好。但是,如果性能很重要,那么建议您测试您正在考虑的替代方案。
我有两个 table。第一个是满满的书,每本书都有 book_id
。第二个 table 是 book_id
到 keyword_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)
你要的是"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
子句中的相关子查询的方法执行得更好。但是,如果性能很重要,那么建议您测试您正在考虑的替代方案。