/*----------------------Web Analytics-----------------------------------------*/ /* WASUMMRY */ /* Use metadata to create text files that contain PROC SUMMARY steps. */ /* */ /*----------------------------------------------------------------------------*/ /* Supported By: Frank Roediger, FRROED; Kevin Scott, SASKXS */ /* */ /*----------------------------------------------------------------------------*/ /* History: */ /* Date Description Username Change Code */ /* 20040916 Program Created frroed NA */ /* 20060329 S0351214 frroed */ /* */ /*----------------------------------------------------------------------------*/ /* Dependencies: */ /* The following macros need to be available: */ /* GET_OBSERVATION_COUNT */ /* */ /* The following macro variables need to be defined and assigned values */ /* in the invoking environment: */ /* &WAB_ERROR: the text that identifies WA macro errors. */ /* */ /*----------------------------------------------------------------------------*/ /* Parameters: */ /* META_DSN: the name of the metadata dataset */ /* OUT_SUMMARY_FILE: the prefix name of the text file that contains the */ /* PROC SUMMARY steps (a numerical suffix and .sas is */ /* appended by the macro so that each PROC step is in */ /* its own text file). */ /* RETCODE: mechanism for passing Return Codes */ /* SUMMCNT: mechanism for passing # of PROC SUMMARY steps */ /* */ /*----------------------------------------------------------------------------*/ /* Input: */ /* (meta_dsn= */ /* ,out_summary_file= */ /* ,retcode= */ /* ,summcnt= */ /* ); */ /* */ /*----------------------------------------------------------------------------*/ /* Output: */ /* */ /*----------------------------------------------------------------------------*/ /* Copyright (c) 2004 SAS Institute Inc. All Rights Reserved */ /*----------------------------------------------------------------------------*/ %macro wasummry(meta_dsn= ,out_summary_file= ,summcnt= ,retcode= ); %******************************************************************************; %* create macro variables to drive the PROC SUMMARY Engine ; %******************************************************************************; data &temp_lib..class_vars /* cvt to uniq count to dimension the _type_ mask */ (keep=class_var) &temp_lib..output_stmts /* cvt to uniq cnt, dim OUTPUT_STMTS array */ (keep=input_table summary_stmt_opts where_stmt); set &meta_dsn; by input_table summary_stmt_opts where_stmt; length class_var $ 32; if _n_ eq 1 then call symput('initial_summarization',put(partition_by_date,1.)); %* unstring the CLASS_VAR entries ; display_str=lowcase(class_vars); do while (display_str gt ' '); class_var=scan(display_str,1,','); output &temp_lib..class_vars; stop_pos=index(display_str,','); if stop_pos then display_str=substr(display_str,(stop_pos+1)); else display_str=' '; end; output &temp_lib..output_stmts; run; proc sort data=&temp_lib..class_vars out =&temp_lib..class_uniq nodupkeys; by class_var; run; %if %get_observation_count(indsn=&temp_lib..class_uniq) eq 0 %then %do; %let dim_type_mask=1; %end; %else %do; %let dim_type_mask=%get_observation_count(indsn=&temp_lib..class_uniq); %end; proc freq data=&temp_lib..output_stmts noprint; tables input_table*summary_stmt_opts*where_stmt / out=&temp_lib..freqout (drop=percent); run; %******************************************************************************; %* ORDER=FREQ does not sequence the data by overall descending count when ; %* there is more than one variable in the TABLES statement. To order the ; %* data by overall descending count, you need an independent PROC SORT. ; %******************************************************************************; proc sort data=&temp_lib..freqout; by descending count; run; data _null_; set &temp_lib..freqout; call symput('dim_array',strip(put(count,z3.))); stop; run; %******************************************************************************; %* each set of INPUT_TABLE/SUMMARY_STMT_OPTS/WHERE_STMT will have its own ; %* PROC SUMMARY step and be in its own file. the following DATA step ; %* creates the DSNs for the text files that will contain the PROC SUMMARY ; %* steps. ; %******************************************************************************; data &temp_lib..filenames; set &meta_dsn end=eof; by input_table summary_stmt_opts where_stmt; length name $ 1024; %***************************************************************************; %* in the subsequent DATA _NULL_ step, it is necessary to have info about ; %* whether a PROC SUMMARY step has OUTPUT OUT= data sets that are ; %* 1) all based on a _TYPE_ mask, 2) all not based on a _TYPE_ mask ; %* (one-row summaries), or 3) a combination of 1 and 2. These are the ; %* three values of the FLAG categorical variable. ; %***************************************************************************; retain type_mask_sw no_type_mask_sw ; if first.where_stmt then do; type_mask_sw=0; flag=0; no_type_mask_sw=0; wab_num_steps+1; end; if not type_mask_sw and class_vars gt ' ' then type_mask_sw=1; if not no_type_mask_sw and class_vars eq ' ' then no_type_mask_sw=1; if last.where_stmt then do; name="&out_summary_file" || strip(put(wab_num_steps,z3.)) || '.sas'; if type_mask_sw then flag+1; if no_type_mask_sw then flag+2; output; end; if eof then call symput('_wab_num_steps_',strip(put(wab_num_steps,5.))); keep name flag ; run; %let &summcnt=&_wab_num_steps_; %******************************************************************************; %* create the text files that contain the PROC SUMMARY steps. ; %* there will be one text file for each PROC SUMMARY step. at the end of ; %* each PROC SUMMARY step, there will be a set of PROC APPEND steps to ; %* concatenate the PROC SUMMARY steps outputs to the "master" SUMMARY ; %* outputs. the entire file is set up as a macro and its final line ; %* is a macro invocation. this "macrofication" process was used so that ; %* the PROC APPEND steps could be executed provisionally and bypassed if ; %* the associated PROC SUMMARY step did not execute cleanly. ; %******************************************************************************; %if &initial_summarization eq 1 %then %do; data _null_; set &meta_dsn; by input_table summary_stmt_opts where_stmt; retain concat_class_vars concat_types concat_anal_vars concat_wt_vars concat_freq_vars concat_id_vars output_stmt001-output_stmt&dim_array output_tbl001-output_tbl&dim_array type_mask001-type_mask&dim_array type_mask_flag osfilename ; length out_rec concat_target concat_class_vars concat_wt_vars concat_freq_vars concat_id_vars concat_anal_vars output_stmt001-output_stmt&dim_array $4096 concat_types display_str $8192 output_tbl001-output_tbl&dim_array $41 type_mask001-type_mask&dim_array $&dim_type_mask display $80 varname $32 osfilename $1024; array output_stmts {1:&dim_array} $ output_stmt001-output_stmt&dim_array; array output_tbls {1:&dim_array} $ output_tbl001-output_tbl&dim_array; array type_masks {1:&dim_array} $ type_mask001-type_mask&dim_array; if first.where_stmt then do; link filenames; file procstep filevar=osfilename; %* create the MACRO statement ; put '%macro inclsmry(retcode=);' / '%let &retcode=0;'; %* create the PROC SUMMARY statement ; out_rec='proc summary data=' || strip(input_table) || ' ' || strip(where_stmt) || ' ' || strip(summary_stmt_opts); %* make sure that CHARTYPE is a SUMMARY statement option ; %* (it is needed for _TYPE_ mask) ; if not index(upcase(out_rec),'CHARTYPE') then out_rec=strip(out_rec) || ' chartype;'; else out_rec=strip(out_rec) || ';'; put out_rec /; array_index=0; concat_class_vars=' '; class_var_cnt=0; concat_types=' '; concat_anal_vars=' '; concat_wt_vars=' '; concat_freq_vars=' '; concat_id_vars=' '; do i=1 to &dim_array; output_stmts{i}=' '; type_masks{i}=' '; end; end; file procstep filevar=osfilename; %* create an entry in the OUTPUT_STMTS array ; array_index+1; output_tbls{array_index}=strip(output_table); output_stmts{array_index}='output out=' || strip(output_table); if partition_by_date then output_stmts{array_index}=strip(output_stmts{array_index}) || '_X'; output_stmts{array_index}=strip(output_stmts{array_index}) || '('; %* add a KEEP statement if it is not already in SUMMARY_OUTDSN_OPTS ; if not index(upcase(summary_outdsn_opts),'KEEP') then do; summary_outdsn_opts=strip(summary_outdsn_opts) || ' keep='; if id_vars gt ' ' then summary_outdsn_opts=strip(summary_outdsn_opts) || strip(translate(id_vars,' ',',')); if class_vars gt ' ' then summary_outdsn_opts=strip(summary_outdsn_opts) || ' ' || strip(translate(class_vars,' ',',')); if analysis_vars gt ' ' then summary_outdsn_opts=strip(summary_outdsn_opts) || ' ' || strip(translate(analysis_vars,' ',',')); if freq_vars gt ' ' then summary_outdsn_opts=strip(summary_outdsn_opts) || ' ' || strip(translate(freq_vars,' ',',')); if weight_vars gt ' ' then summary_outdsn_opts=strip(summary_outdsn_opts) || ' ' || strip(translate(weight_vars,' ',',')); if class_vars gt ' ' or type_mask_flag in (1,3) then summary_outdsn_opts=strip(summary_outdsn_opts) || ' _type_'; end; %* create _type_ mask and embed it into WHERE clause (if one exists) unless ; %* there are no entries for CLASS_VARS. the OUTPUT OUT= statement when ; %* CLASS_VARS is blank does not have a _TYPE_ condition in its WHERE ; %* clause ; if class_vars gt ' ' or type_mask_flag in (1,3) then do; if index(upcase(summary_outdsn_where),'WHERE=(') then do; %* remove the initial characters of the WHERE clause -- they will be ; %* replaced when the required WHERE condition is added ; summary_outdsn_where="where=(_type_='" || '01'x /* placeholder for _TYPE_ mask */ || "' and " ||substr(strip(summary_outdsn_where),8); end; else do; %* _type_ mask is only needed feature in the WHERE clause ; summary_outdsn_where="where=(_type_='" || '01'x /* placeholder for _TYPE_ mask */ || "') "; end; end; else do; %* blank CLASS_VARS -- no change to metadata WHERE clause ; end; output_stmts{array_index}=strip(output_stmts{array_index}) || '02'x /* delimiter between OUTPUT clauses */ || strip(summary_outdsn_where) || '02'x /* delimiter between OUTPUT clauses */ || strip(summary_outdsn_opts) || ') ' /* closing paren for data set options */ || '02'x /* delimiter between OUTPUT clauses */ || strip(summary_output_opts) || ';'; %* initialize entrys _TYPE_ mask -- MAX class vars is &DIM_TYPE_MASK ; type_masks{array_index}=repeat('0',&dim_type_mask); %* add each CLASS_VAR entry if it is not already in CONCAT ; display_str=lowcase(class_vars); do while (display_str gt ' '); varname=','||strip(scan(display_str,1,',')); if not index(concat_class_vars,strip(varname)) then do; concat_class_vars=strip(concat_class_vars) || ',' || scan(display_str,1,','); class_var_cnt+1; end; %* locate entrys position in CONCAT_CLASS_VARS and flip its mask bit ; concat_target=substr(strip(concat_class_vars),2); %* jump col1 "," ; curr_class_var=strip(scan(display_str,1,',')); do i=1 to class_var_cnt; if curr_class_var eq scan(concat_target,i,',') then substr(type_masks{array_index},i,1)='1'; end; stop_pos=index(display_str,','); if stop_pos then display_str=substr(display_str,(stop_pos+1)); else display_str=' '; end; %* add CLASS_VARS to CONCAT_TYPES (if it is not already in it) ; display_str=lowcase(translate(class_vars,'*',',')); %* convert "," to "*" ; %* summaries without any CLASS vars need to have a TYPES of '( )' ; if display_str eq ' ' then display_str='( )'; %***************************************************************************; %* make sure that DISPLAY_STR is not a substring of an existing entry. ; %* the existing entry can be 1) embedded -- preceeded and followed by a ; %* comma, or 2) the final item -- preceeded by a comma and followed by a ; %* blank. NOTE: an initial item will be preceeded and followed by a comma, ; %* so it will be detected as an embedded item. ; %***************************************************************************; if not index(concat_types,','||strip(display_str)||',') and not index(concat_types,','||strip(display_str)||' ') then concat_types=strip(concat_types) || ',' || strip(display_str); %* add each ANALYSIS_VAR entry if it is not already in CONCAT ; display_str=lowcase(analysis_vars); do while (display_str gt ' '); %************************************************************************; %* make sure that the ANALYSIS_VAR entry is not a substring of an ; %* existing entry -- ANALYSIS_VAR entry is SESSION_COUNT and there is an ; %* existing entry for ONE_HIT_SESSION_COUNT. the existing entry can be ; %* 1) embedded -- preceeded and followed by a comma, or 2) the final ; %* item -- preceeded by a comma and followed by a space. NOTE: an ; %* initial item will be preceeded and followed by a comma, so it will be ; %* detected as an embedded item. ; %************************************************************************; if not index(concat_anal_vars ,','||strip(scan(display_str,1,','))||',' ) and not index(concat_anal_vars ,','||strip(scan(display_str,1,','))||' ' ) then concat_anal_vars=strip(concat_anal_vars) || ',' || scan(display_str,1,','); stop_pos=index(display_str,','); if stop_pos then display_str=substr(display_str,(stop_pos+1)); else display_str=' '; end; %* add each ID_VAR entry if it is not already in CONCAT ; display_str=lowcase(id_vars); do while (display_str gt ' '); if not index(concat_id_vars,strip(scan(display_str,1,','))) then concat_id_vars=strip(concat_id_vars) || ',' || scan(display_str,1,','); stop_pos=index(display_str,','); if stop_pos then display_str=substr(display_str,(stop_pos+1)); else display_str=' '; end; %* add each FREQ_VAR entry if it is not already in CONCAT ; display_str=lowcase(freq_vars); do while (display_str gt ' '); if not index(concat_freq_vars,strip(scan(display_str,1,','))) then concat_freq_vars=strip(concat_freq_vars) || ',' || scan(display_str,1,','); stop_pos=index(display_str,','); if stop_pos then display_str=substr(display_str,(stop_pos+1)); else display_str=' '; end; %* add each WEIGHT_VAR entry if it is not already in CONCAT ; display_str=lowcase(weight_vars); do while (display_str gt ' '); if not index(concat_wt_vars,strip(scan(display_str,1,','))) then concat_wt_vars=strip(concat_wt_vars) || ',' || scan(display_str,1,','); stop_pos=index(display_str,','); if stop_pos then display_str=substr(display_str,(stop_pos+1)); else display_str=' '; end; if last.where_stmt then do; %* create the ID statement ; if concat_id_vars gt ' ' then do; display_str=substr(concat_id_vars,2); %* jump col1 ","; put @ 4 'id' @; do while (display_str gt ' '); out_rec=scan(display_str,1,','); put @ 7 out_rec; stop_pos=index(display_str,','); if stop_pos then display_str=substr(display_str,(stop_pos+1)); else display_str=' '; end; put @ 6 ';' /; end; %* create the CLASS statement ; if concat_class_vars gt ' ' then do; display_str=substr(concat_class_vars,2); %* jump col1 ","; put @ 4 'class' @; do while (display_str gt ' '); out_rec=scan(display_str,1,','); put @ 10 out_rec; stop_pos=index(display_str,','); if stop_pos then display_str=substr(display_str,(stop_pos+1)); else display_str=' '; end; put @ 9 ';' /; end; %* create the TYPES statement ; if concat_types gt ' ' and concat_class_vars gt ' ' then do; display_str=substr(strip(concat_types),2); %* jump col1 ","; put @ 4 'types' @; do while (display_str gt ' '); out_rec=scan(display_str,1,','); put @ 10 out_rec; stop_pos=index(display_str,','); if stop_pos then display_str=substr(display_str,(stop_pos+1)); else display_str=' '; end; put @ 9 ';' /; end; %* create the VAR statement ; if concat_anal_vars gt ' ' then do; display_str=substr(concat_anal_vars,2); %* jump col1 ","; put @ 4 'var' @; do while (display_str gt ' '); out_rec=scan(display_str,1,','); put @ 8 out_rec; stop_pos=index(display_str,','); if stop_pos then display_str=substr(display_str,(stop_pos+1)); else display_str=' '; end; put @ 7 ';' /; end; %* create the FREQ statement ; if concat_freq_vars gt ' ' then do; display_str=substr(concat_freq_vars,2); %* jump col1 ","; put @ 4 'freq' @; do while (display_str gt ' '); out_rec=scan(display_str,1,','); put @ 9 out_rec; stop_pos=index(display_str,','); if stop_pos then display_str=substr(display_str,(stop_pos+1)); else display_str=' '; end; put @ 8 ';' /; end; %* create the WEIGHT statement ; if concat_wt_vars gt ' ' then do; display_str=substr(concat_wt_vars,2); %* jump col1 ","; put @ 4 'weight' @; do while (display_str gt ' '); out_rec=scan(display_str,1,','); put @ 11 out_rec; stop_pos=index(display_str,','); if stop_pos then display_str=substr(display_str,(stop_pos+1)); else display_str=' '; end; put @ 10 ';' /; end; %* create the OUTPUT statements ; do i=1 to array_index; %* insert the _type_ mask ; if type_mask_flag in (1,3) then display_str=tranwrd(output_stmts{i} ,'01'x ,substr(type_masks{i},1,class_var_cnt) ); else /* _type_ mask is not needed - replace place-holder with blank */ display_str=tranwrd(output_stmts{i} ,'01'x ,' ' ); %* strip off and print the OUTPUT dsn information ; stop_pos=index(display_str,'02'x); out_rec=substr(display_str,1,(stop_pos-1)); %* this segment of OUTPUT_STMTS{n} will not extend beyond col 80 ; put @ 4 out_rec; display_str=substr(display_str,(stop_pos+1)); %* strip off and print the OUTPUT dsn WHERE statement ; stop_pos=index(display_str,'02'x); if stop_pos gt 1 then do; %* STOP_POS=1 --> no WHERE clause ; out_rec=substr(display_str,1,(stop_pos-1)); %* divide OUT_REC so that it does not go past col 120 ; do while (length(out_rec) ge 120); display=substr(out_rec,1,120); if substr(out_rec,120,1) eq ' ' or substr(out_rec,121,1) eq ' ' then do; out_rec=left(substr(out_rec,121)); end; else do; %* adjust display so that it stops at a blank ; display=strip(reverse(display)); tempstop=index(display,' '); str1=reverse(substr(display,1,tempstop)); display=strip(reverse(substr(display,tempstop))); out_rec=strip(str1) || substr(out_rec,121); end; put @ 11 display; end; if out_rec gt ' ' then put @ 11 out_rec; end; display_str=substr(display_str,(stop_pos+1)); %* strip off and print the OUTPUT dsn options ; stop_pos=index(display_str,'02'x); out_rec=substr(display_str,1,(stop_pos-1)); %* divide OUT_REC so that it does not go past col 80 ; do while (length(out_rec) ge 70); display=substr(out_rec,1,70); if substr(out_rec,70,1) eq ' ' or substr(out_rec,71,1) eq ' ' then do; out_rec=left(substr(out_rec,71)); end; else do; %* adjust display so that it stops at a blank ; display=strip(reverse(display)); tempstop=index(display,' '); str1=reverse(substr(display,1,tempstop)); display=strip(reverse(substr(display,tempstop))); out_rec=strip(str1) || substr(out_rec,71); end; put @ 11 display; end; if out_rec gt ' ' then put @ 11 out_rec; display_str=substr(display_str,(stop_pos+1)); %* strip off and print the OUTPUT options ; out_rec=strip(display_str); %* divide OUT_REC so that it does not go past col 80 ; do while (length(out_rec) ge 70); display=substr(out_rec,1,70); if substr(out_rec,70,1) eq ' ' or substr(out_rec,71,1) eq ' ' then do; out_rec=left(substr(out_rec,71)); end; else do; %* adjust display so that it stops at a blank ; display=strip(reverse(display)); tempstop=index(display,' '); str1=reverse(substr(display,1,tempstop)); display=strip(reverse(substr(display,tempstop))); out_rec=strip(str1) || substr(out_rec,71); end; put @ 11 display; end; if out_rec gt ' ' then put @ 11 out_rec; display_str=substr(display_str,(stop_pos+1)); put; %* blank line between OUTPUT statements ; end; put 'run;' /; put '%if &syserr gt 4 %then %do;' / ' %put %unquote(&wab_error) The PROC SUMMARY step contains errors.;' / ' %let &retcode=1;' / '%end;'; %* only for PARTITION_BY_DATE=1: create DATA steps for OUT= data sets ; if partition_by_date then do; put '%else %do;'; do i=1 to array_index; out_rec='%if %sysfunc(exist(' || strip(tranwrd(output_tbls{i},'_X',' ')) || ')) ne 0 %then %do;'; put @ 4 out_rec; out_rec='data ' || strip(tranwrd(output_tbls{i},'_X',' ')) || ';'; put @ 7 out_rec; out_rec='set ' || tranwrd(output_tbls{i},'_X',' '); put @ 10 out_rec; out_rec=strip(output_tbls{i}) || '_X;'; put @ 14 out_rec; if index(upcase(output_stmts{i}),'SESSION_COUNT') then put @ 10 "label session_count='Session Count';"; put @ 7 'run;' / @ 4 '%end;' / @ 4 '%else %do;'; out_rec='data ' || strip(tranwrd(output_tbls{i},'_X',' ')) || ';'; put @ 7 out_rec; out_rec='set ' || strip(output_tbls{i}) || '_X;'; put @ 10 out_rec; if index(upcase(output_stmts{i}),'SESSION_COUNT') then put @ 10 "label session_count='Session Count';"; put @ 7 'run;' / @ 4 '%end;' /; end; put '%end;'; end; put '%mend inclsmry;' / '%inclsmry(retcode=wab_rc);'; end; return; filenames:; set &temp_lib..filenames; osfilename=strip(name); type_mask_flag=flag; return; run; %end; %* create PROC SQL step for Re-Summarizations ; %else %do; data _null_; set &meta_dsn; by input_table summary_stmt_opts where_stmt; length out_rec $80 osfilename $1024 token_var $32 string_var $32 memname $32 library $32 ; if first.where_stmt then do; link filenames; file procstep filevar=osfilename; %* create the MACRO statement ; put '%macro inclsmry(retcode=);' / '%let &retcode=0;'; end; file procstep filevar=osfilename; %************************************************************************; %* if the re-summarization is needed, a PROC SQL/Group By step is ; %* generated. if it is NOT needed, a PROC Datasets/Change step ; %* is generated to re-name the Initial Summarization output. ; %************************************************************************; if need_re_summary then do; %* create the PROC SQL step ; put @ 1 'proc sql;' /; out_rec='create table ' || strip(output_table) || '_Y as'; put @ 4 out_rec; out_rec='select ' || strip(scan(class_vars,1,',')); put @ 7 out_rec; do i=2 to (1+countc(class_vars,',')); out_rec=',' || strip(scan(class_vars,i,',')); put @ 13 out_rec; end; if id_vars gt ' ' then do; do i=1 to (1+countc(id_vars,',')); out_rec=',' || strip(scan(id_vars,i,',')); put @ 13 out_rec; end; end; do i=1 to (1+countc(analysis_vars,',')); out_rec=',sum(' || strip(scan(analysis_vars,i,',')) || ') as ' || strip(scan(analysis_vars,i,',')); put @ 13 out_rec; end; out_rec='from ' || strip(input_table); put @ 7 out_rec; out_rec='group by ' || strip(scan(class_vars,1,',')); put @ 7 out_rec; do i=2 to (1+countc(class_vars,',')); out_rec=',' || strip(scan(class_vars,i,',')); put @ 15 out_rec; end; if id_vars gt ' ' then do; do i=1 to (1+countc(id_vars,',')); out_rec=',' || strip(scan(id_vars,i,',')); put @ 15 out_rec; end; end; put @ 10 ';'; out_rec='quit;'; put / out_rec /; put '%if &syserr gt 4 %then %do;' / ' %put %unquote(&wab_error) The PROC SQL step contains errors.;' / ' %let &retcode=1;' / '%end;' //; end; else do; %*********************************************************************; %* create a DATA Step to drop _TYPE_ ; %*********************************************************************; out_rec='data ' || strip(output_table) || '_Y;'; put @ 1 out_rec; out_rec='set ' || strip(output_table) || ';'; put @ 4 out_rec; put @ 4 'drop _type_;' / @ 1 'run;' /; end; %************************************************************************; %* SORT and DATA steps for each x_TOKEN CLASS variable so that ; %* the corresponding text string is in the summarized data ; %************************************************************************; if index(class_vars,'_token') then do; do i=1 to (1+countc(class_vars,',')); if index(scan(class_vars,i,','),'_token') then do; token_var=scan(class_vars,i,','); string_var=tranwrd(token_var,'_token',' '); out_rec='proc sort data=' || strip(output_table) || '_Y;'; put @ 1 out_rec; out_rec='by ' || strip(token_var) || ';'; put @ 4 out_rec; put @ 1 'run;'; out_rec='data ' || strip(output_table) || '_Y;'; put @ 1 out_rec; out_rec='merge ' || strip(output_table) || '_Y (in=in_a)'; put @ 4 out_rec; out_rec='summary.hash_' || strip(string_var) || '_token;'; put @ 10 out_rec; out_rec='by ' || strip(token_var) || ';'; put @ 4 out_rec; put @ 4 'if in_a;' / @ 1 'run;' /; end; end; end; put '%mend inclsmry;' / '%inclsmry(retcode=wab_rc);'; return; filenames:; set &temp_lib..filenames; osfilename=strip(name); type_mask_flag=flag; return; run; %end; %mend wasummry;