【问题标题】:LEFT JOIN after GROUP BY?在 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吗?

标签: mysql join group-by group-concat


【解决方案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 个查询快得多! :)
猜你喜欢
  • 2015-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-07
  • 2019-01-15
  • 2020-12-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多