SAS: If condition won't recognize macro variable within datastep - sas

In the code below the IF statement that sends the email isn't evaluating correctly. I am not sure why. I tried to check for a null but that didnt work either. It is just always sending the the first do in that statement. In the below statement TABLE1 exist with no records and TABLE2 does not exist. I think it has something to do with &CNT3 being populated with a COUNT(*) in the proc sql statement.
%IF %SYSFUNC(exist(TABLE1)) %THEN %DO;
PROC SQL;
SELECT COUNT(*) INTO : CNT3 FROM TABLE1;
QUIT;
%END;
%ELSE %DO;
%LET CNT3=0;
%END;
%put &cnt3.;
%IF %SYSFUNC(exist(TABLE2)) %THEN %DO;
PROC SQL;
SELECT COUNT(*) INTO : CNT4 FROM TABLE2;
QUIT;
%END;
%ELSE %DO;
%LET CNT4=0;
%END;
%put &cnt4.;
%IF (&CNT3 ^=0 AND &CNT3^='0') %THEN %DO;
PROC EXPORT DATA=TABLE1.
DBMS=XLSX
OUTFILE="data/REPORT1.xlsx"
REPLACE;
SHEET="TEST1";
RUN;
%END;
%IF (&CNT4 ^=0 AND &CNT4^='0') %THEN %DO;
PROC EXPORT DATA=&ENV..AUTH_ERRORLOG_&REC_DATE.
DBMS=XLSX
OUTFILE="data/REPORT1.xlsx"
REPLACE;
SHEET="TEST2";
RUN;
%END;
%let EMAIL_SUBJECT = "TEST EMAIL.";
FILENAME OUTBOX EMAIL 'TEST#TEST.COM';
DATA _NULL_;
IF (&CNT3 ^=0 AND &CNT3 ^='0') OR (&CNT4 ^=0 AND &CNT4^='0') THEN
DO;
FILE OUTBOX
TO=('TEST#TEST.COM')
SUBJECT= &EMAIL_SUBJECT.
ATTACH=("/data/REPORT1.xlsx" CONTENT_TYPE="APPLICATION/XLSX");
END;
ELSE DO;
FILE OUTBOX
TO=('TEST#TEST.COM')
SUBJECT= &EMAIL_SUBJECT.;
PUT"NO ERRORS FOUND";
END;
RUN;

There may be a number of things going on here, so let's try to clean this up a bit to see if it will resolve your issues.
First, let's grab the observation count from the metadata of the tables of interest instead of counting all the observations. This is a great repeatable macro that I highly recommend keeping as an always-available sasauto:
%macro nobs(data);
%local dsid nobs rc;
%let nobs = -1;
%if(%sysfunc(exist(&data.)) ) %then %do;
%let dsid = %sysfunc(open(&data.));
%let nobs = %sysfunc(attrn(&dsid., nlobs));
%let rc = %sysfunc(close(&dsid.));
%end;
&nobs.
%mend;
This will act like a function and return the number of observations for a SAS table. If it does not exist, it returns -1. For example:
%put The number of obs in sashelp.cars is %nobs(sashelp.cars);
%put The number of obs in a non-existent table is %nobs(doesntexist);
Output:
The number of obs in sashelp.cars is 428
The number of obs in a non-existent table is -1
Now we're guaranteeing that we're always returning a number without spaces in it. Let's replace the program logic:
%if(%nobs(table1) > 0) %then %do;
PROC EXPORT DATA=TABLE1
DBMS=XLSX
OUTFILE="data/REPORT1.xlsx"
REPLACE;
SHEET="TEST1";
RUN;
%end;
%if(%nobs(table2) > 0) %then %do;
PROC EXPORT DATA=&ENV..AUTH_ERRORLOG_&REC_DATE.
DBMS=XLSX
OUTFILE="data/REPORT1.xlsx"
REPLACE;
SHEET="TEST2";
RUN;
%end;
%let EMAIL_SUBJECT = "TEST EMAIL.";
FILENAME OUTBOX EMAIL 'TEST#TEST.COM';
DATA _NULL_;
IF (%nobs(table1) > 0 OR %nobs(table2) > 0) then do;
FILE OUTBOX
TO=('TEST#TEST.COM')
SUBJECT= &EMAIL_SUBJECT.
ATTACH=("/data/REPORT1.xlsx" CONTENT_TYPE="APPLICATION/XLSX");
END;
ELSE DO;
FILE OUTBOX
TO=('TEST#TEST.COM')
SUBJECT= &EMAIL_SUBJECT.;
PUT"NO ERRORS FOUND";
END;
RUN;

