在 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 后左加入?的主要内容,如果未能解决你的问题,请参考以下文章