mysql - 如何查询多对多连接并过滤相同的关系?

标签 mysql sql many-to-many

我简化了一个多对多关系的案例 使用这些模型表。

Posts:
------------------------------
|   id |    title |     body |
------------------------------
|    1 |      One |    text1 |
|    2 |      Two |    text2 |
|    3 |    Three |    text3 |
------------------------------

Tags:
-------------------
|   id |     name |
-------------------
|    1 |      SQL |
|    2 |     GLSL |
|    3 |      PHP |
-------------------

Post_tags:
------------------------------
|   id |    p_id |      t_id |
------------------------------
|    1 |       1 |         1 |
|    2 |       1 |         3 |
|    3 |       2 |         1 |
|    3 |       3 |         2 |
------------------------------

我的目标是查询带有特定标签的帖子,我对此没有问题,但我也想显示帖子的所有相关标签,而不仅仅是我查询的标签。 我的查询如下所示:

SELECT p.Title, p.Body, t.name
FROM Posts p
LEFT JOIN Post_tags pt ON p.id = pt.p_id
LEFT JOIN Tags t ON t.id = pt.t_id
WHERE t.name LIKE '%SQL%'

这会获取带有“SQL”标签的帖子,但它只会在找到“SQL”字符串的位置加入带有标签的帖子表,因此不会加入与帖子关联的其他标签“PHP”。

显然问题是我在 WHERE 子句上连接表,但是如何在一个查询或(最好使用子查询)中解决这个问题?

目前,我在我的应用程序中的两个独立查询中执行此操作,一个用于选择匹配的帖子,另一个用于检索完整的帖子数据。这不是那么有效,而且似乎是一个蹩脚的解决方案,而且我还没有找到更好的解决方案,所以我决定询问 StackOverflow 社区。

最佳答案

我的 old answer不是最短的,这是最短的:

select p.*, '' as x, t.name, t.name like '%SQL%'
from Posts p
join Posts_tags pt on pt.p_id = p.id 
join Tags t on t.id = pt.t_id;

输出:

ID  TITLE   BODY    X       NAME    T.NAME LIKE '%SQL%'
1   One     text1           SQL     1
1   One     text1           PHP     0
2   Two     text2           SQL     1
3   Three   text3           GLSL    0

因此,如果我们按 ID 分组,并检查组中的元素是否至少有一个(借助于 bit_or;Postgresql 也有这个,恰本地命名为 bool_or)满足 '%SQL%' 条件,它的位为 ON (又名 bool 值 = 真)。我们可以选择该组并保留该组下的所有标签,例如,标签 id 1 出现在帖子 1 上,而帖子 1 有其他标签,即 #3 或 PHP。属于同一帖子 ID 的所有标签都不会被丢弃,因为我们不会使用 WHERE 过滤器,我们将使用 HAVING 过滤器:

select p.*, group_concat(t.name) as tags
from Posts p
join Posts_tags pt on pt.p_id = p.id 
join Tags t on t.id = pt.t_id
group by p.id
having bit_or(t.name like '%SQL%');

我们也可以将其重写为:

select p.*, group_concat(t.name) as tags
from Posts p
join Posts_tags pt on pt.p_id = p.id 
join Tags t on t.id = pt.t_id
group by p.id
having sum(t.name like '%SQL%') >= 1;

BIT_OR 类似于 INANY,因此它比通过 SUM 求值更具语义。 p>

输出:

D   TITLE   BODY    TAGS
1   One     text1   PHP,SQL
2   Two     text2   SQL

现场测试:http://www.sqlfiddle.com/#!2/52b3b/26


我在 stackoverflow 上学到了很多东西。在我的旧答案之后,我正在考虑如何通过 SUM OVER partition 使用窗口函数(MySQL 没有)在 Postgresql 中制作一个等效的较短代码。然后想到了Postgresql的bool_or,bool_andevery函数。然后我记得 MySQL 有 bit_or :-)

最后一个使用 SUM 的解决方案只是事后的想法,当我想到 bit_or 只是 至少一个为真 的语义时,那么显然您也可以使用 HAVING SUM(condition) >= 1。现在它适用于所有数据库:-)

我最终没有通过窗口函数解决它,上面的解决方案现在适用于所有数据库:-)

关于mysql - 如何查询多对多连接并过滤相同的关系?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10469884/

相关文章:

php - 从数据库语法错误中使用(select=true)表单填充多个选择

php - 如何在 mysql 和 PHP 中将每个数据添加到表的列中?

将逗号分隔列拆分为多对多关系的 SQL 查询

MySQL查询,多对多关系的交集

Laravel 与自定义表名称和 ID 的多对多关系

mysql - 在 mysql 中从 select 创建表时如何指定排序规则?

MySQL SELECT 到 VIEW 返回不同的结果

php - 有没有更好的方法在 mysql 和 PHP 中查询这个

sql - 获取Oracle中所有表的列表?

forms - 如何按照实体之间的多对多关系将默认值设置为连接用户之后的 symfony 表单字段(用户字段)