1

I want to define nrow to be able to call:

%put %nrow(sashelp.cars);

Maybe there's an existing function or some straightforward solution but I haven't been able to find it.

From online solutions that gave the number of rows in different forms I tried these without success:

%macro nrow1(data);
 %local n;
 data _null_;
  set &data NOBS=size;
  call symput("n",size);
 stop;
 run;
 &n
%mend;

or

%macro nrow2(data);
 %local n;
 PROC SQL;
   select count(*) as n into :n from &data;
 QUIT;
 &n
%mend;

or

%macro nrow3(data,lib);
  %local n;
  proc sql;
    select nobs into :n
    from dictionary.tables
    where libname eq "&lib"
    and memname eq '&table';
    quit;
  &n
%mend;

How to correct those ?

moodymudskipper
  • 46,417
  • 11
  • 121
  • 167

3 Answers3

4

To make this type of macro "function" the result text has to be the only text that the macro generates. Your tests where generating SAS code instead. So when you used them in a %put statement the first SAS statement became part of the %put statement.

You need to only use macro statements, no SAS statements, in your macro for it to be used in the middle of statement like that.

You can use this macro. https://github.com/sasutils/macros/blob/master/nobs.sas Which will use metadata if available and otherwise count the number of observations by fetching the data row by row. It currently defaults to creating a macro variable with the count, but can either call it without a macro variable name, or even modify the macro definition to default to not using a macro variable name.

%macro nobs
/*----------------------------------------------------------------------
Return the number of observations in a dataset reference
----------------------------------------------------------------------*/
(data       /* Dataset specification (where clauses are supported) */
,mvar=nobs  /* Macro variable to store result. */
            /* Set MVAR= to use NOBS as an in-line function. */
);
/*----------------------------------------------------------------------
Usage:

When the MVAR parameter is empty then NOBS will return the result as
the output of the macro call so that NOBS can be called in a %IF or
%LET statement. See examples.

------------------------------------------------------------------------
Examples:

%* Generating the default macro variable ;
%nobs(EVENTS)
%if (&nobs = -1) %then %put %sysfunc(sysmsg()) ;
%else %if (&nobs > 0) %then %do;
  ....

%* Use without generating macro variable ;
%if (%nobs(EVENTS,mvar=) > 0) %then %do;
  ....

%* Generating a different macro variable and use WHERE clause ;
%nobs(demog(where=(sex=1)),mvar=nmales)
%put Number of males = &nmales;

------------------------------------------------------------------------
Notes:

NOBS will return -1 when it cannot count the number of observations.
You can use %sysfunc(sysmsg()) to get the reason.

The macro variable specified in the MVAR parameter is globalized if not
previously defined in the calling environment.

When the DATA parameter is not specified, the last created data file is
used.

In the rare case that NLOBSF function cannot count the observations
then the NOBS macro will loop through the dataset and count.
Testing so far has found that sequential datasets such as V5 transport
libraries cannot use the NLOBSF function. For large sequential datasets
you will get faster results using an SQL query instead of NOBS macro.

-----------------------------------------------------------------------
History:
03DEC95  TRHoffman  Creation
12JUL96  TRHoffman  Protected against different values of MISSING
                    option.
20AUG97  TRHoffman  Protected against case changes in options table.
11MAR99  TRHoffman  Trimmed the returned macro variable. (Recommended
                    by Paulette Staum). Used macro mvartest to globalize
                    previously undefined variables.
25OCT2000 abernt    Updated to handle lowercase letters in where clause
                    and eight character libnames. Eliminated need to
                    use %TRIM() macro.
14OCT03  TRHoffman  Used qupcase function to permit macro variables in
                    where clause.
09JAN2009 abernt    Changed to use ATTRN functions. Test MVAR value.
                    Return results like a function when MVAR is blank.
01MAR2018 abernt    Removed usage of sasname and mvartest macros.
----------------------------------------------------------------------*/
%local dsid return ;

%if %length(&mvar) %then %do;
%*----------------------------------------------------------------------
MVAR parameter must be a valid variable name.
-----------------------------------------------------------------------;
  %if not %sysfunc(nvalid(&mvar)) %then %do;
    %put %str( );
    %put ERROR: Macro NOBS user error.;
    %put ERROR: "&mvar" is not a valid value for MVAR. Must be a valid SAS name.;
    %goto quit;
  %end;
%*----------------------------------------------------------------------
MVAR paramater cannot duplicate a variable name used by NOBS macro.
-----------------------------------------------------------------------;
  %if %sysfunc(indexw(DATA MVAR DSID RETURN,%upcase(&mvar))) %then %do;
    %put %str( );
    %put ERROR: Macro NOBS user error.;
    %put ERROR: "&mvar" is not a valid value for MVAR. Name in use by NOBS macro.;
    %goto quit;
  %end;
%*----------------------------------------------------------------------
Globalize macro variable when not defined.
-----------------------------------------------------------------------;
  %if not %symexist(&mvar) %then %global &mvar ;
%end;

