Sunday, July 9, 2017

Random Libref macro function

Certain SAS procedures such as PROC DATASETS show examples with fixed library references or librefs such as source and dest as follows:

   proc datasets library = source ;
      copy out = dest ;
   run ;

A libref is 1 to 8 characters long, begins with a letter or underscore and contains only letters, numbers or underscores. If you use the SAS dictionary tables you will see that all libnames are stored in upper case.

What if the popularly named librefs of SORUCE and/or DEST are already in use and are not defined in metadata? Since a libref is simply a pointer to a folder (used by Windows and Linux), why not use a random value assigned to a macro variable to avoid any conflicts? The below macro function named %uuidgenlibref() does just that.

The SAS UUIDGEN() function returns a universally unique identifier (UUID) also know as a globally unique identifier (GUID) that looks like this: 83a6f563-67ee-2a47-8498-8f8dc706d0db. As you can see, the first character starts with an 8 which would violate the libref requirement to start with a letter. Here is an example of generating 10 UUIDs from a DO loop:

According to Wikipedia, "In its canonical textual representation, the sixteen octets of a UUID are represented as 32 hexadecimal (base 16) digits, displayed in five groups separated by hyphens, in the form 8-4-4-4-12 for a total of 36 characters (32 alphanumeric characters and four hyphens)."

The macro appends the first seven characters from the UUIDGEN() function using the SUBSTR() function to the 'X' prefix. Here is an example:

   %let sourcelibref = %uuidgenlibref() ;
   %let destlibref = %uuidgenlibref() ;

   libname &sourcelibref. "/lev1/abc" ;
   libname &destlibref. "/lev1/def" ;
   proc datasets library = &sourcelibref. ;
      copy out = &destlibref. ;
   run ;

   libanme &sourcelibref. clear ;
   libname &destlibref. clear ;   

The source consists of a single line of code as you can see below.

*     Program:
*      Author: Tom Bellmer
*     Created: 09Jul2017
*     Purpose: Creates an upper case 8 character libref
*       Usage: %put %uuidgenlibref() ;
%macro uuidgenlibref() ; 
   X%upcase( %substr( %sysfunc( uuidgen() ), 1, 7 ) ) 
%mend ; 
/*EOF: */

Wednesday, April 5, 2017

getvartypecount() macro function

In a prior post, I made mention of the isblank() and getattr() macro functions. I was hoping to find a way to return the number of character or numeric variables in a SAS data set using the ATTRN() SAS function. Alas, the ATTRN() function does not support what I desired neither and does DICTIONARY.TABLES (except in SAS 9.2 (TS2MO) per Dictionary Tables Reference Card where the num_character and num_numeric columns existed), so I wrote my own custom macro function.

The reason I wanted this was to dynamically set the dimension of an ARRAY that can only be a numeric constant, numeric expression or a constant. This concept works because the macro is resolved prior to the data step, this is the key to assigning the array dimension without an error. This macro function will probably tied into a subsequent post utilizing this function but here is a sneak peak at what I am trying to do:

data x ;
  if 0 then set sashelp.class ; /* Prime the PDV */
  array achar[ * ] _character_ ;
  array acount[ %getvartypecount( dsn = sashelp.class, type = C ), 2 ] ;

  /* .... more code goes here */

run ;

Below is the actual source code used to create the macro. Notice that it does use the %isblank() function referenced in the hyperlink of the first paragraph.

*     Program:
*      Author: Tom Bellmer
*     Created: 26Apr2017
* SAS Version: 9.3 (TS2M2)
*     Purpose: returns number of Character or Numeric columns in a data set  
*       Usage: %let x = %getvartypecount( dsn = sashelp.class, type = C ) ;
*       Notes: Great use to declare ARRAY dimensions since they expect
*              a numeric constant, numeric expression or constant.  Macros
*              are resolved prior to data step code so this works.
%macro getvartypecount( dsn =, type = C ) ;
  %local dsid retval i type ;
  %if %isblank( &dsn. ) %then %do ;
    %put %str(E)RROR: the DSN value is missing ;
    %return ;
  %end ;
  %let retval = 0 ;
  %let type = %upcase( &type. ) ;
  %if &type. = C or &type. = N %then %do ;
    %let dsid = %sysfunc( open( &dsn. ) ) ;
    %if &dsid. %then %do ;
      %do i = 1 %to %sysfunc( attrn( &dsid., nvars ) ) ;
        %if &type. = %sysfunc( vartype( &dsid., &i. ) ) 
         %then %let retval = %eval( &retval. + 1 ) ;
      %end ;
      %let dsid = %sysfunc( close( &dsid. ) ) ;
    %end ;
    %else %put %str(E)RROR: Invalid data set name &dsn. ;
  %end ;
  %else %put %str(E)RROR: Only valid TYPE= parameters are C or N ;
%mend ;
/* EOF:  */