我简化了多对多关系案例 这些模型表。
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社区。 p>
答案 0 :(得分:3)
我能想到的最简洁(可能很快):
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
<强>更新强>
有一个比这更优化的解决方案,请参见此处:https://stackoverflow.com/a/10471529
答案 1 :(得分:3)
我的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 (又名boolean = true)。我们可以选择该组并保留该组下的所有标签,例如,标签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
。现在它适用于所有数据库: - )
我最终没有通过窗口函数解决它,上面的解决方案现在适用于所有数据库: - )
答案 2 :(得分:2)
为所有标记添加单独的内部联接
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%'
答案 3 :(得分:1)
试试这个:
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
答案 4 :(得分:1)
另一种方法是围绕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)