Скользящее среднее значение MySQL из N строк

У меня есть простая таблица MySQL, как показано ниже, используемая для вычисления MPG для автомобиля.

+-------------+-------+---------+
| DATE        | MILES | GALLONS |
+-------------+-------+---------+
| JAN 25 1993 |  20.0 |    3.00 |
| FEB 07 1993 |  55.2 |    7.22 |
| MAR 11 1993 |  44.1 |    6.28 |
+-------------+-------+---------+

Я могу легко вычислить количество миль на галлон (MPG) для автомобиля с помощью оператора select, но поскольку MPG сильно варьируется от заправки к заправке (т. е. вы не заправляете каждый раз одно и то же количество бензина), я хотел бы компьютер также является «СКОЛЬЗЯЩИМ СРЕДНИМ». Таким образом, для любой строки МИЛЬ НА ГАЛЛОН — это МИЛЬ/ГАЛЛОН для этой строки, а MOVINGMPG — это СУММА (МИЛЬ)/СУММА (ГАЛЛОН) для последних N строк. Если к этому моменту существует менее N строк, просто СУММ(МИЛИ)/СУММ(ГАЛЛОНЫ) до этого момента.

Существует ли один оператор SELECT, который будет извлекать строки с MPG и MOVINGMPG, подставляя N в оператор select?


person TSG    schedule 21.02.2013    source источник


Ответы (2)


Да, указанный набор результатов можно вернуть с помощью одного оператора SQL.

К сожалению, MySQL не поддерживает аналитические функции, что делает оператор довольно простым. Несмотря на то, что MySQL не имеет синтаксиса для их поддержки, можно эмулировать некоторые аналитические функции, используя пользовательские переменные MySQL.

Один из способов получить указанный набор результатов (с помощью одного оператора SQL) — использовать операцию JOIN с использованием уникального возрастающего целочисленного значения (rownum, полученного и присвоенного в запросе) для каждой строки.

Например:

SELECT q.rownum          AS rownum
     , q.date            AS latest_date
     , q.miles/q.gallons AS latest_mpg
     , COUNT(1)               AS cnt_rows
     , MIN(r.date)            AS earliest_date
     , SUM(r.miles)                AS rtot_miles
     , SUM(r.gallons)              AS rtot_gallons
     , SUM(r.miles)/SUM(r.gallons) AS rtot_mpg
  FROM ( SELECT @s_rownum := @s_rownum + 1 AS rownum
              , s.date
              , s.miles
              , s.gallons
           FROM mytable s
           JOIN (SELECT @s_rownum := 0) c
          ORDER BY s.date
       ) q
  JOIN ( SELECT @t_rownum := @t_rownum + 1 AS rownum
              , t.date                  
              , t.miles
              , t.gallons
           FROM mytable t
           JOIN (SELECT @t_rownum := 0) d
          ORDER BY t.date
       ) r
    ON r.rownum <= q.rownum
   AND r.rownum > q.rownum - 2
 GROUP BY q.rownum

Желаемое значение «n», указывающее, сколько строк включать в каждую строку свертки, указывается в предикате непосредственно перед предложением GROUP BY. В этом примере до «2» строк в каждой строке промежуточного итога.

Если вы укажете значение 1, вы получите (в основном) исходную возвращенную таблицу.

Чтобы исключить любые «неполные» строки промежуточных сумм (состоящие из менее чем «n» строк), это значение «n» необходимо будет указать снова, добавив:

HAVING COUNT(1) >= 2

Демонстрация sqlfiddle: http://sqlfiddle.com/#!2/52420/2

Следовать за:

Вопрос. Я пытаюсь понять ваше выражение SQL. Выполняет ли ваше решение выбор двадцати строк для каждой строки в базе данных? Другими словами, если у меня есть 1000 строк, будет ли ваш оператор выполнять 20000 выборок? (Я беспокоюсь о производительности)...

A: Вы правы, беспокоясь о производительности.

Чтобы ответить на ваш вопрос, нет, это не выполняет 20 000 выборок для 1000 строк.

Ухудшение производительности происходит из-за двух (по сути идентичных) встроенных представлений (с псевдонимами q и r). Что MySQL делает с ними (в основном), так это создает временные таблицы MyISAM (MySQL называет их «производными таблицами»), которые в основном являются копиями mytable с дополнительным столбцом, каждой строке присваивается уникальное целочисленное значение от 1 до количество строк.

