Tips on writing SAS macros

[This site is not connected with the SAS Institute]

[Last updated - 22 May 2011]


A well-written SAS macro is a very valuable thing and can endure for a long time. Indeed, a macro once written can quickly become a "standard" macro and shape the way future sas programs are developed, so it is important that macros be written well the first time. Many people aspire to writing SAS macros. They want to write "good" macros. But how do you go about writing a "good" macro as opposed to a "not-so-good" macro? The learning curve for writing macros can be long, slow and painful. As your experience grows you might be appalled at what you had written maybe only two years earlier. Wouldn't it be nice if you could jump over all the obstacles that you will encounter in your first years of writing macros and instantly reach that point where you know your macros are written to a high standard? Well, hopefully, if you follow the tips on this page then you will be able to do just that. There are quite a lot of ideas on this page to digest, however, and some careful thinking to be done.

Before You Start

Before you write a SAS macro you need to understand something. And that is that sas macro code is separate from sas code. They are not the same. They have a different syntax. Also, and of highest importance, sas macro code gets processed before the sas code. The sas macro code gets interpreted and, as a result of this action, substitutes something in to the code that then gets passed on to the sas compiler. There is a lot of confusion about this so I want to borrow from other computing languages to help clarify this. So forget SAS, for a moment, and allow me to explain. Macro code in other languages is commonly used to tailor the actual code. Depending on what parameters are set it will change the code. The idea being that under different circumstances you want to run slightly different code. So the macro code runs and it might change some file names or add or skip a chunk of code or what have you. But the point is that it is not running at the same time as the code. It runs before the code and its job is done before the code itself starts running. "Macro" means "bigger" or "larger in scope". It is this "larger in scope" concept that applies. It sees the bigger picture, if you like, and sits above actual code. It is like an organizer or editor of code. It makes substitutions or makes deletions before passing on the resulting code to the compiler or interpreter. So its job gets done separately and before the actual code gets run. Back to SAS again - SAS MACRO CODE IS THE SAME. It is done and gone before the sas code itself gets run. The macro usually makes a substitution that gets stuck into the stream of SAS code and then passed on to the compiler. So let's make this the first tip and keep it simple so you remember it - macro code runs BEFORE sas code. This is not something you can just skip over and hope it falls into place at a later date. I'll put it another way - all macro code gets removed or substituted in sas code so that when the sas code runs the macro code is no longer there.

Macros you should NOT write

Writing a macro is aimed at adding value over and above not having that macro. I sometimes come across macros written to call "proc sort". This is a prime example of when NOT to write a macro. Calling "proc sort" going through a macro is worse than calling "proc sort" itself. All programmers know how to call "proc sort" and expecting them to call a macro to do it does not add anything of value and is a nuisance instead. So here is my next tip -- you need a good reason to write a macro.

Utility macros vs. project macros

Experience has taught me that there are two type of macros and different rules apply to each. "Utility" macros, as I see it, are simple macros that perform some sort of useful sas task for you. Project macros, on the other hand, will be creating something of direct relevance for whatever project you are involved with. So utility macros belong in the realm of SAS and project macros belong in the realm of the project and for me they should be kept separate and will have different guidelines on how they should be written. So this is the next tip. There are two types of sas macros -- utilty macros and project macros -- and guidelines for one type are not good for the other.

Utility macros

As mentioned before, utility macros are small macros that do a "SAS" sort of thing. They belong in the realms of SAS. They should look and feel like SAS. If we look in the sasautos library supplied with the sas product then we will see old macros there like %left() and %trim(). These act just like macro functions. Firstly, they have positional parameters, not named parameters. This makes them easier to call and allows them to look and feel like sas macro functions. So the next tip is utility macros should have positional parameters.

