2012-02-22 4 views
3

У меня есть таблица «Песни», «Songs_Tags» (связанные песни с тегами) и «Songs_Votes» (связанные песни с булевым как/неприязнь).LEFT JOIN после GROUP BY?

Мне нужно получить песни с помощью 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 (так что теперь будет только одна запись для каждой песни). Тогда мне понадобится другая GROUP BY m.id.

Есть ли способ сделать это? Нужно ли использовать подзапрос?

+0

ли ваша таблица голосов на ПК? –

+0

Да, (id_song, id_vote) – Lem0n

ответ

5

Там были некоторые хорошие ответы до сих пор, но я бы принять несколько иной метод совершенно аналогично тому, что вы описали первоначально

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 = 3 из 5 являются upvotes, тогда как SUM (1-v.vote) будет суммировать 0 + 0 + 0 + 1 + 1 = 2 из 5 - downvotes.

Если у вас есть индекс голосов с колонками (id_song, vote), тогда этот индекс будет использоваться для этого, чтобы он даже не попал в таблицу. Аналогично, если у вас есть индекс в Songs_Tags с (id_song, id_tag), то эта таблица не будет затронута запросом.

редактировать добавлено решение с использованием счетчика

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 
+0

Мне нравится это решение, особенно тот факт, что он не попал в БД для тегов или голосов ... но я буду придерживаться только COUNT() вместо SUM(), потому что семантически это имеет смысл ИМО (в конце концов, я подсчитываю upvotes и downvotes) – Lem0n

+0

Он работает с любым из них, добавленным в ваш COUNT () –

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 
+0

Я не уверен, что это лучшее решение, но мне нравится, что он решает проблему с (по-видимому) наименьшим количеством изменений/перепроектирования. – Lem0n

+0

На самом деле, он не решает проблему. Если больше людей проголосуют «как» (правда), оно будет считаться как можно больше ONE. – Lem0n

+0

Lem0n: Он работал в моих тестах, но у меня могут быть несколько разные структуры данных. Обратите внимание, что счетчик id_vote, а не 1 ... –

2

Ваши результаты кода в мини-декартово произведение, потому что вы делаете два играя в 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 
+0

не делает 3 SELECTs медленнее? или, может быть, этот код будет быстрее, когда у меня будет много тегов для одной и той же песни? – Lem0n

+1

Будете ли вы мне доверять, если я скажу, что это быстрее?Тест (все запросы, которые дают правильные результаты) с вашими данными и дистрибутивом, вашим сервером и его настройками, с различными размерами таблиц, а затем выберите :) –

Смежные вопросы