This test does not make any sense
&CNT3 ^=0 AND &CNT3 ^='0'
in either the macro logic or the data step logic.
If CNT3 is going to have values like 0 or 123 or even 123 then just test if it is zero or not:
&cnt3 ne 0

Related

SAS check if column exist in Table

I want to make a macro That check if all the columns in col_to_check are in Table and I want exit SAS if one of these columns doesn't exit
I try this:
%let col_to_check = ID SEG AGE;
%MACRO check(table , col_to_check);
%local count;
%let count=0;
%DO i_=1 %TO %sysfunc(countw(&col_to_check.," "));
%LET col=%SCAN(&col_to_check.,&i_.," ");
%if ( %varexist(&table.,&col.) = 1) %then endsas;
%END;
%MEND check;
Use the vcolumn table of the sashelp library.
%macro check(lib, table, col_to_check);
%let nb_col = %sysfunc(countw(&col_to_check., %quote( )));
%let col_names = "%sysfunc(tranwrd(&col_to_check.,%str( )," "))";
proc sql noprint;
select count(distinct name) into :nb
from sashelp.vcolumn where upcase(name) in (&col_names.)
and upcase(libname)="&lib."
and upcase(memname)="&table.";
quit;
%if &nb. ^=&nb_col. %then
%goto end_pg;
%else
%do;
%put do stuff;
%end;
%end_pg:
%mend;
%check(SASHELP, CLASS, SEX WEIGHT NAME AGE);
PS: What do you mean by "exit SAS"? You could easily replace the %goto by %ABORT

sas passing a variable to a conditional macro

I can't figure out how to pass to a conditional macro the values of a dataset variable. Let's say we HAVE:
data HAVE;
input id name $ value ;
datalines;
1 pluto 111
2 paperino 222
3 trump 333
4 topo 444
;
run;
I would like to use dataset variable name inside a sas conditinal macro to make other IF conditions.
What i mean is to use this code with another IF step before the conditional macro (or inside the conditional macro)
options minoperator mlogic;
%macro test(var) / mindelimiter=',';
%if &var in(pippo,pluto) %then %do; "if &var"n = name; end;
%else %do;"mod &var"n = name;end;
%mend test;
data want;
set have;
%test(pippo);
%test(arj);
%test(frank);
%test(pluto);
%test(george);
run;
For example:
options minoperator mlogic;
%macro test(var) / mindelimiter=',';
if name = &var then do;
%if &var in(pippo,pluto) %then %do "if &var"n = name; %end;
%else %do; "mod &var"n = name; end;
end;
%mend test;
but the IF name = &var is always true... there's some problem with using the name dataset variable inside the macro.
EDIT AFTER first answer
example code of conditioning inside the conditional macro:
%macro test(var) / mindelimiter=',';
%if &var in(pippo pluto) %then %do;
if name = 'pluto' then ifif_&var. = name;
if_&var. = name;
%end;
%else %do;
mod_&var. = name;
%end;
%mend test;
it' just an example off course it's almost useless.
There's nothing inherently wrong with using it that way, though you have some errors in your code.
%macro test(var) / mindelimiter=',';
if name = "&var" then do;
%if &var in(pippo pluto) %then %do; if_&Var. = name; %end;
%else %do; mod_&var. = name; %end;
end;
%mend test;
That works, or at least as far as I can tell works for what you want.
Looks like you want to generate code from your data.
data _null_;
set have end=eof;
if _n_=1 then call execute('data want;set have;');
call execute(cats('%nrstr(%test)(',name,')'));
if eof then call execute('run;');
run;