A macro like %trim() is known as a function-style macro. It returns a result. It sort of "boils down" into the result. It does not "push" any SAS code. It executes sas macro code, alright, but not sas code. There is a big difference and you should be sure you understand this because you will not be able to write good utility macros until you do. You will see something odd inside these macros. Have a look at the code and look for an odd line with no semicolon near the end in the macro words. That "%eval(&i - 1)" you see with no semi-colon at the end is not an error. It is what the %words() macro "boils down" to. It subtracts one from the "i" counter and it boils down into that value to give you the number of words. If you look at the alphabetical order macro page, jump to the "purposes" and look for those macros described as "function-style" then you will see one of these strange lines without a semi-colon at the end in all of them. The macros "boil down" into that value. This is what makes them function-style macros. You need to understand how they work and appreciate that they are running sas macro code and not "pushing" sas code itself. They are boiling down into an expression that is the result you want. So this my next tip is know what a function-style macro is and how to write one. Use those examples to guide you.

You can open and close datasets and even read them using %sysfunc calls and using this system is better that "pushing" sas code. And why? It is because if you do not "push" sas code in your macro then you can slot in a call to a macro right in the middle of your sas data step or whatever. If it did push sas code and you called it in the middle of a data step then the macro call would resolve itself into the sas code it was using and so you would get sas code inserted into your data step and your program would crash. If you can avoid using sas code and use %sysfunc calls instead then this is of benefit. Take a look at an example where a flat file is being assigned, opened, read, closed and deassigned all without a single line of actual data step sas code in the macro readfile.

Suppose you want to know the number of observations in a dataset. You could do it like this...
data _null_;
  set dsname nobs=nobs;
  call symput('nobs',compress(put(nobs,11.)));

...and maybe think you had the beginnings of a jolly useful macro. But this would not be a "good" macro because you could have avoided using data step sas code at all for this task. It is better to use %sysfunc calls, even at the expense of simplicity. Take a look at the macro that does this (i.e. gives the number of observations in a dataset) webbed on this site called nobs and the macro it calls called attrn. Yes, it looks much more complicated, but this is a "good" utility macro because it avoids using data steps or procedures. So the next tip is when writing utility macros, avoid "pushing" sas code if at all possible.

Project macros

By "project" macros I mean macros that perform a project task, rather than do something useful within the sas environment. The rules for these are different to that of utility macros. Typically, these macros will contain five or more parameters so it does not make sense to use positional parameters like for utility macros. And if we are going to use explicit parameters then we should ensure naming standards are adhered to. It is annoying to have data= and out= for the input and output of one macro whereas having dsin= and dsout= in another macro in the same project. You need to stick to a set of standard names and adhere to them throughout your collection of project macros. I will straightway recommend you use dsin=, dsout=, dspop= and ds???= for whatever datasets you have going into and coming out of these project macros. So the next two tips are use explicit parameters for project macros and use the dsin=, dsout=, ds???= naming convention for dataset parameters.

Sometimes you will have a project macro with only a few parameters that would be useful to other projects as well. Since you won't know if these other projects will use the same parameter naming convention then you might like to break the above rule in special cases.

Commenting Macro Code

Do not mix up the two comments styles when writing macro. The " * ; " style is for sas code whereas the " %* ; " style is for macro code. Do not mix them up. If you have a part of your macro that is sas code then comment with sas code comments. If another part is macro code then comment with macro code comment. Certainly, for utility macros that are pure macro code, you will get an error if you use non-macro comments. If you use " /* */ " style comments then they are good for both. But remember, do not use " * ; " style comments for macro code. Use " %* ; " instead.

Macro variables and their scope

The thing that will cause you the most difficult to solve problems with macros is sloppy use of macro variables. You need not ever suffer from this so long as you are disciplined and always declare your macro variables as being local to your macro. If you do not do this then a macro variable such as "i" that you are using in one macro could be altered by a call to a sub macro that again uses the macro variable "i" without declaring it local. So this very important tip is always declare your macro variables as local like I have done in this example remove.

The most common one to forget is the "i" as in %do i=1 %to... I'll put my hand up and admit to having made this mistake many times and this is the one that will cause you the most trouble for nested macros.

Shortening and defaulting parameter values

