Time to trade in your jalopy macro?

9

Suppose you have an old jalopy that's perfectly reliable.  Your jalopy gets you where you wanna go: no frills; no drama.

Do you trade your old wheels in for a racecar that accelerates like crazy and corners like it's on rails?

Or stick with what's old and comfortable?   Your choice depends on whether you need the extra speed and capability and, of course, the price.

Here's a jalopy macro that issues SQL code to populate a macro variable with an observation count from a selected dataset:

%macro numobs(dsn);
   %let dsn=%upcase(&dsn);
   %global numobs;
   proc sql noprint;
	select nobs into :numobs
	   from dictionary.tables
		where libname="%scan(&dsn,1)" & memname="%scan(&dsn,2)";
   quit;
%mend numobs;

This jalopy macro does the same thing using the DATA step instead of SQL:

%macro numobs(dsn);
   data _null_;
      call symputx('numobs', number,'G');
      stop;
      set &dsn nobs=number;
   run;
%mend numobs;

Both jalopy macros work just fine.   Why should you trade either one for a racecar macro?   If I were a slick car salesman, or a slick macro salesman, here's what I would say:

The jalopy macros work, but they're indirect, inefficient, and inflexible.

How so?   Because they both place sascode on the input stack.

Why is that indirect and inefficient?  Because, once on the input stack, sascode must be

  1. tokenized
  2. compiled
  3. executed

Why are they inflexible?

Because they cannot be called from within SAS code.  For example, you cannot do this:

TITLE  "%numobs(orion.order_fact) observations";

In addition, they cannot be called in-line within a macro language statement.  For example, you cannot do this:

%if %numobs(orion.order_fact)>10 %then %do;

Neither jalopy macro works like a macro function.   A macro function returns a value and can be called anywhere.  A macro function can also be called in-line.

So how do I turn a jalopy macro into a tire-squealing racecar macro?

  1. Eliminate all SAScode
  2. Replace the SAScode with pure macro language code
  3. Use SAS I/O functions as needed

Using the above technique, you can create your own macro utility functions that return whether a dataset exists, the number of observations in a dataset, the number of observations in a subset, the age of a dataset, a SAS variable's type, a SAS variable's label, a formatted value of a macro variable, etc.  To learn more, attend our tire-squealing new course ... SAS Macro Language 2:  Advanced Techniques.

Share

About Author

Jim Simon

Principal Technical Training Consultant

Jim Simon is a principal instructor and course developer for SAS Education. Jim has a bachelor's degree from UCLA and a master's degree from California State University at Northridge. Prior to joining the SAS Irvine office in 1988, Jim was an instructor at Ventura College and a SAS programmer at The Medstat Group in Santa Barbara. Jim's areas of specialization include the DATA step, application development, web enablement, and the SAS macro language. A native of Southern California, Jim enjoys anything in the warm California sun. On weekends, Jim loves jumping in his Corvette, turning up the stereo, and cruising Pacific Coast Highway, top down, South to Laguna Beach or North to his old home town, Santa Barbara.

Related Posts

9 Comments

  1. Anders Sköllermo on

    1) The code for "No. of obs" can sometimes be shortened to (without any error handling) :
    %PUT "Antal obs:" %sysfunc(attrn(%sysfunc(open(test)),nobs));

    2) A question: Is there any good way in Windows Explorer to see "No. of obs" and "No of var" in a SAS Table ? (without using SAS Universal Viewer)

    Many years ago we had a command on IBM ISPF 3.4 panel called SASTAB, which told us all the SAS tablenames and "No of var" and "No of obs", in each SAS table.
    / Br Anders Sköllermo (Andrew Skollermo in English)

    • 1) This works. Two suggestions: a) follow with the CLOSE function; b) remove the quotation marks.

      2) Sorry, I do not know of a way.

      Thanks, Anders, for posting.
      jim

  2. I'm disappointed that you would tease us with a problem and not provide the solution. I presume that the course would teach some variant of http://support.sas.com/kb/24/671.html , such as the following.

    %macro numobs(ds);
     %global dset nobs;
     %let dset=&ds;
     %let dsid = %sysfunc(open(&dset));
     %if &dsid %then %do;
        %let nobs =%sysfunc(attrn(&dsid,nobs));
        %let rc = %sysfunc(close(&dsid));
        &nobs;
     %end;
     %else .;
    %mend numobs;
     
    data a;
    NCars =%numobs(sashelp.cars);
    NClass =%numobs(sashelp.class);
    run;
    proc print;run;
    • Yes, very much like your example, Rick. Using many of the same tools. Only slicker. More robust. And more versatile. With multiple examples.

      And, yes, I admit, my post is a bit of a tease. I want you to come to class to learn this trick and many more. Or attend my SGF advanced macro pre-conference tutorial in two months. : )

  3. The other alternative is to not use macros at all - no code substitution, tokenization, etc - and instead create a user-defined function using PROC FCMP..

    • Thank you, Andrew, for your post. PROC FCMP is indeed a wonderful tool, but is not always a macro replacement. A call to a PROC FCMP created function must be tokenized, compiled, and executed just like any other sascode. The compiled PROC FCMP created function must also execute. This usually occurs on a row-by-row basis while reading a SAS dataset. A macro function, on the other hand, excutes one-time-only when called, so it is potentially far more efficient. Finally, a PROC FCMP created function can be called wherever a function can be called, while a macro function can be called anywhere; within a TITLE statement, for example.

      • Peter Crawford on

        surprised to hear The Suggestion that a function cannot execute in a title statement?
        Isn't that the "new" feature introduced only 20+ years ago in SAS9.07e?
        (if you haven't guessed or don't quite recall, that feature was
        Title "%sysfunc(almost_any_function(),format_for_numeric_function_results)";
        which might be a great way of placing time stamp and metadata into report endnotes.

        /end.smug.smiley

Back to Top