MySQL - альтернативы вложенным подзапросам при ограничении агрегированных данных в коррелированном подзапросе - PullRequest
3 голосов
/ 30 января 2012

У меня есть таблица, которая выглядит примерно так:

DataTable
+------------+------------+------------+
| Date       | DailyData1 | DailyData2 |
+------------+------------+------------+
| 2012-01-23 |     146.30 |     212.45 |
| 2012-01-20 |     554.62 |     539.11 |
| 2012-01-19 |     710.69 |     536.35 |
+------------+------------+------------+

Я пытаюсь создать представление (назовите его AggregateView), которое будет показывать несколько различных агрегатов для каждой даты и каждого столбца данных. Например, select * from AggregateView where Date = '2012-01-23' может дать:

+------------+--------------+----------------+--------------+----------------+
| Date       | Data1_MTDAvg | Data1_20DayAvg | Data2_MTDAvg | Data2_20DayAvg |
+------------+--------------+----------------+--------------+----------------+
| 2012-01-23 |       697.71 |         566.34 |       601.37 |         192.13 |
+------------+--------------+----------------+--------------+----------------+

, где Data1_MTDAvg показывает avg(DailyData1) для каждой даты в январе до 23 января, а Data1_20DayAvg показывает то же самое, но для предыдущих 20 дат в таблице. Я не ниндзя SQL, но я думал, что лучший способ сделать это - использовать подзапросы. Среднее значение MTD легко:

select t1.Date, (select avg(t2.DailyData1) 
                 from DataTable t2 
                 where t2.Date <= t1.Date 
                   and month(t2.Date) = month(t1.Date) 
                   and year(t2.Date) = year(t1.Date)) Data1_MTDAvg 
from DataTable t1;

Но я зацикливаюсь на среднем за 20 дней из-за необходимости ограничить количество возвращаемых результатов. Обратите внимание, что даты в таблице нерегулярны, поэтому я не могу использовать интервал дат; Мне нужны последние двадцать записей в таблице, а не только все записи за последние двадцать дней. Единственное решение, которое я нашел, - использовать вложенный подзапрос, чтобы сначала ограничить выбранные записи, а затем взять среднее.

В одиночку подзапрос работает для отдельных жестко закодированных дат:

select avg(t2.DailyData1) Data1_20DayAvg 
from (select DailyData1 
      from DataTable 
      where Date <= '2012-01-23' 
      order by Date desc 
      limit 0,20) t2;

Но попытка внедрить это как часть большего запроса взрывается:

select t1.Date, (select avg(t2.DailyData1) Data1_20DayAvg 
                 from (select DailyData1 
                       from DataTable 
                       where Date <= t1.Date 
                       order by Date desc 
                       limit 0,20) t2) 
from DataTable t1;
ERROR 1054 (42S22): Unknown column 't1.Date' in 'where clause'

Из поисков вокруг у меня складывается впечатление, что вы не можете использовать коррелированные подзапросы как часть предложения from, которое, как мне кажется, в этом и заключается. Другая проблема заключается в том, что я не уверен, что MySQL примет определение представления, содержащее предложение from в подзапросе. Есть ли способ ограничить данные в моем совокупном выборе, не прибегая к подзапросам, чтобы обойти эти две проблемы?

1 Ответ

1 голос
/ 31 января 2012

Нет, вы не можете использовать коррелированные подзапросы в предложении FROM.Но вы можете использовать их в условиях ON:

SELECT AVG(d.DailyData1) Data1_20DayAvg 
       --- other aggregate stuff on d (Datatable)
FROM 
      ( SELECT '2012-01-23' AS DateChecked
      ) AS dd
  JOIN
      DataTable AS d
    ON
      d.Date <= dd.DateChecked
    AND
      d.Date >= COALESCE( 
      ( SELECT DailyData1 
        FROM DataTable AS last20 
        WHERE Date <= dd.DateChecked 
          AND (other conditions for last20)
        ORDER BY Date DESC 
        LIMIT 1 OFFSET 19
      ), '1001-01-01'   )
WHERE (other conditions for d Datatable)

Аналогично, для многих дат:

SELECT dd.DateChecked 
     , AVG(d.DailyData1) Data1_20DayAvg 
       --- other aggregate stuff on d (Datatable)
FROM 
      ( SELECT DISTINCT Date AS DateChecked
        FROM DataTable 
      ) AS dd
  JOIN
      DataTable AS d
    ON
      d.Date <= dd.DateChecked
    AND
      d.Date >= COALESCE( 
      ( SELECT DailyData1 
        FROM DataTable AS last20
        WHERE Date <= dd.DateChecked 
          AND (other conditions for last20)
        ORDER BY Date DESC 
        LIMIT 1 OFFSET 19
      ), '1001-01-01'   )
WHERE (other conditions for d Datatable)
GROUP BY 
      dd.DateChecked 

Оба запроса предполагают, что Datatable.Date имеет ограничение UNIQUE.

...