Il y a eu de bonnes réponses jusqu'à présent, mais j'adopterais une méthode légèrement différente assez similaire à ce que vous avez décrit à l'origine
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
Dans ce cas, la sous-requête est chargée de rassembler les chansons avec des balises en une ligne par chanson. Ceci est ensuite joint à Votes par la suite. J'ai également choisi de résumer simplement la colonne v.votes comme vous l'avez indiqué, c'est 1 ou 0 et donc une somme (v.votes) ajoutera 1 + 1 + 1 + 0 + 0 =3 sur 5 sont des votes positifs, tandis que SUM(1-v.vote) additionnera 0+0+0+1+1 =2 sur 5 sont des votes négatifs.
Si vous aviez un index sur les votes avec les colonnes (id_song, vote), cet index serait utilisé pour cela afin qu'il n'atteigne même pas la table. De même, si vous aviez un index sur Songs_Tags avec (id_song,id_tag), cette table ne serait pas touchée par la requête.
modifier solution ajoutée en utilisant count
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