Существует ли более быстрый способ генерирования требуемого вывода, чем использование соединения «один ко многим» в Proc SQL? - PullRequest
0 голосов
/ 14 февраля 2019

Мне требуется вывод, который показывает общее количество часов, отработанных в скользящем 24-часовом окне.В настоящее время данные хранятся таким образом, что каждая строка представляет собой один часовой интервал (например, 7-8 утра 2 января) на человека, и сколько времени они работали в этот час, хранится как «Час».Мне нужно создать еще одно поле, которое представляет собой сумму последних 24 часовых интервалов (включительно) для каждой строки.Таким образом, для приведенного выше примера с 7-8 утра я бы хотел, чтобы сумма «Час» по 24 строкам: 1 января 8-9 утра, 1 января 9-10 утра ... 2 января 6-7 утра, 2 января 7-8 утра.

Промыть и повторить для каждого часового интервала.

Здесь 6000 человек, и у нас есть данные за 6 месяцев, что означает, что в таблице 6000 * 183 дней * 24 часа = 26,3 млн строк.

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

У кого-нибудь есть другие идеи?Все переменные даты / времени представлены в формате datetime.

proc sql;
create table want as
 select x.*
 , case when Hours_Wrkd_In_Window > 16 then 1 else 0 end as Correct 
 from (
  select a.ID
  , a.Start_DTTM
  , a.End_DTTM
  , sum(b.hours) as Hours_Wrkd_In_Window
  from have a
   left join have b
   on a.ID = b.ID
   and b.start_dttm > a.start_dttm - (24 * 60 * 60)
   and b.start_dttm <= a.start_dttm
  where datepart(a.Start_dttm) >= &report_start_date.
  and datepart(a.Start_dttm) < &report_end_date.
  group by ID
  , a.Start_DTTM
  , a.End_DTTM  
) x
order by x.ID
, x.Start_DTTM
;quit;

Ответы [ 2 ]

0 голосов
/ 14 февраля 2019

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

Если у вас широкая лицензия SAS, изучите процедуры в SAS / ETS (Econometrics).и временной ряд).Proc EXPAND может иметь некоторые возможности скользящего агрегата.

В этом примере кода шага DATA потребовалось менее 10 секунд (папка WORK на SSD) для запуска на имитированных данных для 6 тысяч человек с 6 месяцами полного охвата временных интервалов в 1 час.

data have(keep=id start_dt end_dt hours);
  do id = 1 to 6000;

    do start_dt 
     = intnx('dtmonth', datetime(), -12)
    to intnx('dtmonth', datetime(), -6)
    by dhms(0,1,0,0)
    ;
      end_dt = start_dt + dhms(0,1,0,0);

      hours = 0.25 * floor (5 * ranuni(123)); * 0, 1/4, 1/2, 3/4 or 1 hour;

      output;
    end;
  end;

  format hours 5.2;
run;

/* %let log= ; options obs=50 linesize=200; * submit this (instead of next) if you want to log the logic; */

%let log=*; options obs=max;

data want2(keep=id start_dt end_dt hours hours_rolling_sum hours_rolling_cnt hours_out_:);

  array dt_ring(24) _temporary_;
  array hr_ring(24) _temporary_;

  call missing (of dt_ring(*));
  call missing (of hr_ring(*));

  if 0 then set have; * prep pdv column order;

  hours_rolling_sum = 0;
  hours_rolling_cnt = 0;
  label hours_rolling_sum = 'Hours worked in prior 24 hours';

  index = 0;

  do until (last.id);
    set have;
    by id start_dt;

    index + 1;
    if index > 24 then index = 1;

    hours_out_sum = 0;
    hours_out_cnt = 0;

    do clear = 1 by 1 until (clear=0);

      if sum (dt_ring(index), 0) = 0 then do;
        * index is first go through ring array, or hit a zeroed slot;

&log putlog 'NOTE: ' index= 'clear for empty ring item. ';

        clear = 0;
      end;
      else
      if start_dt - dt_ring(index) >= %sysfunc(dhms(0,24,0,0)) then do;

&log putlog / 'NOTE: ' index= 'reducting and zeroing.' /;

        hours_out_sum + hr_ring(index);
        hours_out_cnt + 1;

        hours_rolling_sum = hours_rolling_sum - hr_ring(index);
        hours_rolling_cnt = hours_rolling_cnt - 1;
        dt_ring(index) = 0;
        hr_ring(index) = 0;

        * advance item to next item, that might also be more than 24 hours ago;
        index = index + 1;
        if index > 24 then index = 1;

      end;
      else do;

&log putlog / 'NOTE: ' index= 'back off !' /;

        * index was advanced to an item within 24 hours, back off one;
        index = index - 1;
        if index < 1 then index = 24;
        clear = 0;
      end;

    end; /* do clear */

    dt_ring(index) = start_dt;
    hr_ring(index) = hours;

    hours_rolling_sum + hours;
    hours_rolling_cnt + 1;

&log putlog 'NOTE: ' index= 'overlaying and aggregating.' / 'NOTE:  ' start_dt= hours= hours_rolling_sum= hours_rolling_cnt=;

    output;
  end; /* do until */

  format hours_rolling_sum 5.2 hours_rolling_cnt 2.; 
  format hours_out_sum 5.2 hours_out_cnt 2.;
run;

options obs=max;

При просмотре результатов вы должны заметить, что дельта для hours_rolling_sum равна + (часы в слоте) - (hours_out_sum {которая удалена из кольца})

Если вы должны использовать SQL, я быпредложите после @jspascal и проиндексируйте таблицу, но переставьте запрос, чтобы объединить исходные данные слева с внутренним подвыбором (чтобы SQL выполнял хеш-соединение по индексам с идентификаторами).Для такого же количества людей это должно быть быстрее, чем исходный запрос, но все равно быть слишком медленным для выполнения всех 6K.

proc sql; 
  create index id on have;
  create index id_slot on have (id, start_dt);
quit;

proc sql _method;

  reset inobs=50; * limit data so you can see the _method;

  create table want as
  select
    have.*
  , case 
      when ROLLING.HOURS_WORKED_24_HOUR_PRIOR > 16 
      then 1 
      else 0
    end as REVIEW_TIME_CLOCKING_FLAG

  from 
    have
  left join
  (
    select
        EACH_SLOT.id
      , EACH_SLOT.start_dt
      , count(*) as SLOT_COUNT_24_HOUR_PRIOR
      , sum(PRIOR_SLOT.hours) as HOURS_WORKED_24_HOUR_PRIOR
      from 
        have as EACH_SLOT
      join
        have as PRIOR_SLOT
      on
        EACH_SLOT.ID = PRIOR_SLOT.ID
        and EACH_SLOT.start_dt - PRIOR_SLOT.start_dt between 0 and %sysfunc(dhms(0,24,0,0))-0.1
      group by
        EACH_SLOT.id, EACH_SLOT.start_dt
    ) as ROLLING

    on
      have.ID = ROLLING.ID
      and have.start_dt = ROLLING.start_dt

    order by
        id, start_dt
    ;

  %put NOTE: SQLOOPS = &SQLOOPS;
quit;

Внутреннее соединение похоже на пирамиду и по-прежнему включает много внутренних циклов.

0 голосов
/ 14 февраля 2019

Составной индекс для столбцов, к которым осуществляется доступ в объединенной таблице - id + start_dttm + hours - будет полезен, если его еще нет.

Использование msglevel=i будетраспечатать некоторые диагностики о том, как выполняется запрос.Это может дать некоторые дополнительные советы.

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