2012-02-22 34 views
3

私は、「Songs」、「Songs_Tags」(タグ付きの曲)、「Songs_Votes」(ブーリアンのような/関連しない曲)のテーブルを持っています。GROUP BY後LEFT JOIN?

タグのGROUP_CONCAT()と好きな数(真)と嫌い(偽)の数を取得する必要があります。

私のクエリはそのようなものです:

SELECT 
    s.*, 
    GROUP_CONCAT(st.id_tag) AS tags_ids, 
    COUNT(CASE WHEN v.vote=1 THEN 1 ELSE NULL END) as votesUp, 
    COUNT(CASE WHEN v.vote=0 THEN 1 ELSE NULL END) as votesDown, 
FROM Songs s 
    LEFT JOIN Songs_Tags st ON (s.id = st.id_song) 
    LEFT JOIN Votes v ON (s.id=v.id_song) 
GROUP BY s.id 
ORDER BY id DESC 

問題は、私はCOUNT()を実行するときに、それはより多くを返し、ソングが1個の以上のタグを持っているとき、それはもっとそして一度返されるということです結果。

私が考えることができる最良の解決策は、GROUP BYの後に最後のLEFT JOINを実行することが可能な場合です(今では各曲に1つのエントリしか存在しません)。それから、別のGROUP BY m.idが必要です。

これを達成する方法はありますか?サブクエリを使用する必要がありますか?

+0

を使用して

編集追加ソリューションは、あなたの投票表にはPKを持っていますか? –

+0

はい、(id_song、id_vote) – Lem0n

答えて

5

は、これまでいくつかの良い答えが存在してきたが、私はあなたがもともと

SELECT 
    songsWithTags.*, 
    COALESCE(SUM(v.vote),0) AS votesUp, 
    COALESCE(SUM(1-v.vote),0) AS votesDown 
FROM (
    SELECT 
     s.*, 
     COLLATE(GROUP_CONCAT(st.id_tag),'') AS tags_ids 
    FROM Songs s 
    LEFT JOIN Songs_Tags st 
     ON st.id_song = s.id 
    GROUP BY s.id 
) AS songsWithTags 
LEFT JOIN Votes v 
ON songsWithTags.id = v.id_song 

GROUP BY songsWithTags.id DESC 
説明するものと非常によく似てわずかに異なる方法を採用します

このサブクエリでは、タグを使用してソングを1行1行に照合する役割があります。その後、これはVotesに結合されます。私はまたあなたがそれが1または0であると指示したのでv.votes列を単純に合計することを選んだので、SUM(v.votes)は5のうち1 + 1 + 1 + 0 + 0 = SUM(1-v.vote)は5のうち0 + 0 + 0 + 1 + 1 = 2の合計がダウンボックスです。

カラム(id_song、vote)を使って投票したインデックスがある場合は、そのインデックスがこのために使用されるため、テーブルにもヒットしません。同様に、(id_song、id_tag)を持つSongs_Tagsのインデックスがある場合、そのテーブルはクエリによってヒットされません。数

SELECT 
    songsWithTags.*, 
    COUNT(CASE WHEN v.vote=1 THEN 1 END) as votesUp, 
    COUNT(CASE WHEN v.vote=0 THEN 1 END) as votesDown 
FROM (
    SELECT 
     s.*, 
     COLLATE(GROUP_CONCAT(st.id_tag),'') AS tags_ids 
    FROM Songs s 
    LEFT JOIN Songs_Tags st 
     ON st.id_song = s.id 
    GROUP BY s.id 
) AS songsWithTags 
LEFT JOIN Votes v 
ON songsWithTags.id = v.id_song 

GROUP BY songsWithTags.id DESC 
+0

私はこのソリューションが好きです。票を集めていますが、私は意味的な意味合いがあるので、SUM()の代わりにCOUNT()を使うことにします。(結局upvotesとdownvotesを数えています) – Lem0n

+0

COUNT )オプション –

2

この試してみてください:あなたは2をやっているので、ミニ直積で

SELECT 
    s.*, 
    GROUP_CONCAT(DISTINCT st.id_tag) AS tags_ids, 
    COUNT(DISTINCT CASE WHEN v.vote=1 THEN id_vote ELSE NULL END) AS votesUp, 
    COUNT(DISTINCT CASE WHEN v.vote=0 THEN id_vote ELSE NULL END) AS votesDown 
FROM Songs s 
    LEFT JOIN Songs_Tags st ON (s.id = st.id_song) 
    LEFT JOIN Votes v ON (s.id=v.id_song) 
GROUP BY s.id 
ORDER BY id DESC 
+0

これは最善の解決策ではないかと思いますが、私はそれが(明らかに)最小限の変更/再設計の問題を解決するのが好きです。 – Lem0n

+0

実際には解決しません。より多くの人々が「好き」(真実)に投票すると、多くとも1つのようにカウントされます。 – Lem0n

+0

Lem0n:私のテストではうまくいきましたが、少し異なったデータ構造があります。カウントはid_voteであり、1 ... –

2

あなたのコードの結果は、1-to-many関係に参加し、1テーブルは参加し、両方の同じ側にあります。

グループで2つのサブクエリに変換してから参加:

SELECT 
    s.*, 
    COALESCE(st.tags_ids, '') AS tags_ids, 
    COALESCE(v.votesUp, 0) AS votesUp, 
    COALESCE(v.votesDown, 0) AS votesDown 
FROM 
     Songs AS s 
    LEFT JOIN 
     (SELECT 
       id_song, 
       GROUP_CONCAT(id_tag) AS tags_ids 
      FROM Songs_Tags 
      GROUP BY id_song 
     ) AS st 
     ON s.id = st.id_song 
    LEFT JOIN 
     (SELECT 
       id_song, 
       COUNT(CASE WHEN v.vote=1 THEN id_vote END) AS votesUp, 
       COUNT(CASE WHEN v.vote=0 THEN id_vote END) AS votesDown 
      FROM Votes 
      GROUP BY id_song 
     ) AS v 
     ON s.id = v.id_song 
ORDER BY s.id DESC 
+0

は3回SELECTを実行していませんか?同じ曲のタグがたくさんある場合は、このコードが高速になりますか? – Lem0n

+1

これがもっと速いと言えば、私を信用できますか?あなたのデータとディストリビューション、サーバーとその設定、さまざまなテーブルサイズでテストしてください(正しい結果を返すすべてのクエリ)。 –