在 MySQL 中使用 LEFT JOINing 多个表搜索多个值

search for multiple values with LEFT JOINing several tables in MySQL

我在尝试通过 LEFT JOINing 多个 table 搜索 MySQL 中的多个值时遇到了一个大问题,并使用可能的额外参数进行过滤 "all search"。请允许我详细说明一个示例案例:

我有以下数据库结构:

wp_posts
id | post_name
--------------
1  | sword
2  | bow
3  | shield

wp_postmeta
id  | post_id   | meta_key  | meta_value
------------------------------------
1   |   1       |   user    | 101
2   |   1       |   power   | 5
3   |   1       |   defense | 0
4   |   1       |   range   | 1
5   |   1       |   condi   | old
6   |   2       |   user    | 102
7   |   2       |   range   | 10
8   |   2       |   condi   | new
9   |   3       |   user    | 101
10  |   3       |   power   | 0
11  |   3       |   defense | 10

我正在使用以下内容构建 PHP MySQL 搜索:

1) "all search" 输入。搜索任何 post_name 或任何 meta_value。 2) 过滤器输入选择meta_key并输入meta_value

例如在 power = '5'

上搜索 "sword" 过滤器
SELECT *
FROM wp_posts as p
LEFT JOIN wp_postmeta as pm ON p.id = pm.post_id
WHERE (post_name LIKE '%$search_str%' OR meta_value LIKE '%$search_str%')
/* extra filter */
AND (meta_key = '$filter_key' AND meta_value = '$filter_val')
GROUP BY p.id

我的问题:

我总是希望 wp_posts 中的每条记录有 1 个结果。 但是由于我的 LEFT JOIN,每个 post_keypost_value 都会产生不同的行。
因此,如果我使用多个过滤器(在多个 meta_values 上)搜索内容,我将一无所获。

例如在 range = 1.

上的所有搜索 + 过滤器中搜索 old
SELECT * FROM wp_posts as p
LEFT JOIN wp_postmeta as pm ON p.id = pm.post_id
WHERE (post_name LIKE '%old%' OR meta_value LIKE '%old%')
AND (meta_key = 'range' AND meta_value LIKE '%1%')
GROUP BY p.id

→‖0 个结果...

所以我知道原因,但我不知道好的解决方法...
我知道我可以组 concat 列,但是我不能保持 meta_key 和 meta_value 的设置对吗?关于执行此操作的好方法有什么想法吗?

(等到你听到我正在添加带有标签的第三个 table 并将其添加到我的全搜索框......)

我会使用 exists subquery 来解决这个问题:

SELECT * FROM wp_posts as p
LEFT JOIN wp_postmeta as pm ON p.id = pm.post_id
WHERE (post_name LIKE '%old%' OR meta_value LIKE '%old%')
AND EXISTS (SELECT 1 FROM wp_postmeta pm2 WHERE pm2.meta_key = 'range' AND pm2.meta_value LIKE '%1%' and pm2.post_id=pm.post_id)
GROUP BY p.id

但请注意,您的查询取决于未设置 sql 模式的完整组。 sql 模式现在在 mysql 的较新版本中默认启用。

你可以用两个 EXISTS() 条件来做到这一点:

SELECT * FROM wp_posts p
WHERE (post_name like '%old%'
       or EXISTS(SELECT 1 FROM wp_postmeta p2 WHERE p.id = p2.id AND p2.meta_value LIKE '%OLD%'))
  AND EXISTS(SELECT 1 FROM wp_postmeta p3 WHERE p.id = p3.id AND p3.meta_value LIKE '%1%' AND p3.meta_key = 'range')

另一个解决方案,它(恕我直言)更接近您的原始查询:

SELECT DISTINCT * FROM wp_posts as p
INNER JOIN wp_postmeta as pm1 ON p.id = pm1.post_id
LEFT  JOIN wp_postmeta as pm  ON p.id = pm.post_id
WHERE (p.post_name LIKE '%old%' OR pm.meta_value LIKE '%old%')
AND   (pm1.meta_key = 'range' AND pm1.meta_value LIKE '%1%')
-- GROUP BY p.id

我使用另一个 (INNER) JOIN 按必备条件 范围 LIKE '%1%' 进行过滤。我不确定它会如何表现,但找不到原因,为什么它会表现更差。

我希望 MySQL 首先在 pm1 中搜索 meta_key = 'range'(应该被索引),看看是否 meta_value LIKE '%1%',如果是 - 找到相关的 post.如果 post_name LIKE '%old%' select post 行,如果不是 - 在 wp_postmeta 的所有相关行中搜索 meta_value LIKE '%old%'。如果找到一行 - 跳过搜索(因为 DISTINCT)和 select post 行。如果没有跳过 post 行。

它甚至可能表现得更好,因为您跳过了所有根本没有 "range" 的 post。