SAS: поиск хэш-таблицы с использованием индексированного столбца в качестве подсказки - PullRequest
0 голосов
/ 29 сентября 2018

На работе у меня есть таблица заявок на 200 миллионов строк (300+ столбцов, ~ 200 ГБ), которая индексируется по номеру члена (memno) и дате обслуживания (dos).

Дважды в месяц мне приходится извлекать 50 000 заявок на номера участника из фиксированного диапазона дат (скажем, с 01.01.2017 по 01.01.2008), где мне нужны только ограниченные столбцы из заявок.

MyInputList имеет только 1 столбец (memno).

proc sql;
    create table myClaims as
    select a.claimno, a.dos, a.memno
    from s.claims a inner join myInputList b
       on a.memno = b.memno
    where a.dos between '01Jan2017'd and '01May2018'd;
quit;

Обычно для запуска PROC SQL требуется около 3-4 часов.Сами данные не хранятся в RDMS, я прочитал много эссе SAS о том, что PROC SQL представляет собой декартово произведение, и, поскольку мне не нужны все 300 столбцов на запись, мне интересно, будет ли лучше использовать хеш-таблицу.

Мой вопрос: могу ли я дать «подсказки» хеш-таблице, чтобы она могла использовать индексированные столбцы (memno, dos)?

data myClaimsTest (drop=rc);
if 0 then set myInputList;

declare hash vs(hashexp:7, dataset:'myInputList');
vs.definekey('memno');
vs.definedata();
vs.definedone();

do until (eof);
   set s.claims (keep=claimno dos) end=eof;
   if vs.find()=0 then output;
end;

stop;
run;

Новый раздел (добавленный Ричардом)

Запустите этот код, чтобы получить список переменных и индексов.

dm "clear output"; ods listing; ods noresults; options nocenter; title;
proc contents varum data=all_claims;
run;
dm "output" output; ods results;

Скопируйте и вставьте нижнюю часть вывода здесь.Замените этот пример фактическим списком.

        Variables in Creation Order

#    Variable      Type    Len    Format

1    claim_id      Num       8
2    member_id     Num       8
3    claim_date    Num       8    YYMMDD10.


       Alphabetic List of Indexes and Attributes

                          # of
              Unique    Unique
#    Index    Option    Values    Variables

1    PICK     YES       333338    member_id claim_date

Ответы [ 2 ]

0 голосов
/ 30 сентября 2018

Я добавил второй ответ с несколько иными условиями проблемы - у участника может быть несколько заявок в данный день, и есть только простые индексы с одной переменной.

Создание данных с несколькими заявкамина члена / день

%let MEMBER_N = 1e5;
%let CLAIM_RATE = 0.00125;
%let MULTI_CLAIM_RATE = 0.05; %* iterative rate at which another claim is made on same day a claim is made;
%let STUDY_PROPORTION = 0.001;

data ALL_CLAIMS
( label = "BIG"
  index=
  ( 
/*    PICK = (member_id claim_date) / unique (not happening) */
    member_id
    claim_id
  )
);
  retain claim_id 0 member_id 0 claim_date 0 member_n 0;
  format claim_date yymmdd10.;

  do member_id = 1e7 by 1;
    claim_n = 1;
    do claim_date = '01jan2012'd to '31dec2018'd;
      if ranuni(123) > &CLAIM_RATE then continue;
      if claim_n = 1 then member_n + 1;

      do multi_n = 0 by 1 until (ranuni(123) > &MULTI_CLAIM_RATE); 
        claim_id + 1;
        output;
      end;

      if multi_n > 1 then put 'NOTE: ' member_id= claim_date= multi_n 'claims';

      claim_n + 1;
    end;

    if member_n = &MEMBER_N then leave;
  end;

  stop;

  drop member_n claim_n;
run;

Использование индекса claim_date для предварительного отбора заявок кандидатов, вероятно, не поможет - вы можете получить десятки тысяч заявок в катастрофический день, ваша обработка будет иметьчтобы выполнить итерацию по диапазону дат, установите заявки по дате сопоставления и выполните поиск по хэшу (SMALL: member_id) для каждой записи заявки_идентификации, встречающейся в эти даты сопоставления.Вам придется поэкспериментировать, чтобы увидеть, действительно ли этот антиинтуитивный подход сработает лучше для ваших конкретных ALL и SMALL.

Если вы изучите журнал для SQL, вы увидите, что оптимизатор запросов решит использоватьmember_id index (и внутренне будет перебирать найденные строки для применения предложения where).Недокументированные параметры Proc SQL _method и _tree могут показать вам, что он будет делать - см. "Проект оптимизатора SQL: _Method и _Tree in SAS®9.1" Lavery (SUGI 30).

proc sql _method _tree;
  create table ALL_STUDY_SUBSET as
  select ALL.claim_id, ALL.claim_date, ALL.member_id
  from ALL_CLAIMS ALL inner join STUDY_MEMBERS STUDY
    on ALL.member_id = STUDY.member_id
  where ALL.claim_date between '01Jan2017'd and '01May2018'd
  ;
quit;

выдержка из журнала

INFO: Index member_id of SQL table WORK.ALL_CLAIMS (alias = ALL) selected for SQL WHERE clause
      (join) optimization.

NOTE: SQL execution methods chosen are:

      sqxcrta
          sqxjndx
              sqxsrc( WORK.STUDY_MEMBERS(alias = STUDY) )
              sqxsrc( WORK.ALL_CLAIMS(alias = ALL) )

Tree as planned.
                               /-SYM-V-(ALL.claim_id:1 flag=0001)
                     /-OBJ----|
                    |         |--SYM-V-(ALL.claim_date:3 flag=0001)
                    |          \-SYM-V-(ALL.member_id:2 flag=0001)
           /-JOIN---|
          |         |                              /-SYM-V-(STUDY.member_id:1 flag=0001)
          |         |                    /-OBJ----|
          |         |          /-SRC----|
          |         |         |          \-TABL[WORK].STUDY_MEMBERS opt=''
          |         |--FROM---|
          |         |         |                    /-SYM-V-(ALL.claim_id:1 flag=0001)
          |         |         |          /-OBJ----|
          |         |         |         |         |--SYM-V-(ALL.claim_date:3 flag=0001)
          |         |         |         |          \-SYM-V-(ALL.member_id:2 flag=0001)
          |         |          \-SRC----|
          |         |                   |--TABL[WORK].ALL_CLAIMS opt=''
          |         |                   |          /-NAME--(claim_date:3)
          |         |                    \-IN-----|
          |         |                             |                    /-LITN(20820) DATE.
          |         |                             |          /-RANB---|
          |         |                             |         |          \-LITN(21305) DATE.
          |         |                              \-SET----|
          |         |--empty-
          |         |          /-SYM-V-(STUDY.member_id:1)
          |          \-CEQ----|
          |                    \-SYM-V-(ALL.member_id:2)
 --SSEL---|

и эквивалент шага DATA

data ALL_STUDY_SUBSET5(label="Presuming a preponderance of members file few claims over their all_claims lifetime");
  set STUDY_MEMBERS;

  do until (_iorc_);
    set ALL_CLAIMS key=member_id;
    if _iorc_ = 0 and '01jan2017'd <= claim_date <= '01may2018'd then do;
      OUTPUT;
    end;
  end;

  _error_ = 0;
run;

Все еще медленно?

КогдаВозникает ситуация, когда лучшие усилия и лучшие результаты программирования недостаточно быстры, вам придется искать улучшения с помощью системных ресурсов:

  • Можете ли вы добавить новый составной индекс, используя (member_id claim_date)?
  • Можете ли вы переместить таблицу наборов данных на более быстрый диск?Например:
    • Обеспечить отсутствие сетевых взаимодействий с данными
    • Дефрагментация
    • Заменить 5400 об / мин на 15000 об / мин
    • Заменить вращающийся диск на SSD SATA
    • Замена вращающегося SATA или SSD-диска на NVMe
    • SASFILE
      • Рабочая станция / сервер с большим объемом оперативной памяти (> 200 ГБ)
    • Перемещение данных в облако/ удаленное / решение для хранилища больших данных с большим потенциалом для автоматического распараллеливания и увеличения ресурсов по требованию
