简体   繁体   English

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

[英]How can I query a many-to-many join with filtering on the same relation?

I simplified a many-to-many relationship case with these mockup tables. 我简化了这些模型表的多对多关系案例。

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 |
------------------------------

My goal is to query POSTS with specific TAGS, which I have no problem with, but I also want to display all related tags to the post not just the one I queried for. 我的目标是使用特定的TAGS查询POSTS,我没有遇到任何问题,但我也希望向帖子显示所有相关标签,而不仅仅是我查询的标签。 My query looks like this: 我的查询如下所示:

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%'

This gets the posts with the "SQL" tag, but it only joins the posts table with tags where it found the "SQL" string, so the other tag "PHP" associated with the post doesn't get joined. 这将获得带有“SQL”标记的帖子,但它只将posts表与标签结合在一起,找到“SQL”字符串,因此与帖子关联的其他标签“PHP”不会加入。

Obviously the problem is I'm joining the table on the WHERE clause, but how would one solve this in one query or (preferably with subqueries)? 显然问题是我在WHERE子句上加入表,但是如何在一个查询或(最好是子查询)中解决这个问题?

Currently I'm doing this in two separate queries in my application, one for selecting matching posts and another one that is retreiving full post data. 目前我在我的应用程序中的两个单独查询中执行此操作,一个用于选择匹配的帖子,另一个用于检索完整的帖子数据。 This isn't so efficient and also seems like a lame solution, and I haven't found a better yet, so I decided to ask the StackOverflow community. 这不是那么有效,也似乎是一个蹩脚的解决方案,我还没有找到更好的,所以我决定问StackOverflow社区。

The most concise (might be fast) I can think of: 我能想到的最简洁(可能很快):

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;

If you need the tags collapsed in one line, use GROUP_CONCAT: 如果您需要在一行中折叠标记,请使用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;

Output: 输出:

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

Live test: http://www.sqlfiddle.com/#!2/52b3b/2 实时测试: http//www.sqlfiddle.com/#!2/52b3b/2


UPDATE UPDATE

There's a solution more optimized than this, see here: https://stackoverflow.com/a/10471529 有一个比这更优化的解决方案,请参见此处: https//stackoverflow.com/a/10471529

My old answer is not the shortest, here's the shortest one: 我的旧答案不是最短的,这是最短的答案

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;

Output: 输出:

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

So if we group by ID, and check that if at least one (aided by bit_or; Postgresql has this too, aptly named bool_or) of the elements in the group satisfied the '%SQL%' criteria, its bit is ON (aka boolean = true). 因此,如果我们按ID进行分组,并检查是否至少有一个(由bit_or帮助; Postgresql也有这个,恰当地命名为bool_or)组中的元素满足'%SQL%'条件,它的位是ON(也就是布尔值) =真)。 We can pick that group and we retain all the tags under that group, example, tag id 1 appear on post 1, and post 1 has other tags, which is #3 or PHP. 我们可以选择该组并保留该组下的所有标签,例如,标签ID 1出现在帖子1上,而帖子1有其他标签,即#3或PHP。 All tags that belong to same Post ID will not be discarded, as we will not be using WHERE filter, we will be using HAVING filter instead: 所有属于同一帖子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%');

We can also rewrite that to this: 我们也可以改写这个:

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 is like IN , or ANY , so it's more semantic than evaluating things by SUM BIT_OR就像INANY ,因此它比通过SUM评估事物更具语义

Output: 输出:

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

Live test: http://www.sqlfiddle.com/#!2/52b3b/26 实时测试: http//www.sqlfiddle.com/#!2/52b3b/26


I'm learning so much on stackoverflow. 我在stackoverflow上学到了很多东西。 After my old answer, I'm thinking how to make an equivalent shorter code in Postgresql using windowing function(which MySQL don't have) via SUM OVER partition . 在我的旧答案之后,我正在考虑如何使用窗口函数(MySQL没有)通过SUM OVER partition在Postgresql中创建一个等效的更短代码。 Then I thought of Postgresql's bool_or , bool_and and every function. 然后我想到了Postgresql的bool_orbool_andevery函数。 Then I remember MySQL has bit_or :-) 然后我记得MySQL有bit_or :-)

The last solution using SUM is just an afterthought, when I thought up that bit_or is just a semantic of at least one is true , then it's obvious that you can use HAVING SUM(condition) >= 1 too. 使用SUM的最后一个解决方案只是一个事后的想法,当我想到bit_or只是至少一个的语义是真的时 ,那么显然你也可以使用HAVING SUM(condition) >= 1 Now it works on all database :-) 现在它适用于所有数据库:-)

I ended up not solving it by windowing function, the solution above now works on all database :-) 我最终没有通过窗口函数解决它,上面的解决方案现在适用于所有数据库:-)

Put on a separate inner join for all tags 为所有标签添加单独的内部联接

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%'

Try this: 尝试这个:

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%'

This uses GROUP_CONCAT to create a comma-separated list of tags associated with that particular post. 这使用GROUP_CONCAT创建以逗号分隔的与该特定帖子关联的标签列表。 Output for your query: 您的查询输出:

TITLE BODY   NAME   tags
One   text1  SQL    SQL,GLSL

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

Yet another way to do this is built around an inner join of posts_tags with itself: 另一种方法是使用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)

Without the WHERE clause the inner join would give the full cartesian product (t_id 1, t_id 2) of all tags associated with each post. 如果没有WHERE子句,内部联接将给出与每个帖子关联的所有标签的完整笛卡尔积(t_id 1,t_id 2)。 Applying the WHERE clause to half the cartesian product gives you the "all members of sets containing x" structure you're looking for. WHERE子句应用于笛卡尔积的一半,可以为您提供“包含x的集合的所有成员”结构。 (The example above demonstrates that only posts associated with tag id 1 have been retrieved; further, all tags associated with those posts are present as well.) Now it's two more simple joins to fetch the information associated with p_id and t_id: (上面的示例演示了只检索了与标记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