Oracle: найдите предыдущую запись для ранжированного списка прогнозов - PullRequest
0 голосов
/ 05 октября 2009

Привет, я столкнулся с трудной проблемой:

У меня есть таблица (oracle 9i) прогнозов погоды (многие из сотен миллионов записей в размерах). чей макияж выглядит так:

stationid    forecastdate    forecastinterval    forecastcreated    forecastvalue
---------------------------------------------------------------------------------
varchar (pk) datetime (pk)   integer (pk)        datetime (pk)      integer

где:

  • stationid относится к одной из множества метеостанций, которые могут создавать прогноз;
  • forecastdate относится к дате, на которую рассчитан прогноз (только дата, а не время.)
  • forecastinterval относится к часу в forecastdate для прогноза (0 - 23).
  • forecastcreated относится ко времени, когда был сделан прогноз, может быть за много дней до этого.
  • forecastvalue относится к фактическому значению прогноза (как следует из названия).

Мне нужно определить для данной пары stationid и данной пары forecastdate и forecastinterval записи, в которых forecastvalue увеличивается больше номинального числа (скажем, 500). Я покажу таблицу условий здесь:

stationid    forecastdate    forecastinterval    forecastcreated    forecastvalue
---------------------------------------------------------------------------------
'stationa'   13-dec-09       10                  10-dec-09 04:50:10  0
'stationa'   13-dec-09       10                  10-dec-09 17:06:13  0
'stationa'   13-dec-09       10                  12-dec-09 05:20:50  300
'stationa'   13-dec-09       10                  13-dec-09 09:20:50  300

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

Задача оказывается очень сложной из-за огромного размера таблицы (много сотен миллионов записей), и ее выполнение занимает так много времени (так долго, что мой запрос никогда не возвращался).

Вот моя попытка получить эти значения:

select
    wtr.stationid,
    wtr.forecastcreated,
    wtr.forecastvalue,
    (wtr.forecastdate + wtr.forecastinterval / 24) fcst_date
from
    (select inner.*
            rank() over (partition by stationid, 
                                   (inner.forecastdate + inner.forecastinterval),
                                   inner.forecastcreated
                         order by stationid, 
                                  (inner.forecastdate + inner.forecastinterval) asc,
                                  inner.forecastcreated asc
            ) rk
      from weathertable inner) wtr 
      where
      wtr.forecastvalue - 100 > (
                     select lastvalue
                      from (select y.*,
                            rank() over (partition by stationid, 
                                            (forecastdate + forecastinterval),
                                            forecastcreated
                                         order by stationid, 
                                           (forecastdate + forecastinterval) asc,
                                           forecastcreated asc) rk
                             from weathertable y
                            ) z
                       where z.stationid = wtr.stationid
                             and z.forecastdate = wtr.forecastdate                                                   
                             and (z.forecastinterval =    
                                         wtr.forecastinterval)
/* here is where i try to get the 'previous' forecast value.*/
                             and wtr.rk = z.rk + 1)

1 Ответ

1 голос
/ 05 октября 2009

Предложение Rexem об использовании LAG () является правильным подходом, но нам нужно использовать условие разделения. Это становится понятным, как только мы добавим строки для разных интервалов и разных станций ...

SQL> select * from t
  2  /    
STATIONID  FORECASTDATE INTERVAL FORECASTCREATED     FORECASTVALUE
---------- ------------ -------- ------------------- -------------
stationa   13-12-2009         10 10-12-2009 04:50:10             0
stationa   13-12-2009         10 10-12-2009 17:06:13             0
stationa   13-12-2009         10 12-12-2009 05:20:50           300
stationa   13-12-2009         10 13-12-2009 09:20:50           300
stationa   13-12-2009         11 13-12-2009 09:20:50           400
stationb   13-12-2009         11 13-12-2009 09:20:50           500

6 rows selected.

SQL> SELECT v.stationid,
  2         v.forecastcreated,
  3         v.forecastvalue,
  4         (v.forecastdate + v.forecastinterval / 24) fcst_date
  5    FROM (SELECT t.stationid,
  6                 t.forecastdate,
  7                 t.forecastinterval,
  8                 t.forecastcreated,
  9                 t.forecastvalue,
 10                 t.forecastvalue - LAG(t.forecastvalue, 1)
 11                      OVER (ORDER BY t.forecastcreated) as difference
 12            FROM t) v
 13   WHERE v.difference >= 100
 14  /    
STATIONID  FORECASTCREATED     FORECASTVALUE FCST_DATE
---------- ------------------- ------------- -------------------
stationa   12-12-2009 05:20:50           300 13-12-2009 10:00:00
stationa   13-12-2009 09:20:50           400 13-12-2009 11:00:00
stationb   13-12-2009 09:20:50           500 13-12-2009 11:00:00

SQL> 

Чтобы удалить ложные срабатывания, мы группируем LAG () по STATIONID, FORECASTDATE и FORECASTINTERVAL. Обратите внимание, что следующее опирается на внутренний запрос, возвращающий NULL из первого вычисления каждого окна раздела.

SQL> SELECT v.stationid,
  2         v.forecastcreated,
  3         v.forecastvalue,
  4         (v.forecastdate + v.forecastinterval / 24) fcst_date
  5    FROM (SELECT t.stationid,
  6                 t.forecastdate,
  7                 t.forecastinterval,
  8                 t.forecastcreated,
  9                 t.forecastvalue,
 10                 t.forecastvalue - LAG(t.forecastvalue, 1)
 11                      OVER (PARTITION BY t.stationid
 12                                         , t.forecastdate
 13                                         , t.forecastinterval
 14                            ORDER BY t.forecastcreated) as difference
 15            FROM t) v
 16   WHERE v.difference >= 100
 17  /

STATIONID  FORECASTCREATED     FORECASTVALUE FCST_DATE
---------- ------------------- ------------- -------------------
stationa   12-12-2009 05:20:50           300 13-12-2009 10:00:00

SQL> 

Работа с большими объемами данных

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

Но все равно вот пятиминутный ответ.

Предполагая, что ваша таблица является живой таблицей, она, вероятно, заполняется, добавляя прогнозы по мере их появления, что по сути является дополнительной операцией. Это будет означать, что прогнозы для любой данной станции разбросаны по всей таблице. Следовательно, индексы только STATIONID или даже FORECASTDATE будут иметь плохой фактор кластеризации.

Исходя из этого, я рекомендую вам сначала попробовать создать индекс на (STATIONID, FORCASTDATE, FORECASTINTERVAL, FORECASTCREATED, FORECASTVALUE). Это займет некоторое время (и дисковое пространство) для сборки, но это должно значительно ускорить ваши последующие запросы, поскольку в нем есть все столбцы, необходимые для удовлетворения запроса с помощью INDEX RANGE SCAN, не касаясь таблицы вообще.

...