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