Как только две «производные» таблицы созданы и заполнены, MySQL запускает внешний запрос, используя эти две «производные» таблицы в качестве источника строк. Каждая строка из q сопоставляется с n строками из r для расчета «нарастающей суммы» миль и галлонов.

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

SELECT q.date                      AS latest_date
     , SUM(q.miles)/SUM(q.gallons) AS latest_mpg
     , COUNT(1)                    AS cnt_rows
     , MIN(r.date)                 AS earliest_date
     , SUM(r.miles)                AS rtot_miles
     , SUM(r.gallons)              AS rtot_gallons
     , SUM(r.miles)/SUM(r.gallons) AS rtot_mpg
  FROM mytable q
  JOIN mytable r
    ON r.date <= q.date
   AND r.date > q.date + INTERVAL -30 DAY
 GROUP BY q.date

(Для повышения производительности вам потребуется соответствующий индекс, определенный с помощью date в качестве начального столбца в индексе.)


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


Опять же, на ваш вопрос о выполнении 20 000 выборок для 1000 строк... операция вложенных циклов - это еще один способ получить тот же набор результатов. Для большого количества строк это может привести к снижению производительности. (С другой стороны, этот подход может быть достаточно эффективным, когда возвращаются только несколько строк:

SELECT q.date                 AS latest_date
     , q.miles/q.gallons      AS latest_mpg
     , ( SELECT SUM(r.miles)/SUM(r.gallons)
           FROM mytable r
          WHERE r.date <= q.date
            AND r.date >= q.date + INTERVAL -90 DAY
       ) AS rtot_mpg
  FROM mytable q
 ORDER BY q.date
person spencer7593    schedule 21.02.2013
comment
Я пытаюсь понять ваше выражение SQL. Выполняет ли ваше решение выбор двадцати строк для каждой строки в базе данных? Другими словами, если у меня есть 1000 строк, будет ли ваш оператор выполнять 20000 выборок? (Я беспокоюсь о производительности)... - person TSG; 21.02.2013
comment
Мне интересно, уменьшит ли оператор LIMIT N в соединениях нагрузку на сервер sql? (Не знаю как, просто думаю о нагрузке) - person TSG; 21.02.2013

Что-то вроде этого должно работать:

SELECT Date, Miles, Gallons, Miles/Gallons as MilesPerGallon,
  @Miles:=@Miles+Miles overallMiles,
  @Gallons:=@Gallons+Gallons overallGallons,
  @RunningTotal:=@Miles/@Gallons runningTotal
FROM YourTable
  JOIN (SELECT @Miles:= 0) t
  JOIN (SELECT @Gallons:= 0) s

Демонстрация скрипки SQL

Что производит следующее:

DATE                MILES    GALLONS    MILESPERGALLON   RUNNINGTOTAL
January, 25 1993    20       3          6.666667         6.666666666667
February, 07 1993   55.2     7.22       7.645429         7.358121330724
March, 11 1993      44.1     6.28       7.022293         7.230303030303

--РЕДАКТИРОВАТЬ--

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

SELECT *
FROM (
  SELECT Date, Miles, Gallons, Miles/Gallons as MilesPerGallon,
    @Miles:=@Miles+Miles overallmiles,
    @Gallons:=@Gallons+Gallons overallGallons,
    @RunningTotal:=@Miles/@Gallons runningTotal,
    @RowNumber:=@RowNumber+1 rowNumber
  FROM (SELECT * FROM YourTable ORDER BY Date DESC) u
    JOIN (SELECT @Miles:= 0) t
    JOIN (SELECT @Gallons:= 0) s
    JOIN (SELECT @RowNumber:= 0) r
  ) t
WHERE rowNumber <= 3

Просто измените предложение ORDER BY соответствующим образом. А вот обновленная скрипта.

person sgeddes    schedule 21.02.2013
comment
Что ограничивает RunningTotal последними N записями? - person TSG; 21.02.2013
comment
@Michelle - я добавил RowNumber, чтобы вы могли ограничиться последними записями N - Удачи! - person sgeddes; 21.02.2013
comment
Это останавливается после 3 строк. Я хочу, чтобы SQL выводил все 1000 строк со средним значением последних 3 (или N). В противном случае вывод выглядит правильно. - person TSG; 21.02.2013
comment
@ Мишель - извините, неправильно понял ваш вопрос :) Похоже, у вас есть принятый ответ, поэтому я предполагаю, что вы в порядке. С наилучшими пожеланиями. - person sgeddes; 21.02.2013