2012-06-19 3 views
6

У меня есть две таблицы: «Серверы» и «статистика»MySQL - Средние последние столбцы в другой таблице

серверов имеет столбец с именем «ID», который автоматически приращений. Статистика имеет столбец «сервер», который соответствует строке в таблице серверов, столбец «время», который представляет время его добавления, и столбец «голоса», который я хотел бы получить в среднем.

Я хотел бы получить все серверы (SELECT * FROM servers) вместе со средним числом голосов из 24 последних строк, соответствующих каждому серверу. Я считаю, что это вопрос «самый большой-на-группу».

Это то, что я пытался сделать, но он дал мне 24 строк в общей сложности, а не 24 строк в каждой группе:

SELECT servers.*, 
     IFNULL(AVG(stats.votes), 0) AS avgvotes 
FROM servers 
LEFT OUTER JOIN 
    (SELECT server, 
      votes 
    FROM stats 
    GROUP BY server 
    ORDER BY time DESC LIMIT 24) AS stats ON servers.id = stats.server 
GROUP BY servers.id 

Как я уже сказал, я хотел бы получить 24 последних строк для каждого сервера , а не 24 последних итоговых рядов.

+1

Я считаю, что [это] (http://sqlfiddle.com/#!2/d908f/5) является структурой таблицы вашей таблицы , Правильно? –

ответ

1

Это еще один подход.

Этот запрос будет испытывать те же проблемы с производительностью, что и другие запросы, которые возвращают правильные результаты, поскольку для плана выполнения этого запроса потребуется операция SORT в КАЖДОЙ строке таблицы статистики. Поскольку в столбце времени нет предиката (ограничения), будет рассмотрена КАЧАЯ строка в таблице статистики. Для ДЕЙСТВИТЕЛЬНО большого стола stats это сдует все доступное временное пространство, прежде чем оно умрет от ужасной смерти. (Другие заметки о производительности ниже.)

SELECT r.* 
    , IFNULL(s.avg_votes,0) 
    FROM servers r 
    LEFT 
    JOIN (SELECT t.server 
       , AVG(t.votes) AS avg_votes 
      FROM (SELECT CASE WHEN u.server = @last_server 
          THEN @i := @i + 1 
          ELSE @i := 1 
         END AS i 
         , @last_server := u.server AS `server` 
         , u.votes AS votes 
        FROM (SELECT @i := 0, @last_server := NULL) i 
        JOIN (SELECT v.server, v.votes 
          FROM stats v 
          ORDER BY v.server DESC, v.time DESC 
         ) u 
       ) t 
      WHERE t.i <= 24 
      GROUP BY t.server 
     ) s 
    ON s.server = r.id 

Что этот запрос делает сортирует таблицу статистики, сервер и убывающего порядок по столбцу времени. (Inline view aliased as u.)

С отсортированным набором результатов мы присваиваем номера строк 1,2,3 и т. Д. Каждой строке для каждого сервера. (Inline view aliased как t.)

С этим набором результатов мы отфильтровываем любые строки с номером rownumber> 24 и вычисляем среднее значение столбца votes для «последних» 24 строк для каждого сервера. (Inline view aliased as s.)

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


Примечание:

План выполнения этого запроса будет затратным для большого количества строк в таблице stats.

Для улучшения производительности есть несколько подходов, которые мы могли бы предпринять.

Простейший может включать в запрос предикат EXCLUDES значительное количество строк из таблицы stats (например, строки с time значениями старше 2 дней или старше 2 недель). Это значительно сократило бы количество строк, которые нужно отсортировать, чтобы определить «последние» 24 строки.

Кроме того, с индексом на stats(server,time) также возможно, что MySQL может сделать относительно эффективное «обратное сканирование» по индексу, избегая операции сортировки.

Мы также можем рассмотреть возможность применения индекса в таблице статистики по (server,"reverse_time"). Поскольку MySQL еще не поддерживает нисходящие индексы, реализация будет действительно регулярным (восходящим) индексом для производного значения rtime (выражение «обратного времени», которое возрастает для нисходящих значений time (например, -1*UNIX_TIMESTAMP(my_timestamp) или -1*TIMESTAMPDIFF('1970-01-01',my_datetime).

Другим подходом к повышению производительности будет сохранение теневой таблицы, содержащей самые последние 24 строки для каждого сервера. Это было бы проще реализовать, если мы можем гарантировать, что «последние строки» не будут удалены из таблицы stats Мы можем сохранить эту таблицу с помощью триггера. В принципе, всякий раз, когда строка вставляется в таблицу stats, мы проверяем, будет ли time в новых строках позже, чем самый ранний time, сохраненный для сервера в тени table, если это так, мы заменим самую раннюю строку в теневой таблице новой строкой, не забудьте сохранить не более 24 строк в теневой таблице для каждого сервера.

И еще один подход - написать процедуру или функцию, которые получают результат. Подходом здесь будет цикл через каждый сервер и запуск отдельного запроса к таблице статистики, чтобы получить среднее значение votes для последних 24 строк и собрать все эти результаты вместе. (Этот подход действительно может быть скорее обходным путем, чтобы избежать сортировки на огромном временном наборе, просто для того, чтобы вернуть возвращаемый набор результатов, что не обязательно приводило к быстрому возврату результатов.)

Практический результат этот тип запроса в таблице LARGE ограничивает количество строк, рассмотренных в запросе, и исключает операцию сортировки на большом множестве. Вот как мы получаем такой запрос.


ДОПОЛНЕНИЕ

Для того, чтобы получить «обратное сканирование индекса» операцию (чтобы получить строки из stats заказанных с использованием индекса без операции FileSort), я должен указать DESCENDING на оба выражениях в Предложение ORDER BY. Ранее запрос состоял из ORDER BY server ASC, time DESC, и MySQL всегда хотел сделать fileort, даже указав подсказку FORCE INDEX FOR ORDER BY (stats_ix1).

Если требование заключается в возврате «среднего голоса» для сервера только, если в таблице статистики имеется не менее 24 связанных строк, тогда мы можем сделать более эффективный запрос, даже если он немного больше беспорядочный. (Большая часть беспорядка во вложенных функциях IF() заключается в том, чтобы иметь дело со значениями NULL, которые не включаются в среднее значение. Это может быть намного менее беспорядочно, если у нас есть гарантия, что votes не является NULL, или если мы исключаем любые строки, в которых votes равно нулю.)

SELECT r.* 
    , IFNULL(s.avg_votes,0) 
    FROM servers r 
    LEFT 
    JOIN (SELECT t.server 
       , t.tot/NULLIF(t.cnt,0) AS avg_votes 
      FROM (SELECT IF(v.server = @last_server, @num := @num + 1, @num := 1) AS num 
         , @cnt := IF(v.server = @last_server,IF(@num <= 24, @cnt := @cnt + IF(v.votes IS NULL,0,1),@cnt := 0),@cnt := IF(v.votes IS NULL,0,1)) AS cnt 
         , @tot := IF(v.server = @last_server,IF(@num <= 24, @tot := @tot + IFNULL(v.votes,0)  ,@tot := 0),@tot := IFNULL(v.votes,0)  ) AS tot 
         , @last_server := v.server AS SERVER 
        -- , v.time 
        -- , v.votes 
        -- , @tot/NULLIF(@cnt,0) AS avg_sofar 
        FROM (SELECT @last_server := NULL, @num:= 0, @cnt := 0, @tot := 0) u 
        JOIN stats v FORCE INDEX FOR ORDER BY (stats_ix1) 
        ORDER BY v.server DESC, v.time DESC 
       ) t 
      WHERE t.num = 24 
     ) s 
    ON s.server = r.id 

с индексом покрытия на stats(server,time,votes), Объяснить показал, MySQL, избежать операции FileSort, поэтому он должен быть использован «обратного сканирования индекса» для возврата строки в порядке. Отсутствует индекс покрытия и индекс на '(сервер, время) , MySQL used the index if I included an index hint, with the FORCE INDEX FOR ORDER BY (stats_ix1) `подсказка, MySQL также избегал файлового массива. (Но поскольку моя таблица имела менее 100 строк, я не думаю, что MySQL уделяет много внимания тому, чтобы избежать операции с файловым файлом.)

Выражения времени, голосов и выражения avg_sofar (во встроенном представлении aliased как t); они не нужны, но они предназначены для отладки.

Способ, которым этот запрос стоит, для каждого сервера требуется не менее 24 строк в статистике, чтобы вернуть среднее значение. (Это может быть приемлемо.) Но я думал, что в целом мы можем вернуть общее количество, общее количество (tot) и счетчик операций (cnt).

(Если заменить WHERE t.num = 24 с WHERE t.num <= 24, мы можем видеть, скользящее среднее в действии.)

Для возврата в среднем, где нет, по крайней мере 24 строк в статистике, это действительно вопрос, идентифицирующая (для каждого сервера) с максимальным значением num, которое равно < = 24.

+0

Извините за поздний ответ. Этот запрос работает и работает быстрее, чем предыдущие ответы.Я также очень ценю ваше подробное объяснение и ваши многочисленные решения для увеличения скорости. На данный момент насчитывается 40 000 строк, однако есть потенциал для его увеличения до нескольких миллионов. Я сейчас использую индекс («статистика (сервер, время)»), и если произойдет значительное снижение производительности, я, скорее всего, реализую предложение теневой таблицы. Огромное спасибо! – fruitcup

+0

Индекс покрытия на 'stats (server, time, votes)' будет еще лучше для производительности. Я добавил добавление к моему ответу, с другим запросом, который может быть еще быстрее. У него есть ограничение (как написано) о том, что в таблице статистики должно быть не менее 24 строк для сервера, для получения среднего значения. – spencer7593

2

Спасибо за этот великий post.

alter table add index(server, time) 
set @num:=0, @server:=''; 
select servers.*, IFNULL(AVG(stats.votes), 0) AS avgvotes 
from servers left outer join (
select server, 
     time,votes, 
     @num := if(@server = server, @num + 1, 1) as row_number, 
     @server:= server as dummy 
from stats force index(server) 
group by server, time 
having row_number < 25) as stats 
on servers.id = stats.server 
group by servers.id 

редактировать 1

Я просто заметил, что выше запрос дает самые старые 24 записей для каждой группы.

set @num:=0, @server:=''; 
select servers.*, IFNULL(AVG(stats.votes), 0) AS avgvotes 
from servers left outer join (
select server, 
     time,votes, 
     @num := if(@server = server, @num + 1, 1) as row_number, 
     @server:= server as dummy 
from (select * from stats order by server, time desc) as t 
group by server, time 
having row_number < 25) as stats 
on servers.id = stats.server 
group by servers.id 

, который даст в среднем 24 нового объекта для каждой группы

Edit2

@DrAgonmoray вы можете попробовать внутреннюю часть запроса первый и посмотреть, если он возвращает новейшие 24 записей для каждой группы. В моем mysql 5.5 он работает правильно.

select server, 
     time,votes, 
     @num := if(@server = server, @num + 1, 1) as row_number, 
     @server:= server as dummy 
from (select * from stats order by server, time desc) as t 
group by server, time 
having row_number < 25 
+0

Я получаю синтаксическую ошибку здесь: 'select servers. *, IFNULL (AVG (stats.votes), 0) AS avgvotes от серверов lef' по строке 2 – fruitcup

+1

@DrAgonmoray, поместите ';' после 'alter table add ... ', а также строку' set @num ... ', поскольку они являются отдельными командами из реального запроса. –

+0

Теперь код работает, однако он, как представляется, дает мне среднее значение всех записей для каждого сервера, а не только 24. Я протестировал это, используя несколько разных серверов. – fruitcup

0

Попробуйте это решение, с техники топ-н-на-группы в INNER JOIN подвыборки зачисленных Bill Karwin и его пост об этом here.

SELECT 
    a.*, 
    AVG(b.votes) AS avgvotes 
FROM 
    servers a 
INNER JOIN 
    (
     SELECT 
      aa.server, 
      aa.votes 
     FROM 
      stats aa 
     LEFT JOIN stats bb ON 
      aa.server = bb.server AND 
      aa.time < bb.time 
     GROUP BY 
      aa.time 
     HAVING 
      COUNT(*) < 24 
    ) b ON a.id = b.server 
GROUP BY 
    a.id 
+0

Этот запрос по какой-то причине очень медленный. Я выполняю его и позволяю ему сидеть в течение нескольких минут, и он не заканчивается. Мне не нужна экстремальная скорость, но это слишком долго. – fruitcup

+0

@DrAgonmoray Хорошо, я вижу. Я попытаюсь найти лучшее решение. Какая у вас структура индексирования? У вас есть индекс, настроенный в поле 'time'? –

+0

Нет У меня нет индекса, установленного в поле времени, однако я могу добавлять/удалять индексы по мере необходимости. В настоящее время для статистики не указаны индексы. – fruitcup

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