繁体   English   中英

如何通过对同一关系进行过滤来查询多对多联接?

[英]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就像INANY ,因此它比通过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_orbool_andevery函数。 然后我记得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

SQL小提琴: http ://sqlfiddle.com/#!2/ 2f698/9

另一种方法是使用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM