2012-05-06 55 views
2

我用这些模型表简化了多对多关系案例 。如何通过在相同关系上筛选来查询多对多联接?

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社区。

回答

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


我学习上的计算器这么多。在我的旧回答之后,我正在考虑如何通过SUM OVER partition使用窗口函数(MySQL没有)在Postgresql中创建一个等效的较短代码。然后我想到了Postgresql的bool_orbool_andevery函数。于是,我想起MySQL有bit_or :-)

使用SUM最后的解决方案只是事后的想法,当我想到我们bit_or只是语义的至少有一个为真,那么很明显,你可以使用HAVING SUM(condition) >= 1太。现在,它的工作原理所有数据库:-)

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

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

感谢您的时间回答我的问题,您的解决方案解决了这个问题,但我接受@Micheal的,因为它提供了更多的信息。 – dropout

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 

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

+2

您的查询输出两行。但它没有得到所有的标签。类似于OP的问题。这是你的查询http://www.sqlfiddle.com/#!2/788af/5 –

+0

呃,你是对的。感谢您指出了这一点。 Upvoted你的答案:) – Daan

+1

关于http://www.sqlfiddle.com/不要手工制作你的DDL语句。尝试突出显示OP的数据,将其复制并粘贴到sqlfiddle的Text to DDL。这是一个漂亮的功能ツ这有点启发式,也可以处理CSV分隔;空格分隔也会起作用,但数据不得有空格 –

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


UPDATE

还有比这更优化的解决方案,在这里看到:https://stackoverflow.com/a/10471529

+0

谢谢,另一种方式(与额外的内部连接字段)也适用,但这似乎是一个更优雅的解决方案给我。 – dropout

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) 
+0

针对史蒂夫乔布斯,我试图吞噬我的[旧答案](http://stackoverflow.com/a/10470671)。我想出了一个[更优化的解决方案](http://stackoverflow.com/a/10471529):-)查看我的新优化解决方案:-)笛卡尔产品几乎不需要这种类型的问题;-) –

+0

我给这个答复upvote。相同的结果,不同的逻辑。 – dropout

相关问题