Er zijn tot nu toe enkele goede antwoorden geweest, maar ik zou een iets andere methode gebruiken die erg lijkt op wat je oorspronkelijk beschreef
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
Hierin is de subquery verantwoordelijk voor het verzamelen van nummers met tags in een rij van 1 rij per nummer. Dit wordt daarna samengevoegd met Stemmen. Ik heb er ook voor gekozen om de v.votes-kolom gewoon samen te vatten, zoals je hebt aangegeven dat het 1 of 0 is en daarom zal een SUM (v.votes) 1+1+1+0+0 =3 van de 5 zijn upvotes, terwijl SUM(1-v.vote) 0+0+0+1+1 =2 van de 5 downvotes zal zijn.
Als je een index op stemmen had met de kolommen (id_song,vote) dan zou die index hiervoor worden gebruikt, zodat hij niet eens in de tabel zou komen. Evenzo, als je een index op Songs_Tags had met (id_song,id_tag), dan zou die tabel niet worden geraakt door de zoekopdracht.
bewerken toegevoegde oplossing met 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