[英]How can I query a many-to-many join with filtering on the same relation?
我简化了这些模型表的多对多关系案例。
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 |
------------------------------
我的目标是使用特定的TAGS查询POSTS,我没有遇到任何问题,但我也希望向帖子显示所有相关标签,而不仅仅是我查询的标签。 我的查询如下所示:
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”标记的帖子,但它只将posts表与标签结合在一起,找到“SQL”字符串,因此与帖子关联的其他标签“PHP”不会加入。
显然问题是我在WHERE子句上加入表,但是如何在一个查询或(最好是子查询)中解决这个问题?
目前我在我的应用程序中的两个单独查询中执行此操作,一个用于选择匹配的帖子,另一个用于检索完整的帖子数据。 这不是那么有效,也似乎是一个蹩脚的解决方案,我还没有找到更好的,所以我决定问StackOverflow社区。
我能想到的最简洁(可能很快):
select p.*, '' as x, t.name
from Posts p
join Posts_tags pt
ON pt.p_id = p.id
AND pt.p_id in (select p_id
from Posts_tags
join Tags on Tags.id = Posts_tags.t_id
where Tags.name like '%SQL%')
join Tags t on t.id = pt.t_id;
如果您需要在一行中折叠标记,请使用GROUP_CONCAT:
select p.*, group_concat(t.name) as tags
from Posts p
join Posts_tags pt
ON pt.p_id = p.id
AND pt.p_id in (select p_id
from Posts_tags
join Tags on Tags.id = Posts_tags.t_id
where Tags.name like '%SQL%')
join Tags t on t.id = pt.t_id
group by p.id;
输出:
ID TITLE BODY TAGS
1 One text1 SQL,PHP
2 Two text2 SQL
实时测试: http : //www.sqlfiddle.com/#!2/52b3b/2
UPDATE
有一个比这更优化的解决方案,请参见此处: https : //stackoverflow.com/a/10471529
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(也就是布尔值) =真)。 我们可以选择该组并保留该组下的所有标签,例如,标签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
就像IN
或ANY
,因此它比通过SUM
评估事物更具语义
输出:
D TITLE BODY TAGS
1 One text1 PHP,SQL
2 Two text2 SQL
实时测试: http : //www.sqlfiddle.com/#!2/52b3b/26
我在stackoverflow上学到了很多东西。 在我的旧答案之后,我正在考虑如何使用窗口函数(MySQL没有)通过SUM OVER partition
在Postgresql中创建一个等效的更短代码。 然后我想到了Postgresql的bool_or
, bool_and
和every
函数。 然后我记得MySQL有bit_or
:-)
使用SUM
的最后一个解决方案只是一个事后的想法,当我想到bit_or
只是至少一个的语义是真的时 ,那么显然你也可以使用HAVING SUM(condition) >= 1
。 现在它适用于所有数据库:-)
我最终没有通过窗口函数解决它,上面的解决方案现在适用于所有数据库:-)
为所有标签添加单独的内部联接
SELECT p.Title, p.Body, t2.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
INNER JOIN Post_tags pt2 ON p.id = pt2.p_id
INNER JOIN Tags t2 on ON t2.id = pt2.t_id
WHERE t.name LIKE '%SQL%'
尝试这个:
SELECT p.Title, p.Body, t.name,GROUP_CONCAT(t2.name) AS `tags`
FROM Posts p
LEFT JOIN Post_tags pt ON p.id = pt.p_id
LEFT JOIN Tags t ON t.id = pt.t_id
JOIN Tags t2 ON t2.id = p.id
WHERE t.name LIKE '%SQL%'
这使用GROUP_CONCAT创建以逗号分隔的与该特定帖子关联的标签列表。 您的查询输出:
TITLE BODY NAME tags
One text1 SQL SQL,GLSL
另一种方法是使用posts_tags
与其自身的内部posts_tags
:
SELECT *
FROM posts_tags pt1
JOIN posts_tags pt2
USING(p_id)
WHERE pt2.t_id = 1;
+------+------+------+
| p_id | t_id | t_id |
+------+------+------+
| 1 | 1 | 1 |
| 1 | 3 | 1 |
| 1 | 4 | 1 |
| 3 | 1 | 1 |
| 3 | 2 | 1 |
| 5 | 1 | 1 |
| 5 | 3 | 1 |
| 7 | 1 | 1 |
+------+------+------+
8 rows in set (0.00 sec)
如果没有WHERE
子句,内部联接将给出与每个帖子关联的所有标签的完整笛卡尔积(t_id 1,t_id 2)。 将WHERE
子句应用于笛卡尔积的一半,可以为您提供“包含x的集合的所有成员”结构。 (上面的示例演示了只检索了与标记ID 1关联的帖子;此外,还存在与这些帖子关联的所有标记。)现在,它是两个更简单的连接,用于获取与p_id和t_id关联的信息:
SELECT title,name
FROM posts_tags pt1
JOIN posts_tags pt2
ON(pt1.p_id = pt2.p_id)
JOIN posts
ON(pt1.p_id = posts.id)
JOIN tags
ON (pt1.t_id = tags.id)
WHERE pt2.t_id = 1;
+---------+--------+
| title | name |
+---------+--------+
| first | php |
| first | skiing |
| first | tuna |
| third | php |
| third | sql |
| fifth | php |
| fifth | skiing |
| seventh | php |
+---------+--------+
8 rows in set (0.01 sec)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.