Почему неправильно подсчитываются комментарии?

#mysql

#mysql

Вопрос:

Мне нужно подсчитать комментарии и голоса за проект, но комментарии считаются неправильно.

    SELECT projects . * , COUNT( votes.project_id ) AS votes, COUNT( comments.user_id) AS comments
    FROM  `projects` 
    LEFT JOIN  `votes` ON  `projects`.`id` =  `votes`.`project_id` 
    LEFT JOIN  `comments` ON  `projects`.`id` =  `comments`.`project_id` 
    WHERE  `votes`.`created_at` >  '2014-05-31 20:21:43' AND 
    GROUP BY  `projects`.`id` 
    ORDER BY  `votes` DESC 
  

http://i.imgur.com/CcRo5JC.png

введите описание изображения здесь

Вывод:

http://i.imgur.com/aCUZETO.png

Ответ №1:

Вам нужно подсчитывать разные значения, поэтому что-то вроде:

 SELECT projects . * , COUNT( DISTINCT votes.user_id ) AS votes, COUNT( DISTINCT comments.user_id) AS comments
FROM  `projects` 
LEFT JOIN  `votes` ON  `projects`.`id` =  `votes`.`project_id` 
LEFT JOIN  `comments` ON  `projects`.`id` =  `comments`.`project_id` 
WHERE  `votes`.`created_at` >  '2014-05-31 20:21:43' AND 
GROUP BY  `projects`.`id` 
ORDER BY  `votes` DESC 
  

Ответ №2:

Счетчик «неправильный», потому COUNT() что агрегат подсчитывает строки в результирующем наборе, а не строки из отдельных таблиц. Если бы у вас было две строки с project_id = 1 в comments таблице, оба агрегата count возвращали бы 8.

По сути, каждая строка from comments сопоставляется с каждой строкой from projects .

Существует несколько подходов к решению этой проблемы. Один из них — использовать подвыборку в списке ВЫБОРА, хотя это может быть дорогостоящим (с точки зрения производительности) для больших наборов:

 SELECT p.*
     , ( SELECT COUNT(1)
           FROM votes v
          WHERE v.project_id = p.project_id
            AND v.created_at > '2014-05-31 20:21:43'
       ) AS votes
     , ( SELECT COUNT(1)
           FROM comments c
          WHERE c.project_id = p.project_id
       ) AS comment_cnt
  FROM projects p
HAVING votes > 0
 ORDER BY votes DESC
  

(Предложение HAVING включено для эмуляции исходного запроса; в оригинале «внешность» ЛЕВОГО СОЕДИНЕНИЯ с таблицей голосов отрицается b предикатом on votes.created_at в предложении WHERE .)


Другой подход заключается в получении количества из каждой из таблиц отдельно (получите количество по project_id в двух отдельных запросах, на которые ссылаются как встроенные представления, а затем объедините эти подсчеты с операциями ОБЪЕДИНЕНИЯ. Например:

 SELECT p.*
     , w.votes
     , IFNULL(d.comment_cnt,0) AS comment_cnt
  FROM projects p
  JOIN ( SELECT v.project_id
              , COUNT(1) AS votes
           FROM votes v
          WHERE v.created_at > '2014-05-31 20:21:43'
          GROUP BY v.project_id
       ) w
    ON w.project_id = p.project_id
  LEFT
  JOIN ( SELECT c.project_id
               , COUNT(1) AS comment_cnt
           FROM comments c
          GROUP BY c.project_id
       ) d
    ON d.project_id = p.project_id
 ORDER BY w.votes DESC
  

(Поскольку спецификация должна была возвращать только строки, у которых количество «голосов» больше нуля, мы можем использовать ВНУТРЕННЕЕ ОБЪЕДИНЕНИЕ, чтобы исключить строки, у которых нет никаких «голосов». Для подсчета комментариев мы используем ВНЕШНЕЕ СОЕДИНЕНИЕ и просто заменяем любые нулевые значения нулями.

Есть и другие подходы.

Производительность будет зависеть от количества строк, мощности столбцов, на которые ссылаются ссылки, доступных индексов, плана выполнения, выбранного оптимизатором, и так далее.