在GROUP BY之后左转加入?

时间:2012-02-22 07:26:01

标签: mysql join group-by group-concat

我有一张“Songs”,“Songs_Tags”(与歌曲相关的歌曲)和“Songs_Votes”(与布尔喜欢/不喜欢的歌曲相关)的表格。

我需要使用其标签的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

问题是,当一首歌有超过1个标签时,它会被多次返回,所以当我执行COUNT()时,它会返回更多结果。

我能想到的最好的解决方案是,在GROUP BY之后是否可以进行最后的LEFT JOIN(所以现在每首歌只有一个条目)。然后我需要另一个GROUP BY m.id。

有没有办法实现这一目标?我需要使用子查询吗?

3 个答案:

答案 0 :(得分: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行。然后将其加入投票。我还选择简单地总结v.votes列,因为你已经指出它是1或0,因此SUM(v.votes)将加起来1 + 1 + 1 + 0 + 0 = 3中的3个是upvotes,而SUM(1-v.vote)将总和0 + 0 + 0 + 1 + 1 = 2中的5个是downvotes。

如果你有一个带有列(id_song,vote)的投票索引,那么该索引将用于此,所以它甚至不会命中表。同样,如果您在Songs_Tags上有一个带有(id_song,id_tag)的索引,那么该表将不会被查询命中。

编辑使用计数

添加解决方案
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

答案 1 :(得分:3)

试试这个:

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

答案 2 :(得分: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