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

4

5 回答 5

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

于 2012-05-06T12:54:34.140 回答
3

我的旧答案不是最短的,这是最短的:

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。属于同一 Post 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, or ANY, 所以它比评估事物更具语义SUM

输出:

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

现场测试:http ://www.sqlfiddle.com/#!2/52b3b/26


我在stackoverflow上学到了很多东西。在我的旧答案之后,我正在考虑如何在 Postgresql 中使用窗口函数(MySQL 没有)通过SUM OVER partition. 然后我想到了 Postgresql 的bool_or,bool_andevery函数。然后我记得MySQL有bit_or:-)

使用的最后一个解决方案SUM只是事后的想法,当我想到这bit_or只是至少一个语义为 true时,很明显你也可以使用HAVING SUM(condition) >= 1。现在它适用于所有数据库:-)

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

于 2012-05-06T15:00:54.347 回答
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%'
于 2012-05-06T10:47:10.033 回答
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

于 2012-05-06T10:53:58.687 回答
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)
于 2012-05-06T15:01:31.823 回答