Checking for variable type in SAS-Macro

I am trying to summarize my variables using proc sql and proc freq procedures in a macro.
Here is the code:
%macro des_freq(input= ,vars= );
%let n=%sysfunc(countw(&vars));
%let binary=NO;
%do i = 1 %to &n;
%let values = %scan(&vars, &i);
%if %datatyp(&values)=NUMERIC %then %do;
proc summary data = &input;
output out=x min(&values)=minx max(&values)=maxx;
run;
data _null_;
set x;
if minx = 0 and maxx = 1 then call symputx('binary','YES');
run;
%if &binary = YES %then %do;
proc sql;
select segment_final,
(sum(case when &values = 1 then 1 else 0 end)/ count(*)) * 100 as &values._percent
from &input
group by segment_final;
quit;
%end;
%else %do;
proc freq data =&input;
tables segment_final*&values/nofreq nopercent nocol;
run;
%end;
%end;
%else %do;
proc freq data =&input;
tables segment_final*&values/nofreq nopercent nocol;
run;
%end;
%end;
%mend;
My variables can be numeric or character. If it's numeric, it can 2 more distinct values.
I want % of 1's in a binary variable by segments(hence proc sql) and % of all distinct variables for each segment(hence proc freq).
My first if statement is checking whether the variable if numeric or not and then if its numeric, next few steps is checking if its binary or not. If its binary then execute the proc sql else execute proc freq.
If the variable is character then just execute the proc freq.
I am not able to figure out how to check if my variable is numeric or not. I tried %SYSFUNC(Vartype), %isnum and %DATATYP. None of them seem to work. Please help!!
First you can look into sashelp.vcolumn table to check variables types:
data want(keep=libname memname name type);
set sashelp.vcolumn( where= (libname='SASHELP' and memname='CLASS'));
run;
If you don't want to use vcolumn table, you can use vtype() data step function as #Tom suggest:
data _NULL_;
set &input (obs=1);
call symput('binary',ifc(vtype(&values)='N','YES','NO' ));
run;

Setting Macro Variable equal to value in table

I have a table with one row and 4 columns. I would like to create 4 macro variables named after each column where the value is set to the value in the 1 row.
If this were R I could access the values directly with something like:
newvar1=tablename[1,1]
newvar2=tablename[1,2]...
Is there anyway for me to select values from a table and set macrovariables equal to that value?
So something like:
%macrovar1=tablename[1,1]...
Except obviously the right side of the equals sign is R code not SAS.
Thanks
You can use proc sql to do it like so:
proc sql noprint inobs=1;
select name into :my_val from sashelp.class;
quit;
%put &my_val;
Or you can use call symput from the datastep like this:
data _null_;
set sashelp.class(obs=1);
call symput('my_val',name);
run;
For something more flexibile, we use a utility macro that allows us to check values from anywhere in our code. I've modified it slightly to accommodate your request but the usage will be like this:
%let my_val = %get_val(iDs=sashelp.class, iField=name);
You could also use it in the middle of a proc or a datastep like so:
data _null_;
my_value = "%get_val(iDs=sashelp.class, iField=name)";
run;
Or even:
proc sql noprint;
create table want as
select * from sashelp.class
where name = "%get_val(iDs=sashelp.class, iField=name)"
;
quit;
Here is the macro definition:
%macro get_val(iDs=, iField=);
%local dsid pos rc result cnt value;
%let result=;
%let cnt=0;
/*
** ENSURE ALL THE REQUIRED PARAMETERS WERE PASSED IN.
*/
%if "&iDs" ne "" and "&iField" ne "" %then %do;
%let dsid=%sysfunc(open(&iDs,i));
%if &dsid %then %do;
%let pos=%sysfunc(varnum(&dsid,&iField));
%if &pos %then %do;
%let rc=%sysfunc(fetch(&dsid));
%if "%sysfunc(vartype(&dsid,&pos))" = "C" %then %do;
%let value = %qsysfunc(getvarc(&dsid,&pos));
%if "%trim(&value)" ne "" %then %do;
%let value = %qtrim(&value);
%end;
%end;
%else %do;
%let value = %sysfunc(getvarn(&dsid,&pos));
%end;
&value
%end;
%else %do;
%put ERROR: MACRO.GET_VAL.SAS: FIELD &iField NOT FOUND IN DATASET %upcase(&iDs).;
%end;
%end;
%else %do;
%put ERROR: MACRO.GET_VAL.SAS: DATASET %upcase(&iDs) COULD NOT BE OPENED.;
%end;
%let rc=%sysfunc(close(&dsid));
%end;
%else %do;
%put ERROR: MACRO.GET_VAL.SAS: YOU MUST SPECIFY BOTH THE IDS AND IFIELD PARAMETERS TO CALL THIS MACRO.;
%end;
%mend;
The above macro is an abbreviated version of the %ds2list macro found here.
There are several ways, but the simplest is:
data _null_;
set have;
if _n_=1 then do;
call symputx('macrovar1',var1);
*more of these;
end;
stop;
run;
That opens the dataset, then if on first row (_n_ is iteration of data step loop, which in most cases is the first row), use call symputx to assign its value to a macro variable.
I would note that you should remember that SAS macro variables are not data variables, do not have a data type (always are text), and usually are not used to store data the way you would use vectors in R.

