what is wrong with the following sas code? - sas

I am new to sas. I have written a basic code but it is not working. Can somebody help me figure out what is wrong with the code. I wish to append the datasets.
options mprint mlogic symbolgen;
%macro temp();
%let count = 0;
%if &count = 0 %then %do;
data temp;
set survey_201106;
%let count = %eval(&count +1);
%end;
%else %do;
%do i = 201107 %to 201108;
data temp;
set temp survey_&i;
%end;
%end;
run;
%mend;
%temp;

You are setting &count to 0 at the beginning of the macro, so the %else clause will never be executed.
I'm not sure what your aim is, but it looks like you just want to concatenate 3 datasets and store in a new dataset. If so, will this not suffice:
data temp;
set survey_201106-survey_201108;
run;
This creates a dataset called temp and populates it with the the contents of survey_201106, survey_201107 and survey_201108 in order. The - tells SAS that you want the all the datasets named survey_20110* between survey_201106 and survey_201108 inclusive.
Details of the syntax.

options mprint mlogic symbolgen;
%macro temp();
proc sql noprint;
create table table_list as
select monotonic() as num,memname
from dictionary.tables
where libname = 'WORK' and memname contains 'SURVEY_';
quit;
proc sql noprint;
select count(*) into :cnt
from table_list;
quit;
%do i = 1 %to &cnt.;
%if &i eq 1 and %sysfunc(exist(work.temp)) %then %do;
proc sql;
drop table work.temp;
quit;
%end;
proc sql noprint;
select memname into :memname
from table_list
where num = &i.;
quit;
proc append base = temp data = &memname. force;
run;
%end;
%mend;
%temp;
Working: Above code will append all the work data sets whose names starting with
'SURVEY_' to temp data set.
dictionary.tables is used to create a data set which will contain the list of data sets whose names starts with 'survey_'.
table_list data set:
num memname
1 survey_201106
2. survey_201107
3. survey_201108
cnt macro variable is created to hold number of such data sets
Within loop, each data set present the list of data set names (in table table_list) will be appended to work.temp data set

What you're probably trying to do is create a macro that appends (without using proc append for some reason) when a dataset exists, or creates it new when it does.
SAS is not like r or other similar languages, where you have to control largely everything that happens. SAS's strength is that you can ask it to do common things with only a line or two of code. SAS is what's commonly called a 4th Generation Language for this reason: you're not supposed to control all of the little bits. That's a waste of time. Use the Procedures (PROC...) and constructs SAS provides you.
In this case, PROC APPEND does exactly what this whole macro does. It creates a dataset or appends new rows to it if it already exists.
proc append base=temp data=in_data;
run;
Now, if you're trying to learn the macro language and using this concept as a learning tool only, it is possible to do this in a macro that's not all that different from yours.
Note: This is not a good way to do this. It might be useful for learning macro concepts, but it should not be used as an example of good code. Despite my improvements, it is still not the way you should do this; proc append or SRSwift's example are better.
One thing I'm going to introduce here: a macro parameter. A good rule of macro programming is that all macros should have parameters. If there's no possible parameter, it should usually be possible to do without needing a macro. Parameters are what make macros useful, most of the time. In this case I'm going to rewrite your macro to take one append dataset as a parameter and one 'base' dataset. In your example, temp was the base dataset and survey_1106 etc. are the append datasets.
Also, &count needs to be a global macro variable. In SAS, variables created inside a macro are by default local in scope - ie, they only are defined inside one run of the macro and then disappear. This is nearly identical to functions in c/etc. languages (a bit different from r, which uses lexical scoping, and you might be expecting given how you wrote this). There are some funny rules, though, but for now we'll just go with this. global macro variables, which include any variable that has already been defined in the global scope, are available in all macro iterations (and outside of macros).
So:
%macro append_dataset(base=,append=);
%if &count=0 %then %do;
data &base.;
set &append.;
run;
%end;
%else %do;
data &base.;
set &base. &append.;
run;
%end;
%let count=%eval(&count.+1);
%mend append_dataset;
%let count=0;
%append_dataset(base=temp,append=survey_1106);
%append_dataset(base=temp,append=survey_1107);
%append_dataset(base=temp,append=survey_1108);
Now, you could generate those calls through an external method (such as dictionary.tables as in Harshad's example). You also could add another element to the macro, which is to iterate over all of the elements in a list provided as append. You could also hardcode the list in a %do loop, as you did in the initial example (but I think that's bad practice to get into). You could literally do that in my macro:
%macro append_dataset(base=,append=);
%do survey=201106 to 201108;
%if &count=0 %then %do;
data &base.;
set survey_&survey.;
run;
%end;
%else %do;
data &base.;
set &base. survey_&survey.s;
run;
%end;
%let count=%eval(&count.+1);
%end;
%mend append_dataset;
Notice the count increment is inside the do loop - that's one of the places you went wrong here. Otherwise this is just adding an outer loop and changing the append mentions to the calculated values from the loop. But again, this is fairly poor coding practice - the loop should at minimum be constructed from a macro parameter.