%*----------------------------------------------------------------------
When DATA parameter not specified, use &syslast macro variable to get
last created data set.
-----------------------------------------------------------------------;
%if %bquote(&data) =  %then %let data=&syslast;

%*----------------------------------------------------------------------
DATA=_NULL_ will successfully OPEN, but cannot be queried with ATTRN
function. So by setting DATA=*_NULL_* the OPEN call will fail and set
an error message that can be retrieved with the SYSMSG() function.
-----------------------------------------------------------------------;
%if (%qupcase(&data) = _NULL_) %then %let data=*_NULL_*;

%*----------------------------------------------------------------------
Initialize for failure.
-----------------------------------------------------------------------;
%let return=-1;

%*----------------------------------------------------------------------
Open the dataset for random access.
  When there are no active where clauses then use NLOBS.
  If that did not get a count then try NLOBSF.
-----------------------------------------------------------------------;
%let dsid = %sysfunc(open(&data));
%if &dsid %then %do;
  %if not %sysfunc(attrn(&dsid,WHSTMT)) %then
    %let return = %sysfunc(attrn(&dsid,NLOBS));
  %if (&return = -1) %then %let return = %sysfunc(attrn(&dsid,NLOBSF));
  %let dsid = %sysfunc(close(&dsid));
%end;

%*----------------------------------------------------------------------
If unable to get a count then try to open dataset for sequential access
and count observations by fetching each one.
-----------------------------------------------------------------------;
%if (&return = -1) %then %do;
  %let dsid = %sysfunc(open(&data,IS));
  %if &dsid %then %do;
    %let return=0;
    %do %while (%sysfunc(fetch(&dsid)) = 0);
      %let return = %eval(&return + 1);
    %end;
    %let dsid = %sysfunc(close(&dsid));
  %end;
%end;

%*----------------------------------------------------------------------
Return the value.
-----------------------------------------------------------------------;
%if %length(&mvar) %then %let &mvar=&return;
%else &return;

%quit:
%mend nobs;
Tom
  • 47,574
  • 2
  • 16
  • 29
  • Interesting... can you provide an example of data that would open but not allow random access? – Robert Penridge Mar 01 '18 at 17:58
  • 1
    @RobertPenridge SAS transport files, Views, remote database engines – Tom Mar 01 '18 at 18:07
  • Switching to this answer as the previously selected answer returned `-1` for views and tables in Oracle libraries – moodymudskipper May 30 '18 at 10:00
  • It works well but I see that for Oracle tables it's a bit slow as it fetches all observations. The number of observations should be available in the `NUM_ROWS` field of `ALL_TABLES`, which should be much faster to query, though I don't know how to get this table from `SAS`. – moodymudskipper May 30 '18 at 10:21
  • @Moody_Mudskipper If you know the "dataset" is really an Oracle table then you could craft a different utility to take advantage of that knowledge. – Tom May 30 '18 at 12:00
  • I "know" but I haven't found an automated way to get it, that's the point of this question : https://stackoverflow.com/questions/50603438/get-server-info-for-all-librefs – moodymudskipper May 30 '18 at 12:11
1

This returns the total rows in a dataset. It is -1 if the dataset doesn't exist (or if it is a view). You can optionally include a where statement. The where statement will significantly decrease performance since SAS needs to recount the whole table.

%macro nrow(data, where=);

    %if(%index(%sysfunc(compress(&data.)), %bquote(where=))) 
        %then %let option = nlobsf;
    %else %let option = nlobs;

    %if(%sysfunc(exist( %scan(&data., 1, %str(%() ) ) ) ) %then %do;
        %let dsid   = %sysfunc(open(&data.) );
        %let n      = %sysfunc(attrn(&dsid, &option.) );
        %let rc     = %sysfunc(close(&dsid) );
    %end;
        %else %let n=-1;

    &n.;
%mend;
Stu Sztukowski
  • 10,597
  • 1
  • 12
  • 21
  • Thanks, can you tell me how I can reformat it so I can call `%put %nrow(sashelp.cars);` and no macro variables is polluting my environment ? – moodymudskipper Mar 01 '18 at 16:36
1

Here is an update to Stu's code that gives you what you are looking for. Notice the %local declarations so that you don't have extra macros floating around.

%macro get_nobs(data);
    %local option dsid nobs rc;

    %if(%index(%sysfunc(compress(&data.)), %bquote(where=))) 
        %then %let option = nlobsf;
    %else %let option = nlobs;

    %if(%sysfunc(exist( %scan(&data., 1, %str(%() ) ) ) ) %then %do;
        %let dsid   = %sysfunc(open(&data.) );
        %let nobs  = %sysfunc(attrn(&dsid, &option.) );
        %let rc     = %sysfunc(close(&dsid) );
    %end;
        %else %let nobs=-1;

    &nobs
%mend;

%put NOBS: %get_nobs(sashelp.cars);

SAS Macros write code for you. Your attempts didn't work because they were trying to execute non-macro code inside the %put statement.

You can do what you what you were attempting, but the how involves writing a function that calls a macro and return the value. It's involved and the method above is much simpler.

DomPazz
  • 12,415
  • 17
  • 23