0 голосов
/ 30 сентября 2018

Предположим, BIG - это ваша индексированная таблица SAS на 200 ГБ, а SMALL - это строка с критериями выбора строки 50K.

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

Существует два варианта обработки

  1. BIG полного сканирования, тестируйте каждыйзапись с помощью поиска в SMALL или
  2. Обработка каждой записи SMALL, выполнение индексированного извлечения из BIG

Хэш-код в вашем вопросе соответствует # 1, а соединение SQL - # 2, хотя SQL может выбрать вариант # 1.

Вот пример создателя данных

%let MEMBER_N = 1e5;
%let CLAIM_RATE = 0.00125;
%let MEMBER_SAMPLE_N = 1e2;
%let STUDY_PROPORTION = 0.001;

data ALL_CLAIMS
( label = "BIG"
  index=
  ( 
    PICK = (member_id claim_date) / unique
  )
);
  retain claim_id 0 member_id 0 claim_date 0 member_n 0;
  format claim_date yymmdd10.;

  do member_id = 1e7 by 1;
    claim_n = 1;
    do claim_date = '01jan2012'd to '31dec2018'd;
      if ranuni(123) > &CLAIM_RATE then continue;
      claim_id + 1;
      if claim_n = 1 then member_n + 1;
      output;
      claim_n + 1;
    end;

    if member_n = &MEMBER_N then leave;
  end;

  stop;

  drop member_n claim_n;
run;

%put note: sample population is %sysevalf(5e4/200e6*100)% of all claims;
%put note: or ~%sysevalf(5e4/200e6*1e6) rows in this example;

data STUDY_MEMBERS(keep=member_id label="SMALL");
  * k / n selection method, Proc SURVEYSELECT is better but not always available;
  * an early sighting on SAS-L would be https://listserv.uga.edu/cgi-bin/wa?A2=ind9909c&L=SAS-L&P=173979
  * Re: Random Selection (Sep 20, 1999);

  retain 
    k %sysevalf(&MEMBER_N*&STUDY_PROPORTION, FLOOR) 
    n &MEMBER_N
  ;

  set ALL_CLAIMS;
  by member_id;

  if first.member_id;

  if ranuni(123) < k/n then do;
    output;
    k + (-1);
  end;

  n + (-1);

  if n=0 then stop;
run;

и кода обработки

options msglevel=i;

proc sql;
  create table ALL_STUDY_SUBSET as
  select ALL.claim_id, ALL.claim_date, ALL.member_id
  from ALL_CLAIMS ALL inner join STUDY_MEMBERS STUDY
    on ALL.member_id = STUDY.member_id
  where ALL.claim_date between '01Jan2017'd and '01May2018'd
  ;
quit;

* extend study data with a date variable that matches the data variable in the ALL index; 

data STUDY_MEMBERS_WITH_ITERATED_DATE;
  set STUDY_MEMBERS;
  do claim_date = '01Jan2017'd to '01May2018'd;
    output;
  end;
run;

* join on both variables in ALL key;

proc sql;
  create table ALL_STUDY_SUBSET2 as
  select ALL.claim_id, ALL.claim_date, ALL.member_id
  from ALL_CLAIMS ALL inner join STUDY_MEMBERS_WITH_ITERATED_DATE STUDY
    on ALL.member_id = STUDY.member_id
   and ALL.claim_date = STUDY.claim_date
  ;
quit;

* full scan with hash based match;

data ALL_STUDY_SUBSET3;
  SET ALL_CLAIMS;

  if _n_ = 1 then do;
    declare hash study (dataset:'STUDY_MEMBERS');
    study.defineKey('member_id');
    study.defineDone();
  end;

  if '01jan2017'd <= claim_date <= '01may2018'd;

  if study.find() = 0; 
run;

* SMALL scan with iterated dates to complete info to allow BIG index (key)
* to be used;

data ALL_STUDY_SUBSET4;
  set STUDY_MEMBERS;

  do claim_date = '01jan2017'd to '01may2018'd;
    set ALL_CLAIMS key=pick;
    if _iorc_ = 0 then output;
  end;

  _error_ = 0;
run;
...