Related

Is there any way to apply conditional logic for keeping/dropping columns in SAS?

I'm trying to drop particular columns in SAS based on a macro variable, my hands are slightly tied in terms of what code I can use - so I need a solution in BASE SAS.
I've already tried wrapping the drop/keep in an if, but I know the drop happens at run time so this won't work.
Example:
data dropsomecolumns;
if &somemacro =1 then do;
drop somecol1 somecol2;
end;
run;
You either need to use macro code to conditionally generate the code you want.
data dropsomecolumns;
set have;
%if &somemacro =1 %then %do;
drop somecol1 somecol2;
%end;
run;
Or change so that the macro variable has the list of columns to drop.
%let drop_columns=somecol1 somecol2;
data dropsomecolumns(drop=&drop_columns);
set have;
run;
Note that the drop statement will give a warning if no variables are listed, but the drop= dataset option will not give that warning.
You have have no variables listed and it will work fine and assumes not are to be dropped. So if the macro variable is a list of variables just use:
drop &drop_columns;
This works fine:
data demo;
set sashelp.class;
drop ;
run;
So no conditional logic needed.

Dynamically create the "KEEP" List in SAS

I am trying to create a keep list dynamically. Say:
%MACRO TEST(A=,B=,OUT_VAR=,KEEP_VAR=);
&OUT_VAR=MAX(&A,&B);
%IF &KEEP_VAR = 'Y' %THEN VAR_LIST=%SYSFUNC(CATS(VAR_LIST,&OUT_VAR));
%PUT VAR_LIST;
%MEND;
DATA ABC (keep = VAR_LIST);
LENGTH VAR_LIST $100.;
RETAIN VAR_LIST '';
%TEST(A=1,B=3,OUT_VAR=FIRS,KEEP_VAR='Y');
%TEST(A=2,B=4,OUT_VAR=SEC,KEEP_VAR='Y');
%TEST(A=3,B=5,OUT_VAR=THIR,KEEP_VAR='N');
RUN;
I have a datastep in which i am creating several variables calculated by the macro code.
I want to create a dynamic list of these output variables and then use it in the keep statement.
The above code does not seem to work, can someone suggest what am I missing here.
A running data step is source code that has already been compiled. You are trying to change the source while it is running -- that is not going to happen. This is a symptom of mixing scopes and contexts between macro code and generated code.
However, the TEST macro is generating source and thus can either
maintain state information for generating a KEEP source code statement after the macros are invoked.
generate an additional KEEP statement per TEST invocation
In general, macro only parameter values are already 'strings', and do not need to be quoted.
%MACRO TEST(A=,B=,OUT_VAR=,KEEP_VAR=);
&OUT_VAR=MAX(&A,&B);/* generates data step source code*/
%IF &KEEP_VAR = 'Y' %THEN
%LET VAR_LIST=&VARLIST &OUT_VAR;
%MEND;
* must occur before sequence of TEST invocations;
%global VARLIST; * globals generally want to be avoided, next version avoids it;
%let VARLIST =;
DATA ABC;
%TEST(A=1,B=3,OUT_VAR=FIRS,KEEP_VAR='Y');
%TEST(A=2,B=4,OUT_VAR=SEC,KEEP_VAR='Y');
%TEST(A=3,B=5,OUT_VAR=THIR,KEEP_VAR='N');
KEEP &VARLIST;
RUN;
%symdel VARLIST;
I hope this is a learning exercise -- The above pattern of coding has a macro generate code that would suffice as two normal non-macro data step statements do. Too much macroification can be obfuscating.
Additionally, the output ABC might not be what you expect. There will be one row with A=3, B=5, FIRS=4, SEC=6 and no THIR=8. So you have also some mixed up row and column data concepts.
Here is a cleaner alternative in which TEST generates the KEEP
%MACRO TEST(A=,B=,OUT_VAR=,KEEP_VAR=);
&OUT_VAR=MAX(&A,&B);/* generates data step source code*/
%IF %upcase(&KEEP_VAR) = Y %THEN %STR(KEEP &OUT_VAR;);
%MEND;
DATA ABC;
%TEST(A=1,B=3,OUT_VAR=FIRS,KEEP_VAR=Y);
%TEST(A=2,B=4,OUT_VAR=SEC,KEEP_VAR=Y);
%TEST(A=3,B=5,OUT_VAR=THIR,KEEP_VAR=N);
RUN;
The macro code still has to rely on the caller to specify a proper KEEP_VAR parameter Y or not Y.
An alternative is to push the Y/N KEEP concept into two parameters, use of one parameter over the other discriminates the KEEP.
%MACRO TEST(A=,B=,KEEP_VAR=,TEMP_VAR=);
%IF %length (&KEEP_VAR) %then %do;
&KEEP_VAR
%end;
%else %do;
&TEMP_VAR
%end;
MAX(&A,&B);
%IF %length (&KEEP_VAR) %then %do;
KEEP &KEEP_VAR;
%end;
%MEND;
DATA ABC;
%TEST(A=1,B=3,KEEP_VAR=FIRS);
%TEST(A=2,B=4,KEEP_VAR=SEC);
%TEST(A=3,B=5,TEMP_VAR=THIR);
RUN;
For me, a widely used macro should have clarity of invocation. The black box of the macro implementation can strive to be as clean and efficient but does not have to be.
It is not clear what you are trying to do, but you can use multiple KEEP statements if want.
data want ;
x=1;
keep x;
y=2;
z=3;
keep z;
run;
Although for your application it looks like you might want to generate a DROP statement instead.
%MACRO TEST(A=,B=,OUT_VAR=,KEEP_VAR=Y);
&OUT_VAR=MAX(&A,&B);
%IF (&KEEP_VAR ^= Y) %THEN %DO;
drop &out_var ;
%end;
%MEND;
DATA ABC ;
%TEST(A=1,B=3,OUT_VAR=FIRST)
%TEST(A=2,B=4,OUT_VAR=SECOND)
%TEST(A=3,B=5,OUT_VAR=THIRD,KEEP_VAR=N)
RUN;

