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

Posted

技术标签:

【中文标题】在 MySQL 中使用 LEFT JOINing 多个表搜索多个值【英文标题】:search for multiple values with LEFT JOINing several tables in MySQL 【发布时间】:2016-07-14 17:03:25 【问题描述】:

我在尝试使用 LEFT JOINing 多个表并使用可能的额外参数进行“全部搜索”来搜索 mysql 中的多个值时遇到了一个大问题。请允许我详细说明一个示例:

我有以下数据库结构:

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) “所有搜索”输入。搜索任何post_name 或任何meta_value。 2)过滤器输入选择meta_key并输入meta_value

例如在power = '5' 上搜索“剑”过滤器

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 都会导致不同的行。 因此,如果我使用多个过滤器(在多个元值上)搜索内容,我将一无所获。

例如在所有搜索中搜索old + 过滤range = 1

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 保持不变?有什么好的方法可以做到这一点吗?

(等到你听到我添加带有标签的第三个表并将其添加到我的所有搜索框的问题......)

【问题讨论】:

当您使用多个过滤器时,您希望得到一行?还是每个过滤器一行? 我想保留GROUP BY p.id,所以我总是想要 1 个结果。不管有多少过滤器。 不确定我是否在关注,你说i get nothing if i.... 如果你是什么?发布一个您一无所获的示例,以及您期望获得的实际数据。 用示例查询更新了问题。 是的,我明白了,现在,您希望得到哪一行? id 4 还是 id 5?还是组合?.. 【参考方案1】:

您可以使用两个 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')

【讨论】:

在 MySQL 指南中,他们在“现有查询”中使用SELECT *,你为什么要使用SELECT 1 @mesqueeb 存在子查询的选择列表中的内容无关紧要。如果您查看我的解决方案,您会发现我也使用了 1 而不是 *。 @mesqueeb "MySQL 在这样的子查询中忽略了 SELECT 列表,所以没有区别"【参考方案2】:

我会使用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

但请注意,您的查询取决于未设置的完整 group by sql 模式。新版本的 mysql 现在默认开启这个 sql 模式。

【讨论】:

“但请注意,您的查询取决于未设置的完整组按 sql 模式。现在,在较新版本的 mysql 中,此 sql 模式现在默认开启。” 如果设置了这个sql模式,你的查询会产生语法错误,因为它不符合sql标准。 设置了sql模式是什么意思? My SQL 模式未设置。所以在这种情况下这是一件好事,对吧? 为什么不直接使用DISTINCT 而不是GROUP BY【参考方案3】:

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

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_name LIKE '%old%' 选择帖子行,如果不是 - 从wp_postmeta 中搜索所有相关行以查找meta_value LIKE '%old%'。如果找到一行 - 跳过搜索(因为DISTINCT)并选择帖子行。如果不跳过帖子行。

它甚至可能表现更好,因为您跳过了所有根本没有“范围”的帖子。

【讨论】:

以上是关于在 MySQL 中使用 LEFT JOINing 多个表搜索多个值的主要内容,如果未能解决你的问题,请参考以下文章

MySQL JOINING 同一张表在 SUM() 上返回错误结果 [重复]

Hibernate 在第一次 fetch-joining 然后 simple-joining 时不生成 JOIN

join和left join的区别

在 WHERE/Joining 3 个表中进行子查询,2 个用于记录,1 个用于数字,不返回结果/失败 - MSAccess

当我使用 servletRequest.getReader().lines().collect(Collectors.joining()) 时请求更改正文

JOINing多对多表时避免SQL查询中的重复行