Несколько GROUP BY и сортировка по сумме значений групп - PullRequest
3 голосов
/ 27 августа 2010

Я работаю над отчетами для нашего приложения для отслеживания времени.Каждый раз запись относится к проекту и услуге.Вот упрощенный запрос для группировки записей времени по проекту и услуге.

SELECT                    
  projects.name as project_name,
  services.name as service_name,
  SUM(minutes) AS minutes 
FROM `time_entries`             
JOIN `projects` ON `projects`.id = `time_entries`.project_id 
JOIN `services` ON `services`.id = `time_entries`.service_id 
GROUP BY 
  time_entries.project_id, 
  time_entries.service_id    
ORDER BY
  max(minutes)   DESC

Это приведет к такой таблице:

+---------------+--------------+---------+
| project_name  | service_name | minutes |
+---------------+--------------+---------+
| Business Card | Consulting   |    4800 |
| Microsite     | Coding       |    3200 |
| Microsite     | Consulting   |    2400 |
| Microsite     | Design       |    2400 |
| Business Card | Design       |     800 |
+---------------+--------------+---------+

Что я пытаюсь достичь, хотя это возможность,сортировать по сумме проектных минут.Не проект «Визитная карточка» должен быть на вершине, а проект «Микросайт», потому что у него больше минут.

+---------------+--------------+-----------------+---------+
| project_name  | service_name | project_minutes | minutes |
+---------------+--------------+-----------------+---------+
| Microsite     | Coding       |            8000 |    3200 |
| Microsite     | Consulting   |            8000 |    2400 |
| Microsite     | Design       |            8000 |    2400 |
| Business Card | Consulting   |            5600 |    4800 |
| Business Card | Design       |            5600 |     800 |
+---------------+--------------+-----------------+---------+

Единственный способ найти колонку »project_minutes« - этосначала создайте таблицу и соедините ее с собой.Запрос, который я предложил:

DROP TABLE IF EXISTS group2;    
CREATE TABLE group2     SELECT                     
  projects.id as project_id,
  projects.name as project_name,
  services.name as service_name,
  SUM(minutes) AS minutes 
FROM `time_entries`             
JOIN `projects` ON `projects`.id = `time_entries`.project_id 
JOIN `services` ON `services`.id = `time_entries`.service_id 
GROUP BY 
  time_entries.project_id, 
  time_entries.service_id    
ORDER BY
  max(minutes)   DESC
LIMIT 0, 30;

SELECT 
  project_name, service_name, project_minutes, minutes
FROM  
  group2
LEFT JOIN 
  (
    SELECT project_id as project_id, sum(minutes) AS project_minutes
      FROM group2
     GROUP BY project_id         
  ) as group1  on group1.project_id = group2.project_id
ORDER BY 
  project_minutes DESC, 
  minutes DESC;    

Я даже не могу создать временную таблицу из-за ошибки MySQL (?): http://www.google.com/search?&q=site:bugs.mysql.com+reopen+temporary+table

Мои вопросы:

  1. Каков наилучший способ получить столбец наподобие »project_minutes«, который суммирует минуты групп и добавляет результат в виде дополнительного столбца?Есть ли хитрый SQL-трюк, о котором я не знаю?
  2. Если вы не нашли пути для моего первого вопроса, считаете ли вы целесообразным создание дополнительной таблицы для каждого запроса?Это быстрее, чем делать эту логику вручную после в коде?Мы используем Rails, в случае, если это что-то меняет.

Большое спасибо за вашу помощь!

ОБНОВЛЕНИЕ

Спасибо за ваши ответыдо сих пор.Я суммирую их, чтобы получить лучший обзор: http://gist.github.com/553560

Правильно ли я понимаю, что нет другого способа, кроме запроса таблицы time_entries один раз для каждой группы по выражению?Если да, вы видите проблемы с производительностью из-за следующих фактов:

  1. Таблица time_entries, безусловно, та, с наибольшим количеством строк (~ 4 миллиона)
  2. Пользователь может группироватьдо 6 столбцов.Посмотрите на этот скриншот: http://dl.dropbox.com/u/732913/time_entries_grouped_by_customer_project_service_user.png

Ответы [ 2 ]

0 голосов
/ 27 августа 2010

Я предполагаю, что project_id в time_entries всегда НЕ NULL, а services_id может быть нулевым

Select t.date, t.project_name, t.service_name, p.minutes as Project_minutes, t.minutes
FROM
(SELECT                             
  time_entries.date_at,
  time_entries.project_Id,
  projects.name as project_name,
  services.name as service_name,
  SUM(minutes) AS minutes 
FROM time_entries             
JOIN projects ON projects.id = time_entries.project_id 
LEFT JOIN services ON services.id = time_entries.service_id 
GROUP BY 
  time_entries.date_at
  time_entries.project_id, 
  time_entries.service_id    
) t
JOIN
  (Select date_at, project_Id, Sum(minutes) minutes
  from time_entries
  group by date_at, project_id) p
ON (p.date_at = t.date_at AND p.project_id = t.project_id)
0 голосов
/ 27 августа 2010

Что-то вроде этого должно делать то, что вы хотите:

SELECT ilv1.date_at, ilv1.project_name, ilv1.service_name, ilv1.minutes
FROM 
( SELECT                             
  te1.date_at,
  p1.name as project_name,
  s1.name as service_name,
  SUM(minutes) AS minutes 
FROM time_entries te1             
LEFT OUTER JOIN projects p1 ON p1.id = te1.project_id 
LEFT OUTER JOIN services s1 ON s1.id = te1.service_id 
GROUP BY 
  te1.project_id, 
  te1.service_id) AS ilv1,
( SELECT                             
  te2.date_at,
  p2.name as project_name,
  SUM(minutes) AS minutes 
FROM time_entries te1             
LEFT OUTER JOIN projects p1 ON p1.id = te1.project_id  
GROUP BY 
  te1.project_id) AS ilv2

ГДЕ ilv1.date_at = ilv2.date_at И ilv1.project_name = ilv2.project_name ЗАКАЗАТЬ по ilv2.minutes;

(Вам действительно, действительно нужны все эти внешние соединения - они сильно повредят производительности)

Вероятно, будет гораздо эффективнее использовать материализованную базу представлений в исходном запросе (и двухпроходный запрос с различными группировками, как указано выше). Но на полпути можно было бы использовать один и тот же базовый запрос дважды и обернуть его в блок консолидации, например,

SELECT ilv1.date_at, ilv1.project_name, ilv1.service_name, ilv1.minutes
FROM 
 (....) ilv1,
 (SELECT ilv3.date_at, ilv3.project_name, sum(ilv3.minutes) as minutes 
  FROM (...copy of ilv1) ilv3
  GROUP BY ilv3.date_at, ilv3.project_name
 ) ilv2
WHERE ilv1.date_at=ilv2.date_at

И ilv1.project_name = ilv2.project_name ЗАКАЗАТЬ ilv2.minutes;

С

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...