SAS Proc SQL получает записи с датой, ближайшей к определенной дате

У меня есть 2 таблицы, как показано ниже:

Таблица 1, таблица списка пользователей:

Year  Month Id Type 
2010  3     1  A
2010  5     2  B
2010  10    1  A
2010  12    1  A

В таблице 2 описана история продвижения пользователей:

Promote Date Id
2/20/2010    1
5/20/2010    1     (4/2010 the user got demoted, and after 1 month he got promote again)

Из этих двух таблиц мне нужно создать таблицу результатов, которая нравится таблице 1, но добавить столбец, который классифицирует пользователя с типом A, который был повышен за последние 3 месяца или более 3 месяцев в определенную дату. Например, результаты будут такими:

Year  Month Id | Duration
2010  3     1  | A < 3 months
2010  10    1  | A > 3 months
2010  12    1  | A > 3 months    

Общая идея была бы такой:

  • Мне нужно преобразовать столбец месяца и столбца года из таблицы 1 в формат даты, например 3/2010
  • вычтите новое преобразованное значение с ближайшей датой продвижения к указанной выше дате (2/2010), чтобы получить количество дней, в течение которых пользователь был продвинут
  • сравните с 90 днями, чтобы определить продолжительность его повышения

Есть 2 проблемы, с которыми я сейчас застрял.

Я не знаю, как лучше всего преобразовать столбец месяца и столбца года в формат даты месяц / год.

Предполагая, что я уже преобразовал столбец месяца / года из таблицы 1, я использую функцию Max, чтобы получить ближайшую дату из таблицы 2. Насколько мне известно, функция max плохо сказывается на производительности, поэтому есть ли другое решение вместо использования max? В mysql это легко решить, используя Limit 1, но SAS proc-sql не поддерживает Limit. Есть ли эквивалент ограничения в proc-sql? Ниже приведен код, о котором я сейчас думаю.

PROC SQL;
Create table Result as SELECT table1.Year, table1.Month, table1.Code, 
(Case When table1.Type = "B" then "B"
When table1.Type = "A" AND (table1.Date - (Select MAX(table2.Date) From table2 Where table2.Date <= table1.Date AND table2.Id = table1.Id ) < 90) THEN "A < 3 months"
When table1.Type = "A" AND (table1.Date - (Select MAX(table2.Date) From table2 Where table2.Date <= table1.Date AND table2.Id = table1.Id ) >= 90) THEN "A > 3 months"
When table1.Type = "C" then "C"
end) as NewType
From table1
LEFT JOIN
// .... 
;
QUIT;

Как видите, мне нужно присоединиться к таблице table1 с другими таблицами, поэтому я использую подзапрос, который также является плохой производительностью, но я не знаю, есть ли другой способ. Помощь и совет приветствуются.


person Luke Henz    schedule 02.03.2013    source источник


Ответы (1)


Вы можете создать значение даты из него, используя функцию mdy(), например:

data have;
input Year  Month Id Type $;
datalines;
2010  3     1  A
2010  5     2  B
2010  10    1  A
2010  12    1  A
;
run;

data have;
set have;
format date date9.;
date = mdy(Month, 1, Year);
run;

У вас нет значения дня, поэтому я просто использовал 1 (каждая созданная дата - первое число месяца).

Теперь вы можете объединить две таблицы по идентификатору и вычислить разницу между датой в первой таблице и датой продвижения во второй таблице:

proc sql;
    create table want as
    select *
          ,abs(date - promote) as diff
    from have as a
           left join
         prom as b
           on a.id = b.id;
quit;

После этого вы сортируете итоговую таблицу по идентификатору, дате и разнице:

proc sort data=want;
by id date diff; 
run;

После сортировки набор данных выглядит так:

Year  Month  Id  Type   date       Promote    diff
---------------------------------------------------
2010  3      1   A      01MAR2010  20FEB2010  9
2010  3      1   A      01MAR2010  20MAY2010  80
2010  5      2   B      01MAY2010  .          .
2010  10     1   A      01OCT2010  20MAY2010  134
2010  10     1   A      01OCT2010  20FEB2010  223
2010  12     1   A      01DEC2010  20MAY2010  195
2010  12     1   A      01DEC2010  20FEB2010  284

Последний шаг, выполните итерацию по набору данных и проверьте, является ли первое значение diff для каждого значения идентификатора и даты меньше или больше 3 месяцев (я только что проверил 90 дней, вы также можете использовать функцию intck). Поскольку мы отсортировали набор данных по идентификатору, дате и разнице, первая строка должна быть ближайшей к дате, поэтому вы output только первую строку.

data want2(keep = year month id type duration);
set want;
by date;

if first.date and Type = 'A' then do;


if diff lt 90 then do;
    duration = 'A < 3 months';
    output want2;
end;
if diff gt 90 then do;
    duration = 'A > 3 months';
    output want2;

    end;
end;
else if first.date  then do;
    duration = type;
    output want2;
end;

run;

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

Это конечный результат:

Year    Month    Id    Type    duration
--------------------------------------------
2010    3        1     A       A < 3 months
2010    5        2     B       B
2010    10       1     A       A > 3 months
2010    12       1     A       A > 3 months
person Dejan Peretin    schedule 02.03.2013
comment
Большое спасибо за подробный ответ. Что касается значения дня, есть ли способ сделать значение дня последним днем ​​месяца? Последний блок кода, не могли бы вы указать мне, где находится команда для итерации? И если мы воспользуемся командой вывода, создаст ли она желаемую таблицу результатов? - person Luke Henz; 02.03.2013
comment
Вы можете использовать функцию intnx, чтобы переместить значение даты в конец месяца следующим образом: intnx('month', mdy(Month, 1, Year), 0, 'end'). Нет необходимости в команде «итерация», существует неявный цикл по строкам набора данных, когда вы используете его в операторе set. Я собираюсь отредактировать ответ, чтобы лучше проиллюстрировать, что здесь происходит. - person Dejan Peretin; 02.03.2013
comment
Извините за поздний ответ. Я попробовал ваш ответ, и он отлично работает. Я немного изменил его, чтобы он соответствовал моим потребностям. Большое спасибо за подробный ответ. - person Luke Henz; 04.03.2013