Friday, June 6, 2014

SYSCC - bridge between SAS and OS

SYSCC automatic macro variable plays important role in the communication between SAS and the operating environment.

To use it efficiently, please keep in mind the points at below:
1. SYSCC will take only integer value, even for decimal value. Furthermore, it will prompt error when non-numeric value is assigned.
42   %let syscc=-1.3;
43   %put &=syscc;
SYSCC=-1
44   %let syscc=test;
ERROR: The value supplied for assignment to the numeric automatic macro variable SYSCC was out of range or could not be converted to a numeric value.

2. SYSCC code will be translated to a return code on SAS exit. For more information, please see host-specific features of the SAS language.
3. SYSCC always take bigger numeric value, which means it retains highest error code in the end.
4. When SYSCC has error code (>=5), it does NOT stop executing any subsequent SAS statements while ABORT statement does.

Thursday, May 15, 2014

How to execute an operating environment command from SAS session

The topic has been discussed in so many SAS papers. Below are my considerations:

1. X statement / X command
It is the most popular one. I always use it in interactive mode. However, I would suggest that we should not use it in batch since it is difficult to control task and get status.

2. %SYSEXEC macro statement
As you know, macro string quoting always a challenge to SAS programmer. If there is any special character in the command, it may take you much time for troubleshooting. To make the code clean and readable, I prefer not using %SYSEXEC in our production code.

3. SYSTEM function / CALL SYSTEM routine
It works good with DATA step.

4. FILENAME statement PIPE engine
It is convenient to get the output of command.
Tip: to get the return code of the command, we can use this: FILENAME CMD PIPE "command; echo $?";

5. SYSTASK statement
It is my favorite one. It is because you will have more control on the command: execute many tasks in parallel, list tasks, kill task, and get status easily. Furthermore, it is the only way to execute command asynchronously.

Wednesday, May 14, 2014

colon(:) operator modifier - Note 2

WHERE statement is so useful tool to subset data. However, colon(:) operator modifier can not be used in WHERE statement.
Currently I can find some workarounds at below:
1.
PROC SQL truncated string comparison operators such as EQT, GTT, and LET:
They are undocumented operators because I can not find them in SAS document. For more information, please read SUGI paper 056-2009.

Please note that they have to be used in WHERE statement of PROC SQL.
2.
LIKE operator:
WHERE name like 'J%'; * all name with leading J character;

3.
To encapsulate the operator in FCMP:
proc fcmp outlib=work.funcs.trial;
   function func_in(a $, b $);
      if a =: b then result=1;
      else result=0;

      return (result);
   endsub;
run;

options cmplib=work.funcs;

data test;
    set sashelp.class;
    if func_in(name, 'J');
run;

Although they can not really replace the colon(:) operator modifier, they are useful to open your mind.

colon(:) operator modifier - Note 1

When string with different length are compared, the colon(:) operator modifier will be very helpful since it truncate longer string before comparison.
However, what is the "length"? Is it the actual length of of a non-blank character string (from LENGTH function)? or the amount of memory that is allocated for the string (from LENGTHM function)?

To clarify the question, please see the sample:
data a;
    length a1 a4 $10;
    a1 = 't';
    a4 = 'test';
    
    flag_a1 = ifc(a1=:'te', 'Y', 'N');
    flag_a4 = ifc(a4=:'te', 'Y', 'N');

    put a1= flag_a1=:;
    put a4= flag_a4=;
run;

Output:
a1=t flag_a1=N  (Explanation: a1 is not truncated because the memory length is 10. the implicit string comparison is 't' = 'te'.)
a4=test flag_a4=Y


Conclusion: the colon(:) operator modifier will truncate the longer string or pad pad with blank the shorter one.

Friday, October 4, 2013

Tip on Hash declaration

If you are familiar with hash programming, you must be familiar with the standard hash code template at below:
length c1 c2 $ 20;
if _n_=1 then do;
    declare hash h (dataset:"dsn");
    rc = h.definekey('n1', 'c1');
    rc = h.definedata('c2');
    rc = definedone();
    call missing(n1, c1, c2);
end;

Below is the SAS explanation: The hash object does not assign values to key variables (for example, h.find(key:'abc')), and the SAS compiler cannot detect the data variable assignments that are performed by the hash object and the hash iterator. Therefore, if no assignment to a key or data variable appears in the program, SAS issues a note stating that the variable is uninitialized.
So, we can have simpler and robust codes:
if _n_=1 then do;
    if 0 then set dsn (keep=n1 c1 c2); /* copy variable metadata from original dataset */
    declare hash h (dataset:"dsn");
    rc = h.definekey('n1', 'c1');
    rc = h.definedata('c2');
    rc = definedone();
end;

Friday, April 12, 2013

How to get duplicate records using PROC SORT?

I always read carefully "What's New" when SAS release any new product. It is fun when I find great tip to improve my SAS knowledge. In SAS 9.3, I think we should pay attention to PROC SORT.

Before 9.3, if we want to get all duplicate records in one step, we have to use PROC SQL. I admit that PROC SQL is a very great tool. However, It is not born for SAS dataset and may not have best performance when processing "HUGE" SAS dataset. With the two new NOUNIQUEKEY and UNIQUEOUT= options in SAS 9.3, PROC SORT has great improvement on handing duplicate records.

Below are the samples:
* Sample data;
data a;
 do i=1 to 3;
  output;
  output;
  output;
 end;

 i=5;
 output;
run;

* Note: UNI1 has all non-duplicate records while
 DUP1 has all duplicate records with same keys;
proc sort data=a out=dup1 nouniquekey uniout=uni1;
 by i;
run;

* Note: UNI2 will have 1st duplicate record while 
 DUP2 has all Other duplicate records with same keys;
proc sort data=a out=uni2 nodupkey dupout=dup2;
 by i;
run;

* Before 9.3;
proc sql noprint;
 create table dup3 as
  select *
  from a
  where i in 
  (
   select i from a
    group by i
   having count(i) > 1
  );
quit;

Thursday, April 11, 2013

Calculating leap year

SAS support give out one way to identify leap year:
Sample 44233: Calculating leap year using PROC FCMP and user-defined function

Below is another simple method:
/* check if Febuary has 28 or 29 days in the year */
data _null_;
 do year = 1997 to 2005;
  days = datdif(mdy(2,1,year), mdy(3,1,year), 'act/act');

  if days=29 then leap='Yes';
  else leap='No';

  put year= leap=;
 end;
run;