在 GROUP BY 后左加入?

Posted

技术标签:

【中文标题】在 GROUP BY 后左加入?【英文标题】:LEFT JOIN after GROUP BY? 【发布时间】:2012-03-12 12:54:31 【问题描述】:

我有一个“Songs”表,“Songs_Tags”(将歌曲与标签相关联)和“Songs_Votes”(将歌曲与布尔喜欢/不喜欢相关联)。

我需要检索带有 GROUP_CONCAT() 标记的歌曲以及喜欢 (true) 和不喜欢 (false) 的数量。

我的查询是这样的:

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。

有没有办法做到这一点?我需要使用子查询吗?

【问题讨论】:

你的投票表有PK吗? 【参考方案1】:

到目前为止,已经有一些很好的答案,但我会采用与您最初描述的方法略有不同的方法

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 = 5 个中有 3 个是赞成票,而 SUM(1-v.vote) 将求和 0+0+0+1+1 = 5 个中有 2 个是反对票。

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

edit使用计数添加解决方案

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

【讨论】:

我喜欢这个解决方案,特别是它不会为标签或投票打到数据库的事实......但我只会坚持使用 COUNT() 而不是 SUM() 因为语义上它更有意义 IMO(毕竟,我正在计算赞成票和反对票)【参考方案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

【讨论】:

我不确定这是不是最好的解决方案,但我喜欢它通过(显然)最少的更改/重新设计来解决问题。 其实并没有解决。如果更多人投票“赞”(真),最多算作一个赞。 Lem0n:它在我的测试中有效,但我的数据结构可能与您略有不同。请注意,计数是 id_vote,而不是 1... 啊,有道理。但 id_vote 实际上只是一个布尔值真/假(是的,坏名声)。也许我可以像 (id_song, id_user, vote) 这样计算整行?【参考方案3】:

您的代码会产生一个迷你笛卡尔积,因为您在 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

【讨论】:

执行 3 次 SELECT 不是更慢吗?或者当我为同一首歌有很多标签时,这段代码可能会更快? 如果我说这更快,你会相信我吗?使用您的数据和分布、您的服务器及其设置以及各种表大小进行测试(所有给出正确结果的查询),然后选择:) @Lem0n 100 个超快查询肯定比 1 个查询快得多! :)

以上是关于在 GROUP BY 后左加入?的主要内容,如果未能解决你的问题,请参考以下文章

Hive Group by 后自我加入

我们如何使用 GROUP BY 加入结果

左加入 Group By

如何通过自我加入使用 group by

在 MySQL 中使用 Case 加入 Group By & Order

在 group by 中获取具有正确报价 ID 的最高报价并加入查询