[英]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
就像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
。 現在它適用於所有數據庫:-)
我最終沒有通過窗口函數解決它,上面的解決方案現在適用於所有數據庫:-)
為所有標簽添加單獨的內部聯接
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
另一種方法是使用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.