SAS macro modification

I have two values which represent dates:
a=101 and b=103
Below is first macro saved in separate file one.sas:
%global time nmall;
%let nmall =;
%macro pmall;
%do i=&a. %to &b;
%if &i =&a. then %do;
%let nmall=&nmall.&i;
%end;
%else %let nmall=&nmall.,&i;
end;
%put (&nmall);
%mend;
%pmall;
So above pmall give me values 101,102,103.
Below is second macro:
%include “one.as”;
%macro c(a=,b=);
%let m=;
%let m1=;
%do i =&a %to &b;
%let o=&i;
proc sql;
create table new&o as select * from data where nb in(&o.);quit;
%let m =&m.date&o;
data date&o.;
set date&o.;
if pass =&o.;
run;
proc sort data=date&o.;
by flag;
end;
data output &a._&b.;
set &m;
%mend;
The above macro creates three datasets date101 date102 and date 103, then append it to output101_103.
I am trying to modify above macros in such a way that I will not use %macro and %mend approach. Below is the modified macro code:
data a_to_c;
do o=&a to &c;
output;
end;
run;
so above code will have values 101 102 103 in variable o for dataset a_to_c.
data _null_;
set a_to_c;
call execute ('create table new’||strip(o)||' as select * from data
where nb in(’||strip(o)||' );quit;’);
run;
I want to know how to do below things.
Create pmall values in a macro variable in my modified macro inside the data step data a_to_c, so that I can use it further.
How to proceed from %let m macro in the first macro code to new code which I am developing above.
Geetha:
I think you will find the macro-ization of the process to be far easier if you go from a data-centric explicit solution and proceed abstracting the salient features into macro symbols (aka variables)
The end run solution appears to be:
data output_101_to_103;
set original_data;
where nb between 101 and 103;
run;
proc sort data=output_101_to_103;
by nb flag;
run;
In which case you could code a macro that abstracts 101 to FIRST and 103 to LAST. The data sets could also be abstracted. The abstracted parts are specified as the macro parameters.
%macro subsetter(DATA=, FIRST=, LAST, OUTPREFIX=OUTPUT);
%local out;
%let out = &OUTPREFIX._&FIRST._&LAST.;
data &out;
set &DATA.;
where nb between &FIRST. and &LAST.;
* condition = "between &FIRST. and &LAST."; * uncomment if you want to carry along the condition into your output data set;
run;
proc sort data=&out;
by nb flag;
run;
%mend;
And use as
%subsetter (data=original_data, first=101, last=103, outprefix=output)
Note: If you did keep the condition variable in the output data, you WOULD NOT be able to use it directly as a source code statement in a future data step, as in if nb condition then ...
I suppose you could also pass the NB and FLAG as parameters -- but you approach a point of diminishing returns on the utility of the macro.
Macro-izing the specific example I showed doesn't make too much sense unless you need to perform a lot of different variations of FIRST and LAST in a well documented framework. Sometimes it is just better to not abstract the code and work with the specific cases. Why? Because when there are too many abstracted pieces the macro invocation is almost as long as the specific code you are generating and the abstraction just gets in the way of understanding.
If the macro is simply chopping up data and reassembling data, you might be better served rethinking the flow using where, by, and class statements and abstracting around that.
Pmall is macro variable which will have list of values separated by
commas. In my modify macro, i want to create pmall as macro variable
in the datastep data a_to_c; do o=&a to &c; output; end; run; – geetha
anand 1 min ago
To create a macro variable from within a data step using the CALL SYMPUTX() function.
data a_to_c;
length pmall $200 ;
do o=&a to &c;
pmall=catx(',',pmall,o);
output;
end;
call symputx('pmall',pmall);
drop pmall;
run;
If you really want to generate code without a SAS macro you can use CALL EXECUTE() or write the code to a file and use %INCLUDE to run it. Or for small pieces of code you could try putting the code in a macro variable, but macro variables can only contain 64K bytes.
It is really hard to tell from what you posted what code you want to generate. Let's assume that you want to generate an new dataset for each value in the sequence and then append that to some aggregate dataset. So for the first pass through the loop your code might be as simple as these two steps. First to create the proper subset in the right order and the second to append the result to the aggregate dataset.
proc sort data=nb out=date101 ;
where nb=101 ;
by flag ;
run;
proc append base=date101_103 data=date101 force;
run;
Then next two times through the loop will look the same only the "101" will be replaced by the current value in the sequence.
So using CALL EXECUTE your program might look like:
%let a=101;
%let c=103;
proc delete data=date&a._&c ;
run;
data _null_;
do nb=&a to &c;
call execute(catx(' ','proc sort data=nb out=',cats('date',nb,'),';'));
call execute(cats('where nb=',nb,';')) ;
call execute('by flag; run;');
call execute("proc append base=date&a._&c data=");
call execute(cats('date',nb));
call execute(' force; run;');
end;
run;
Writing it to a file to run via %INCLUDE would look like this:
filename code temp ;
data _null_;
file code ;
do nb=&a to &c;
put 'proc sort data=nb out=date' nb ';'
/ ' where ' nb= ';'
/ ' by flag;'
/ ';'
/ "proc append base=date&a._&c data=date" nb 'force;'
/ 'run;'
;
end;
run;
proc delete data=date&a._&c ;
run;
%include code / source2;
If the goal is to just create the aggregate dataset and you do not need to keep the smaller intermediate datasets then you could just use the same name for the intermediate dataset on each pass through the loop. That will make the code generation easier as then there is only only place that needs to change based on the current value. Also that way you only need to have two dataset names even for a sequence of 10 or 20 values. It will take less space and reduce clutter in the work library.

How to mask an & in a SAS macro variable call via an indirect reference

I'm using the following macro code to generate a list of clients from a table:
%macro listclient;
proc sql noprint;
select unique(client)
into :cli1 -
from books; quit;
%put Total Number of Clients: &sqlobs..;
%do i=1 %to &sqlobs;
%put Client &i &&cli&i;
%end;
%mend listclient;
%listclient
My problem is that some of the clients have names such as Smith & Jones, so I need to use some sort of masking function to get a correct resolution. I've tried a few things, my best guess being to use %nrbquote(&&cli&i) but can't seem to null the problem out. I imagine that I am making a syntax error, or that there may be an issue with the indirect macro variable referencing.
The code runs but with a warning every time an & is encountered in the client name.
I would prefer not to go down the route of replacing all of the &s with "and"s and then changing them back again!
Can anybody be of assistance?
A useful macro function for this is %SUPERQ(). You just need to provide it with the name (not the value) of the macro variable to be quoted.
%do i=1 %to &sqlobs;
%put Client &i %superq(cli&i);
%end;
You could also build your macro variable to already be formatted as quoted strings. If you use single quotes then the macro triggers inside will be ignored.
proc sql noprint;
select distinct cats("'",tranwrd(name,"'","''"),"'")
into :name1 -
from sashelp.class
;
quit;
%put &=NAME1;
NAME1='Alfred'

Using a series of values for a SAS macro parameter

I'm looking for a way to use a series of values for a macro parameter instead of a single value. I'm basically manipulating a series of files for consecutive months (May 2014 to Sept 2015) and I've written a macro to take advantage of the naming conventions. However, I'm still manually writing out the months to use the macro. I'm doing this many times over with lots of different files from this month. Is there a way to have the parameter reference a list of values and go through them like an array/do-loop? I've looked into %ARRAY as a possibility but that doesn't seem to do what I'm looking for unless I'm not seeing it's full capability. I've attached a sample of this code below.
%MACRO memmonth(monyr=);
proc freq data=work.both_&monyr ;
table var1/ out=work.mem_&monyr;
run;
data work.mem_&monyr;
set work.mem_&monyr;
count_&monyr=count;
run;
%MEND memmonth;
%memmonth(monyr=may14)
%memmonth(monyr=jun14)
%memmonth(monyr=jul14)
%memmonth(monyr=aug14)
%memmonth(monyr=sep14)
%memmonth(monyr=oct14)
%memmonth(monyr=nov14)
%memmonth(monyr=dec14)
%memmonth(monyr=jan15)
%memmonth(monyr=feb15)
%memmonth(monyr=mar15)
%memmonth(monyr=apr15)
%memmonth(monyr=may15)
%memmonth(monyr=jun15)
%memmonth(monyr=jul15)
%memmonth(monyr=aug15)
%memmonth(monyr=sep15)
In general I would recommend passing the list of values as a space delimited list and adding looping logic in the macro. If spaces are valid characters in the values then use some other delimiter. Do not use comma as the delimiter as it means you will need to use macro quoting to call the macro.
So your basic macro is this.
%macro memmonth(monyr);
proc freq data=work.both_&monyr ;
table var1/ out=work.mem_&monyr (rename=(count=count_&monyr)) ;
run;
%mend memmonth;
%memmonth(may14)
%memmonth(jun14)
You could change it to this.
%macro memmonth(monyrlist);
%local i monyr;
%do i=1 %to %sysfunc(countw(&monyrlist));
%let monyr=%scan(&monyrlist,&i);
proc freq data=work.both_&monyr ;
table var1/ out=work.mem_&monyr (rename=(count=count_&monyr)) ;
run;
%end;
%mend memmonth;
%memmonth(may14 jun14)
If you always want to process all of the months in an interval then you could just pass in the start and end month of the interval.
%macro memmonth(start,end);
%local i monyr;
%do i=0 %to %sysfunc(intck(month,"01&start"d,"01&end"d));
%let monyr=%sysfunc(intnx(month,"01&start"d,&i),monyy5.);
proc freq data=work.both_&monyr ;
table var1/ out=work.mem_&monyr (rename=(count=count_&monyr)) ;
run;
%end;
%mend memmonth;
%memmonth(may14,sep15)
If you have a source list, whether it is a text file or a dataset, you can use a simple data step to generate macro calls. So if you have an input dataset with the variable MONYR then your driver program would look like this:
data _null_;
set mylist ;
call execute(cats('%nrstr(memmonth)(',MONYR,')'));
run;
If the source is a file with the names then replace the SET statement with the appropriate INFILE and INPUT statements. If the source is a directory name then look at one of the many SAS ways to read the names of files in a directory into a dataset and use that to drive the macro call generation.