What's the fastest way to partition a sas dataset for batch processing?

I have a large sas dataset (1.5m obs, ~250 variables) that I need to split into several smaller sas datasets of equal size for batch processing. Each dataset needs to contain all the variables but only a fraction of the obs. What is the fastest way of doing this?
You could do something like the following:
%macro splitds(inlib=,inds=,splitnum=,outid=);
proc sql noprint;
select nobs into :nobs
from sashelp.vtable
where libname=upcase("&inlib") and memname=upcase("&inds");
quit;
%put Number of observations in &inlib..&inds.: &nobs;
data %do i=1 %to &splitnum.;
&outid.&i
%end;;
set &inds.;
%do j=1 %to (&splitnum.-1);
%if &j.=1 %then %do;
if
%end;
%else %do;
else if
%end;
_n_<=((&nobs./&splitnum.)*&j.) then output &outid.&j.;
%end;
else output &outid.&splitnum.;
run;
%mend;
An example call to split MYLIB.MYDATA into 10 data sets named NEWDATA1 - NEWDATA10 would be:
%splitds(inlib=mylib,inds=mydata,splitnum=10,outid=newdata);
Try this. I haven't tested yet, so expect a bug somewhere. You will need to edit the macro call to BATCH_PROCESS to include the names of the datasets, number of new data sets, etc.
%macro nobs (dsn);
%local nobs dsid rc;
%let nobs=0;
%let dsid = %sysfunc(open(&dsn));
%if &dsid %then %do;
%let nobs = %sysfunc(attrn(&dsid,NOBS));
%end;
%else %put Open for dataset &dsn failed - %sysfunc(sysmsg());
%let rc = %sysfunc(close(&dsid));
&nobs
%mend nobs;
%macro batch_process(dsn_in,dsn_out_prefix,number_of_dsns);
%let dsn_obs = &nobs(&dsn_in);
%let obs_per_dsn = %sysevalf(&dsn_obs / &number_of_dsns);
data
%do i = 1 %to &number_of_dsns;
&dsn_out_prefix.&i
%end;
;
set &dsn_in;
drop _count;
retain _count 0;
_count = _count + 1;
%do i = 1 %to &number_of_dsns;
if (1 + ((&i - 1) * &obs_per_dsn)) <= _count <= (&i * &obs_per_dsn) then do;
output &dsn_out_prefix.&i;
end;
%end;
run;
%mend batch_process;
%batch_process( dsn_in=DSN_NAME , dsn_out_prefix = PREFIX_ , number_of_dsns = 5 );