You might have a parameter that the user sets to "yes". They might set it to "YES" instead or even "y" or "Y". Your macro needs to cope with this variation. The best way to handle this is to firstly make sure the parameter has a default value if it is supposed to and then set it to the uppercase first character. Then if it was set to "yes" or whatever it will always by "Y" you test for in the macro code and if set to "no" or whatever variation you just test for the "N" in your code. If the first character of the parameter value is enough information to identify the setting then always reset the parameter to first character uppercase.

Don't duplicate variable lists

Suppose you have been passed a list of variables by a parameter then you can use the %quotelst() and %commas() sasautos extension macros to put quotes round them or delimit them with commas for the benefit of proc sql or whatever. There is no need to duplicate lists of variables for different purposes.

Using %sysfunc() to give you more macro functions

If you look at the SAS documentation on macro functions then you might think you are limited to using only those functions in your macros. This is not the case, however. Most of the functions available in sas code can be accessed using %sysfunc() so you can use many more functions in your macros. In the example below, you see me using the ceil() function.
17   %put %sysfunc(ceil(-2.4));

%sysfunc() with a format

If you add a second parameter to a %sysfunc() call then this second parameter is treated as a format that is used to display the result of what you supplied as the first parameter. In this way you can use it like a data step "put" statement. It is easy to forget about this second parameter format. See below for an example of where the date9. format is applied to today's date.
33   data _null_;
34     x=date();
35     put x=date9.;
36   run;

NOTE: DATA statement used (Total process time):
      real time           0.10 seconds
      cpu time            0.03 seconds

38   %put %sysfunc(date(),date9.);

%eval() and %sysevalf()

For many years, macro programmers were limited to calculations done in integers only, or having the result from calculations shown as an integer when it should have been a floating point number. This limitation was removed with the %sysevalf() function which handles floating point numbers. Here you see the two macro functions compared. Note that %eval() returns the number 2 but %sysevalf() returns the floating point number 2.5 .
55   %put %eval(5/2);

57   %put %sysevalf(5/2);

Be careful using %index() for matching

It is common careless macro coding to match on variable names like this:

%if %index(ONE TWO THREE,THR) %then %do...

The reason the above may not work correctly is because a match may be found on substrings of names and not the whole name itself. In the example above, "THR" will match with the first three letters of "THREE" and we may not want this.  Better would be to use a macro function %indexw, if it existed, to match on whole words. No such macro function exists but we can use the sas code function indexw() through a %sysfunc call like this:

%if %sysfunc(indexw(ONE TWO THREE,THR)) %then %do...

I use the %quotelst sasautos extension macro to ensure I get a full and correct match, bearing in mind that the %quotelst macro adds double quotes and not single quotes around each list element.

%if %index(%quotelst(ONE TWO THREE),"THR") %then %do...

So my tip is match on quoted list elements, not unquoted ones, or match on whole words.

The "%global" declaration trick

This is the tip - declaring a macro variable as %global does not change its contents. You can declare a global macro as global multiple times and no warnings will be issued. You can use this as a trick to see if a global macro variable you are expecting has been set up or not. It's a "dirty" trick because it would be better if the value were passed as a parameter but sometimes you will want to use it. Here is an example of when you might choose to use this trick. Suppose you have a clinical trial and you have a global macro variable set up in the start of each code member that is called TAPERVIS and is set to a value corresponding to the start of a drug taper phase. And suppose you have a macro that gathers together the important dates for each subject, including the start of their taper phase. This macro could be useful to many projects, some of which do not have a taper phase. So you would either calculate this date or not depending on whether this global macro parameter has been set. So if you do this near the top of the macro:

%global TAPERVIS;

...then later on you can test whether it was set and process accordingly using:

%if %length(&TAPERVIS) %then....

Yes, its dirty, but I bet you'll use it sometimes now you know about it.

Boolean Expressions

I use this technique but maybe I should not. I will tell you about it here rather than recommend it. If a value is other than 0 in SAS then it is true. If it is 0 then it is false. It's the same for sas code and sas macro code and it can save a bit of space. So if you see something like this:

%if %length(&dspop) %then...

...then it is testing for whether the dspop= parameter has been set to something (i.e. has a non-zero length) and if so will go off and do something. The long way of writing it would be:

