Include macro condition in report - sas

Below is the small piece of code to get reports in excel.
%if &linear %then %do;
ods excel options(sheet_name="vol");
proc print data=perf;
id direction segment;
var accts;
run;
%end;
%else %do;
ods excel options(sheet_name="vol");
proc print data=perf;
id direction segment;
var accts;
run;
%end;
Direction segment accts
A model 17177
A booked 567
A unbooked 5676
B model 17177
B booked 567
B unbooked 5676
If segments are not available i will get report as below
Direction segment accts
A model 17177
A 1 17177
B model 17177
B 1 17177
Iam planing to introduce two macro variables
%let dir =A;
%let Non_segment=y;
Based on value for direction it should give only those direction and if there no segment(Non_segment=y;), it should have only first observation. So the output will looks like below for Non_segment=y
Direction segment accts
A model 17177

If you do not have any variable to test then you cannot subset to the first observation per by group with just a WHERE statement. You will need to generate a data step.
data to_print ;
set perf ;
by direction;
%if %length(&dir) %then %do;
where direction="&dir";
%end;
%if %upcase(&non_segment)=Y %then %do;
if first.direction;
%end;
run;

I tried below code.
%macro isblank(var);
%if %symexist(&var) %then 1; %*not exist*;
%else %if %sysevalf(%superq(&var)=,boolean) %then 1; %*blank*;
%else 0;
%mend isblank;
%let dir =A;
%let Non_segment=y;
proc print data=new;
%if %isblank(Non_segment) %then (obs=1);*firstobs*;
id direction segment;
var acct;
%else;*all obsevation*;
proc print data =new;
id direction segment;
var acct;
run;
Getting below error
proc print data=new;
78 %if %isblank(Non_segment) %then (obs=1);*firstobs*;
ERROR: Nesting of %IF statements in open code is not supported. %IF ignored.
ERROR: Skipping to next %END statement.
79 id direction segment;
80 var acct;
81 %else;*all obsevation*;
ERROR: The %ELSE statement is not valid in open code.
NOTE: The SAS System stopped processing this step because of errors.
NOTE: There were 6 observations read from the data set WORK.NEW.
NOTE: PROCEDURE PRINT used (Total process time):
real time 10.57 seconds
cpu time 6.31 seconds
82 proc print data =new;
83 id direction segment;
84 var acct;
85 run;

Related

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

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

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.

How to create this macro with conditions?

Sample input data:
FirstName LastName Group Age LastVenue Position
Jack Smith ULDA 25 TheaterA 1
Jesse James GODL 37 TheaterB 12
Jane Doe ULDA 29 TheaterA 3
Izzy Gord IIPA 41 TheaterC 8
Ann Roswell GODL 30 TheaterB 16
Chelsea Jenk ULDA 19 TheaterA 11
I am trying to create:
%macro group_members(group=);
proc print data=sample;
var Position Age Group FirstName LastName;
where group=&group;
%mend group_members;
However I want to add conditions to it so if nothing is entered %group_members() then it will display all groups with the order of the variables shown above. If an invalid group is entered in this case: %group_members(LOL) then I would like a note to be sent to the log %put 'An invalid group was entered'. and therefor nothing should be printed. I am trying to create a program very similar on a much larger dataset.
I appreciate any help in advanced! Thank you :)
So far I have tried:
%macro group_members(group=);
proc sql;
select count(*) into :ct
from sample
where group="&group"
quit;
proc print data=sample;
%if &group ^= %then %do;
where group="&group."; %end;
%if &ct = 0 %then %put An Invalid group was entered;
%else %do;
where group="&group.";
%end; run;
%mend group_members;
I get errors from every test.. for example %group_members() returns an error of:
ERROR: More positional parameters found than defined
Entering a blank resulting in all groups being shown could be achieved by surrounding the where statement with this macro code:
%if &group ^= %then %do;
where group="&group.";
%end;
This only submits the where statement, in the event that the &group variable is populated. Note also that I've added double quotes so that the where statement doesn't generate syntax errors.
The macro would need to know which groups were or were not valid. This would require an extra processing step before the proc print:
proc sql;
select count(*) into :ct
from sample
where group="&group";
quit;
%if &ct = 0 %then %put An invalid group was entered;
%else %do;
...
&ct will contain the number of records that match the where clause. If zero, then I'm assuming that means it's an invalid group.
/Creating Sample dataset/
data test;
infile datalines dlm="," missover;
input FirstName : $10.
LastName : $10.
Group : $8.
Age : 8.
LastVenue : $10.
Position : 8.
;
datalines;
Jack,Smith,ULDA,25,TheaterA,1
Jesse,James,GODL,37,TheaterB,12
Jane,Doe,ULDA,29,TheaterA,3
Izzy,Gord,IIPA,41,TheaterC,8
Ann,Roswell,GODL,30,TheaterB,16
Chelsea,Jenk,ULDA,19,TheaterA,11
;
run;
Have added comments in the code itself
%macro group_members(group=);
%put &group.;
/*Checking if the group is valid or invalid*/
proc sql noprint;
select count(*) into :num from test where group="&group.";
quit;
%put &num.;
data final;
set test;
/*checking if the group entered is NULL, if it is ,then it will output all the records*/
%if "&group."="" %then %do; %end;
/*If the group is Valid or not, if it is invalid then nothing will be in output and a msg in the LOG will be displayed, you can put ERROR statement if you want*/
%else %if &num. = 0 %then %do;
where group="&group.";
%put "An Invalid group was entered";
/*If above two are not the case then it will filter on that group*/
%end;
%else %do;
where group="&group.";
%end;
run;
%mend group_members;
%group_members(group=);
Credit to #mjsqu
Step1: Test if &group is a valid group. count(*) do this for you.
Step2: If count(*) return 0, then output user-defined messages.
Step3: Otherwise, continue proc print. If &group = then list all records.
%macro group_members(group);
proc sql noprint;
select count(*) into :ct
from sample
where group="&group.";
%if &ct = 0 and &group ne %then %put An Invalid group was entered;
%else %do;
proc print data=sample;
var Position Age Group FirstName LastName;
%if &group ne %then
%do;
where group="&group.";
%end;
%end;
%mend group_members;
%group_members();
%group_members(GODL);
%group_members(G);