2012-05-06 5 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句のテーブルに加わることですが、1つのクエリで(またはサブクエリで)この問題をどのように解決できますか?

現在、私はアプリケーション内の2つの別々のクエリで、一致する投稿と完全な投稿データを取得している別のクエリを選択しています。これはあまり効率的ではなく、不自由な解決策のようにも思えますが、私はまだ見つけていないので、StackOverflowコミュニティに尋ねることにしました。個別の内部に入れて

答えて

3

、ここで最短です1:

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)が表示されます。

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; 
同じポストIDに属しているすべてのタグは、私たちが WHEREフィルタを使用しないように、我々は代わりに HAVINGフィルタを使用することになり、破棄されることはありません

BIT_ORそれはSUM

出力で物事を評価するよりもセマンティックですので、IN、またはANYのようなものです:

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

ライブテスト:http://www.sqlfiddle.com/#!2/52b3b/26


私はstackoverflowの上そんなに学びました。私の古い答えの後、私はSUM OVER partition経由でウィンドウ機能(これはMySQLが持っていない)を使ってPostgresqlで同等の短いコードを作る方法を考えています。 Postgresqlのbool_or,bool_andeveryの機能について考えました。それから私は、私はbit_orはただの意味であることを思いついたとき、MySQLは、最後の解決策は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のフィドル:、あなたは1行で倒れたタグが必要な場合は

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; 

:私は考えることができる(速いかもしれません)http://sqlfiddle.com/#!2/2f698/9

+2

クエリから2行が出力されます。しかし、すべてのタグを取得するわけではありません。 OPの問題と同様です。あなたの質問は次のとおりです。http://www.sqlfiddle.com/#!2/788af/5 –

+0

Er、そうです。それを指摘してくれてありがとう。 Upvoted your answer :) – Daan

+1

についてhttp://www.sqlfiddle.com/ DDLステートメントを手作りしないでください。 OPのデータを強調表示し、コピーしてsqlfiddleのText to DDLに貼り付けてください。これは素晴らしい機能です。ちょっと発見的で、csvで区切られたものも扱えます。スペース区切りも機能しますが、データにはスペースがあってはなりません –

3

最も簡潔な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

これ以上最適化されたソリューションがあります、こちらをご覧ください:マイold answerが最短ではない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、2 t_id)。デカルト積の半分にWHERE句を適用すると、探している "xを含む集合のすべてのメンバー"が得られます。 (上記の例では、タグID 1に関連付けられた投稿のみが検索され、さらにこれらの投稿に関連付けられたタグもすべて表示されていることがわかりました)。次に、p_idとt_idに関連付けられた情報を取得する2つの簡単な結合を示します。

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

Iこの答えをアップヴォートにする。同じ結果、異なるロジック。 – dropout

関連する問題