%if %length(&dspop) GT 0 %then...

It saves a bit of space but is not a recommended practice. So my next tip is simply learn to recognize boolean expressions for what they are.

Setting up a "byline" Macro Variable

Suppose you have a by= parameter in your macro so that the user can specify the "by" variables. If this is set then you will probably need to add the line "by &by ;" underneath some of the procedure calls. This can be handled by setting up a macro variable like in the following:

%local byline;
%if %length(&by) %then %let byline=by &by %str(;);

and then you can add this line under your procedure calls as follows:

proc summary nway data=ds;

In this way you either get nothing if the by= parameter was not set or you get the correct "by" line.

Actually, if you try it out, all the sas procedures I know of will accept a null "by" line in the form "by ;" and will just ignore the line. This isn't documented anywhere, though, and so I wonder how long that will remain the case. That makes me think it is better to do it the way I am suggesting above.

Testing for quotes

If you don't know how to test for quote marks you can waste a lot of time and see a lot of strange error messages. Use %str(%"%') and %qsubstr() to test for the existence of quotes when you need to. %str(%'%") is the way to tell the macro parser that you really do mean a quote mark as a string.

Here is a bit of code you can play with that tests for quotes, compresses for quotes and finally inserts a space inside the quoted string if the second character is not a space:
%macro test;
%let str="test";
%if %qsubstr(&str,1,1)=%str(%') %then %put >>>>> single quote;
%else %if %qsubstr(&str,1,1)=%str(%") %then %put >>>>> double quote;
%if %index(%str(%'%"),%qsubstr(&str,1,1)) %then %put >>>> quoted;
%put str=&str;
%put str=%sysfunc(compress(&str,%str(%'%")));
%if %qsubstr(&str,2,1) NE %str( ) %then %let str=%qsubstr(&str,1,1) %qsubstr(&str,2);
%put str=&str;

Making Sure Something is Quoted

Carrying on from the previous tip, suppose the user has supplied a split= value to a parameter that you will be passing on to a proc report step where the syntax expects is to be quoted. You can't be sure that the user has supplied it as a quoted string or an unquoted one. You want it quoted, but you don't want it quoted twice, of course. You can do it like this:

proc report... split="%sysfunc(compress(&split,%str(%'%")))" ....;

Use %commas() to delimit variable list with commas

If you are using proc sql on a list of variables then you may not know which of the parameters have been supplied a value. This can lead to syntax errors in sql as you might have followed an unset parameter value with a comma. You end up with two commas together which sql will certainly complain about. Use the sasautos extension macro %commas to delimit a parameter list of variables for sql and you will never have a problem.

Adding to macro strings

Sometimes you'll be mixing sas code and macro code. You'll have no choice in certain circumstances. And you may want to output an accumulation of values to a macro variable. If you are storing the result in a data step character variable then you should set the length to the maximum size of 32767 bytes so that you are not losing any data off the end of the string. Another way you can accumulate values and not have to bother about overflowing a length limit is to use "call execute"s within your data step that increment the value of the macro variable. These will be performed after the data step completes and will safely build up your macro variable contents to whatever size you like. You will see how I have done this in the macro missvars. Here is a piece of the code that does this:
do i=1 to &nvarsn;
  if _nmiss(i) EQ '1' then call execute('%let &globvar=&&&globvar '|| trim(vname(_num(i)))||';');

Input and output dataset names

The next tip is always assume the user calling your macro has put any combination of where/drop/keep/rename after their dataset name. Not just input but output names as well. You have to process around it. Don't even force it to uppercase as the "where" clause might contain a case-sensitive string. The best way to avoid problems is to make a temporary copy of the dataset like this:
data _npctin;
  set &dsin;

Also, when it comes to creating the final output dataset, do it something like this.
data &dsout;
  set _temp;

Note that although I highly recommend the above, you can not use this technique where your macro does not push any sas code such as function-style macros that have SCL at their base like %nobs which calls %attrn.

Defaulting an output dataset name

It is common practice to default an output dataset name to that of the input dataset name if no output name is specified. But keep in mind the previous tip that the user could have supplied where/drop/keep/rename modifiers with the input dataset so you need to scan up to the first bracket. Use this technique whether the input dataset has modifiers supplied with it or not. It will always give you the result you want. Note that, like a quote mark, the left parenthesis must be both enclosed in a %str() statement and be preceded by an %.

%if not %length(&dsout) %then %let dsout=%scan(&dsin,1,%str(%());

Never assume both character and numeric variables are present

Don't write sas code within your macros that assume both character and numeric variables are present. They could all be numeric or all characater, sometimes. If you declare temporary arrays mapped to _numeric_ and _character_ then you could get a problem. It is better to count them using the %nvarsn and %nvarsc macro as in the sasautos extensions. You can see I have done this in the macro that counts missing values called misscnt.

Testing parameters for numeric

If a value supplied to a macro parameter has to be numeric for the macro to work, then it helps if you test this value to see whether it is numeric or not and to issue an error message if there is a problem with the value that clearly states what the problem is, rather than letting the value through to cause a syntax error in the macro code. To test a value for numeric, use the %datatyp macro supplied by the SAS Institute in their macro autocall library. You have this macro if you have SAS. Use of it is documented in the SAS online documentation.

Catch as many errors as you can

If a user calling one of your macros has forgotten to set some of the parameter then they have probably forgotten to set others as well. Instead of just giving them the one error message you might as well give them all the error messages possible you can so it has a chance of working second time round. The next tip is the same as the title, which is catch as many errors as you can.

The way I code it is to declare a local macro variable called "error" and set it to "0" (false). Then when I spot an error in a parameter I set it to "1" (true). I check a number of parameters like this and they put out error messages but it carries on running. Then I do the test "%if &error %then %goto error;" and it branches to a point an the bottom of the macro and exits with yet another error message saying that it is leaving the macro due to the errors listed. I'll point you to some code shortly. You might want to put in more than one section to check for errors in parameters. Firstly making sure you have got enough of them set and then after that to make sure the settings are consistent with dataset contents, field lengths or whatever. "Good" macros are written to catch errors. If you have "bad" going into your macro then you will have "bad" coming out. I have a clinical reporting macro that does a veritable "blitz" on possible errors. Take a look at the two error detection sections at the start of the code and also look at where the "error" label is at the end of the code and how it exits. It's a lot easier and neater to do it like this rather than using the "%else %do; %end;" sort of processing. The macro to look at is npcttab.

Naming temporary datasets

Your macro might contain many temporary datasets. But what do you call them? There is a danger that you could overwrite an existing dataset. There are various ways to get round this. You could have a special macro data library such that your temporary dataset would be called macro.temp instead of just temp. That would get round it. But some of the datasets might be huge and although there may exist enough space in the work files then there might not be enough in your "macro" data area so let's skip that idea. It would not be a "good" macro if it fell over occasionally due to lack of space. It has to go in the work area but we need to avoid overwriting an existing dataset. I use two methods for this. One is to start the temporary datasets with an underscore. This makes it is sort of reserved class of words especially for macros. The other is I start the name of the dataset with the start of the macro name. This way, if you are nesting macros such that one macro is being called by another macro then they will not overwrite each others temporary datasets. I think this is good enough to be a tip so start your temporary macro datasets with an underscore and follow with the start of your macro name to avoid good datasets being overwritten. You can see I am doing this in npcttab.

Naming temporary variables

Suppose you are adding new calculated values to an input dataset somewhere in your macro. If the user is free to specify the input variables then there is just a chance that the variable name you choose is the same as one that already exists. To avoid this problem, if you are adding a temporary variable to an input dataset then start it with an underscore if there is any possibility of a name clash.

More than one by= variable

If you have a by= parameter for your "by" variables then keep in mind that the user will sometimes supply a list of these variables. And they could have very valid reasons for doing so so your code should be able to cope with it. So don't use expressions like "if last.&by then..." in your code. Use something like "if last.%scan(&by,-1) then.." to refer to the rightmost variable or whatever is appropriate.

Be careful with global macro variables

Be careful and sparing when it comes to declaring macro variables as global. You will certainly not be doing anybody a favor if you declare these liberally without the user's knowledge. You might be overwriting some of their own global macro variables. "Good" macros use global macro variables sparingly. "Good" macros also use a convention for them so they become a sort of reserved class of name. I usually start and end global macro variables with an underscore with the understanding that any global macro variable that starts and ends with an underscore is reserved for my macro use only. Also, by convention, a macro variable that starts and ends with an underscore MUST be declared global.

Keep these limited and starting and ending with an underscore in your macro collection and you are unlikely to ever encounter a problem.

Restore System Options

If you change one of the system options in your macro to suppress notes or temporarily change line size, for example, then be sure to restore them before leaving the macro. Suppose you want to suppress notes at a stage in your macro. Then if you don't restore them before leaving the macro then this "nonotes" option will remain in effect and rob the user of useful notes in their log. So my next tip is if you change system options then restore them to original before leaving the macro. That is unless you really did want an option to remain in effect. But you had better document that well and warn the user that that would happen.

The way to code this is to set up a local macro variable, get the list of options you are about to change and assign them to that variable and then use that string to restore options at the end.

%local opts;
%let opts=%sysfunc(getoption(ls,keyword)) %sysfunc(getoption(notes,keyword));

...and then at the end of the macro....

options &opts;

Note that I used the "keyword" option. This will give you the form of the statement that you use to restore that option. If you do this with "linesize" for example and the linesize was 100 then it would return "LINESIZE=100" rather than just "100". For other options such as "notes" it will have no effect. It is safer to do it this way and it allows you to use the above method without any thought as to what mix of options you will be resetting and restoring.

Using Shell Macros

You can make the user's life easier sometimes if you use shell macros. These are macros that do hardly any work except call another macro. You can pick a helpful name for your shell macro and have many values conveniently defaulting so that the user does not have to remember what the parameter settings should be. Suppose you have a macro called "aetab", for example, that produces a table of AEs (Adverse Events) and their counts and percentages. This macro could be used in other circumstances not directly related to AEs. But instead of the user having to call this "aetab" then you could use a shell macro with a better name that just calls aetab. Maybe you could have the variable names defaulting as well as defaults for column widths. That could save the user having to look up a manual on the macro. In fact the aetab macro that does all the work might be better off being renamed into something more general and then you could have a shell macro called aetab that called it. So my next tip is using shell macros can make life easier.

Most of the sasautos extension macros are shell macros. The low level macros %attrc, %attrn and %attrv do most of the work. Take a look at the macro called nobs.

Disabling chunks of code

If you want to disable a chunk of code then chances are you can not do it with /* */ since parts of the code itself might contain comments like it. In this case the comments would only work up to the first */. So the best way to disable chunks of code is to put it in a macro declaration and never call the macro, like this:

%macro dontdo;
%mend dontdo;

Writing "really big" macros

I am in the process of writing a monster macro at the moment. It is going to be called "aetab". It will list out the "N" counts and the percentages of subjects suffering from Adverse Events as broken down by body system. The macro will do so much processing that it will be very, very large. Maybe a thousand lines in total or more. But it doesn't bother me. I don't even get stressed about it. If you are disciplined and you use the methods described so far then you can do it. You can't do it in a day, maybe, but you can do it in a week or two. The extra tip to add is that you can write extremely large and complex macros so long as you learn to break down your tasks into achievable, logically separable pieces. I think Henry Ford said something like that but I don't know the reference. Once it is all written in pieces then you might want to bring the pieces together or not. It is up to you. But you can always set yourself the task that is a piece of the big macro that is achievable and achieve it. I am getting there in the process of writing "aetab" by writing a sub-macro called "npctcalc" that will calculate totals for "N" and "PCT" without producing any output. It will just create an output dataset. I'll leave the rest to "aetab" proper. I might absorb the code I write into the final "aetab" macro or not. I think I'll leave it sitting outside as I have used the code for "npctcalc" to illustrate various coding techniques and so it serves a useful purpose as an independent entity. If you can split a task into knowingly achievable pieces then you can achieve it. If there is any special skill you need to learn then it is knowing where to put that split. And if there is an extra tip to state here then that is to split a large task into pieces in such a way the separated pieces can stand on their own to a large degree and have independent achievable goals. It is just a restatement of what I highlighted already so I won't restate it as a tip. I can't give much in the way of detail on how to do this but consider that some people have built their own houses from the dirt upwards. They just had the sense to separate each task and achieve each one in turn to get there. And building a house is much harder than writing a major macro. So if they were able to do it using their common sense then so will you be able to write your major macro. It's just a case of being methodical and using a logical approach.

Think Before You Code

Think before you code. Don't start coding if you have only part thought out a macro. Always give yourself time to think about it before you even start coding. The longer you think about it the clearer things will become. Other things will fall into place around your ideas and you will get further ideas. Possible problems will spring to mind and then you will think of ways round that and perhaps reformulate your ideas. The longer you give yourself time to think, the better the macro or system of macros you will write. If your ideas are clear then your code will almost drop into place. Conversely, if you are getting bogged down in your coding it could indicate that your ideas have not been thought out correctly. Coding won't be a big effort which it can otherwise be if your ideas are not clear. You will never get bogged down if you have thought out everything. If you allow yourself the time to think things through carefully then you will write better macros quicker so never code macros under pressure. Once a macro goes into production and people start to use it then it is maybe too late for making changes that you thought of afterwards. You've got to do all your thinking ahead of writing the macro.

Realize that you are a limited resource. Never code when you are tired. If you find yourself making silly little errors then stop and do something different for the rest of the day. In the morning you should be fine again. Take frequent breaks to avoid getting tired. You can spend four hours trying to solve a problem when you are tired, just digging a deeper hole for yourself, that you could probably solve in four minutes the next morning. You won't be doing yourself or your employer any favors by pushing yourself too hard. Writing macros is not an endurance test and if you turn it into an endurance test then you will be writing poor code and will have to go back and rewrite some of it later.

Batch development is best

I develop my complex macros in batch, rather than using interactive SAS. To describe a bit more, I have my macro code open in an editor ("Notepad" usually) and after I have amended it, I run the sas program in batch using a command on the command line. I have Cygwin on my PC so when I develop SAS code it is just like using Unix for me. The reason it is better to develop code this way is because when you get macro errors, these can persist in an interactive session, even if you have amended the macro to fix a problem. You can always delete the macro from the work/sasmacr library, using the "Explorer", but often this does not restore things the way it should be. You can get error messages saying a macro is not found, even if you know it is there. You can get even stranger "SCL code" error messages. These can be frustrating and these sorts of annoying problems are not helpful if you are developing complex macros. If you develop in batch then you get a fresh sessions each time you run your code, which is much better, so my advice is to develop complex macros in batch rather than using interactive SAS sessions.

Deep Crisis Debugging

I am sure you know about using the options mprint and symbolgen to give you more details about why a macro isn't working. There's "mlogic" as well if you have time to wade through all the log comments. But if you have written a large macro there could be errors in it that stop it being recognized as a macro and you get no diagnostics at all. Instead you get something like a message saying you have a missing %mend when you know full well you have put it there. If things get really nasty then you need to use the PC SAS editor. Read the macro into the editor. It sets colors for different parts of the syntax and it might know where your error is and it will show it as an unexpected color change. The recognized macro code will be in blue. Check it very carefully to make sure you have your %then's matching your %if's etc. Your problem will usually be caused by an "%if" being written as "if" and a "%then" written as "then". Don't go too much on the error messages you get out. If your macro isn't compiling at all then the PC SAS editor with its different colors is your best bet.

Tidy up afterwards

You should delete all macro temporary datasets before leaving the macro. This can be done with a "proc datasets". Take a look at the end of npcttab for an example of this. This is the last bit of macro processing you should do and that is why I made this the last tip on this page.


Go back to the home page.

E-mail the macro and web site author.