Lisa Sanbonmatsu, Consultant, Somerville, MA: Moving From Macro Variables To Macros

Download as pdf or txt
Download as pdf or txt
You are on page 1of 9

Moving from Macro Variables to Macros

Lisa Sanbonmatsu, Consultant, Somerville, MA

When the macro facility processes our code, it will


ABSTRACT substitute the full phrase for “&trademrk” to produce
The macro facility is one of the most powerful features of base the following footnote:
SAS. Macros and macro variables allow users to write more
flexible code, perform repetitive tasks, pass information Notice: ACME is a registered trademark of
between data or proc steps, conditionally execute SAS code, ACME, Inc. Other brand and product names are
and generate SAS statements based on what is in the data. trademarks of their respective companies.
Although beginning SAS users sometimes shy away from
macros, getting started with macros is actually easier than it Through text substitution the macro facility allows
looks. you to accomplish many functions. Just by using
macro variables you can:
This paper is designed to help the beginning user step into
macros. The paper starts with built-in and user-defined 1) Automatically insert the date and other session
macro variables, illustrating how users can add flexibility to information into your code,
their code with the simple addition of a few %LET statements 2) Write more flexible code, and
and ampersands. Passing information between data steps 3) Pass data between steps.
using CALL SYMPUT is also discussed. Next the paper
demonstrates how to actually create a macro. The paper
With macros we can add even more functions to the
breaks the process down into steps and incorporates
list:
debugging into the coding process to help beginners avoid the
frustrations of trying to figure out why a macro does not
work. For users who want to write more complex macros, the 4) Perform simple repetitions,
paper concludes with a few more advanced macro examples. 5) Conditionally execute program steps,
6) Perform highly repetitive tasks using loops and
INTRODUCTION nesting,
7) Generate data dependent code.
The macro facility is like a substitution machine: it replaces
one sequence of text with another (often larger) sequence of This paper will show you how to get started with
text. There are two forms of substitution: 1) macro variables macros and will demonstrate how each of the above
and 2) macros. For example, with a macro variable you could functions (except for data dependent code) can be
use a 10-letter abbreviation as a short-hand for a text phrase achieved. Although the basic concept behind the
of 250 characters. With macros you can create short-hands macro facility is simple text substitution, macro
for entire sections of code rather than just phrases. solutions can be quite complex. Getting started with
macros is quick, mastering them takes more time.
Sound simple? The basic idea behind macros is simple.
Creating and using macro variables is also simple. For
1. AUTOMATICALLY INSERTING THE DATE
example, if we frequently use a trademark notice in our
SAS provides “automatic” or pre-defined macro
footnotes and titles, we might want to create an abbreviation
variables. These variables contain information about
or short-hand for this notice. We can create a macro variable
the current SAS session.
called TRADEMRK and assign our notice to it with a %Let
statement:
To reference a macro variable you just type an
%Let trademrk = ACME is a registered ampersand immediately followed by the name of the
trademark of ACME, Inc. Other brand and macro variable. For example, SYSDATE is one of
product names are trademarks of their the most commonly used automatic macro variables.
respective companies.;
Wherever &SYSDATE appears in your code, the
macro facility will substitute in the date on which the
Now we can insert &trademrk (the ampersand identifies the current session began. (Note that the date SAS was
word as a macro variable) into a footnote or wherever else we invoked is not necessarily the current date.) Below
want it: is an example of how you can easily add the date into
your footnotes:
footnote1 “Notice: &trademrk”;
footnote “Processed on &SYSDATE”;
confusing at first. Just keep in mind that these
On the printout, this generates the following footnote: symbols are necessary to let the macro facility know
that this is macro-related code.
Processed on 30SEP00
Macro variables can be used throughout your
When you use a macro variable within a quote (as in the program, not just in quoted text. SYSLAST is a
above example), you must use double quotes: SAS treats macro variable that contains the two-level name of
macro variables enclosed in single quotes as regular text. the last data set that you created. At the start of your
Thus the following statement: SAS session it is empty. Each time you create a data
set, SYSLAST is updated to reflect the most recent
/* mistakenly using single quotes */ data set. We can insert &SYSLAST into our code to
footnote ‘Processed on &SYSDATE’; print out the first 10 observations from our most
recently created data set:
would produce the following footnote:
proc print data = &SYSLAST
(obs = 10);
Processed on &SYSDATE footnote “Obs from &SYSLAST”;
footnote2 "Printed on
In addition to SYSDATE, there are a number of other &SYSDATE..";
commonly used automatic macro variables: run;

VARIABLE DESCRIPTION EXAMPLE If the last data set we created is WORK.TEMP, the
SYSDATE date SAS session 30SEP00 SAS macro facility would substitute
started “WORK.TEMP” wherever “&SYSLAST” appears.
At run time, the facility will generate the equivalent
SYSDATE9 date in DATE9. 30SEP2000
of:
format
SYSDAY day session Saturday proc print data=WORK.TEMP (obs =
started 10);
SYSLAST last data set WORK.TEMP footnote “Obs from WORK.TEMP”;
created footnote2 "Printed on 30SEP00.";
run;
SYSSCP host system WIN
SYSTIME time session 10:09
started And produces the following footnotes:
SYSVER SAS Version 7.00
Observations from WORK.TEMP
SYSUSERID userid USER
Printed on 30SEP00.
Automatic variables are easy to spot in code because they
In footnote2 we needed to use a double-period after
begin with “&AF” and “&SYS”. To see the values currently
&SYSDATE: the first period is interpreted by the
assigned to all automatic macro variables, type the following
macro facility as the end of the macro name and the
line of code:
second period is treated as text. No period is
necessary after the other automatic variables in the
%put _automatic_;
above code because they are followed by a space,
double quote, or other punctuation that makes the
This will send a list of the current values (including empty end of the variable name clear.
values) to your log:

AUTOMATIC AFDSID 0 Automatic Macro Variables At-A-Glance


AUTOMATIC AFDSNAME Assigning:
AUTOMATIC AFLIB • No need to assign. These are assigned
... automatically by SAS.
• All automatic variables are global (except for
Note that “%Put” starts with a percent sign. The percent SYSPBUFF) so a value is in effect until the SAS
symbol indicates to the macro facility that what follows is a session ends or the value changes.
macro keyword (%IF, %DO, etc.) just as an ampersand Using:
indicates the start of a macro variable. The many percent • Insert into your code using:
signs and ampersands that are used in macro code can look
“&” + <macro variable name> book author
volume
• Use double quotes when quoting (“Today is
&SYSDATE”)
Seven Suns E. Ernest
• Use a single period to separate the macro variable name 887
from any text immediately following it World of the C. Chaudry
(“&SYSLAST.TEXT”) 550
• Use a double-period to produce a single period Planet XYZ J. Johnson
immediately after the macro variable (“&SYSDATE..”) 333
More SciFi W. Wu
Displaying values assigned (in your log): 200
Jumping Jupiter D. Diullo
• %put _automatic_; 179
The above code is not very flexible. When we want
2. WRITING MORE FLEXIBLE CODE to print out the same information for July, we would
In addition to using the macro variables assigned need to change “06” to “07” in 4 separate places.
automatically by SAS, you can create your own custom macro Instead of hard coding the month, we could instead
variables. The easiest way to create a user-defined macro assign the month to a macro variable and then refer
variable is with a %Let statement: to this macro variable throughout our code. This
would allow us to change the month by changing
%Let <macro var name> = <text string>; only one %Let statement.

Macro variables are an easy way to add flexibility to your Values that are likely to change and appear multiple
code by reducing “hard coding.” Typing specific information times in your code should generally be assigned with
into your program is “hard coding” it. In the example below, a %Let statement. Interest rates, selection criteria,
all the specifics such as the month, the type of books, and the file names, dates and other specifics that are likely to
number of top books are hard coded: change are good candidates for macro variables.

/* TOP BOOKS */ To make a program more flexible:


/* This program prints a list of top
5 Sci-Fi books for June */
1) Identify specific information that occurs multiple
/* Each observation is one book */ times in your code and is likely to change.
2) Replace each occurrence of this information with
/* book = name of the book */ a reference to a macro variable (i.e., &MONTH).
/* author = author of book */ 3) At the top of your code use a %Let statement to
/* type = type of book */ assign the specific information to the macro variable
/* volume = number of books sold*/
/* profits = profits from book */
name:

proc sort data = sales06 %Let month =06;


(where = (type = "Sci-Fi"))
out = sort06; Using the above steps we can make our “Top Books”
by descending volume; program more flexible. After adding macro
/* sorts from most to least books */
variables and changing the %Let statements to print
run;
the top 10 Fiction books rather than top 5 Science
proc print data = sort06 (obs=5) Fiction books, here is how it looks:
noobs;
/* noobs suppresses obs #s */; %let month = 06;
var book author volume; %let type = Fiction;
title1 "Top 5 Sci-Fi Books for 0600"; %let criteria=Volume;
title2 "Based on Volume"; %let topnum= 10;
run;
proc sort data = sales&month
(where = (type = "&type") )
The output from our code is:
out = sort&month;
by descending &criteria;
Top 5 Sci-Fi Books for 0600 run;
Based on Volume
proc print data = sort&month
(obs = &topnum) noobs; “TOP_SELLING_BOOK”, but we are also able to
var book author &criteria; trim the trailing blanks from the variable at the same
title1 "Top &topnum &type
time (using the TRIM function). Macro variable
Books for &month.00";
title2 "Based on &criteria"; names can exceed 8 characters. Re-issuing a “%put
run; _user_” statement reveals our two new macro
variables:
Since the macro facility will ignore leading blanks after the
GLOBAL MONTH 06
equals sign in a %Let statement, it does not matter whether GLOBAL TOPNUM 10
you start the text directly after the equal sign or put a space in GLOBAL TOP_AUTHOR J.K. Rowling
between. GLOBAL TYPE Fiction
GLOBAL CRITERIA Volume
GLOBAL TOP_SELLING_BOOK Harry Potter
We can list all of our user-defined macro variables in our Log and the Goblet of Fire
by typing:
We can now use these macro variables throughout
our program, including our proc print title:
%put _user_;
title1 "Our #1 seller was
For the above example, this will produce the following lines: <<&TOP_SELLING_BOOK>> by
&TOP_AUTHOR!";
GLOBAL MONTH 06
GLOBAL TOPNUM 10
GLOBAL TYPE Fiction Which yields the title:
GLOBAL CRITERIA Volume
Our #1 seller was <<Harry Potter and the Goblet
of Fire>> by J.K. Rowling!
3. PASSING DATA BETWEEN DATA STEPS
Call Symput provides another of creating a user-defined User-defined Macro Variables At-A-Glance
macro variable. The format for Call Symput is: Assigning:
• Assign using a:
Call Symput(<macro varname>, <expression>); %Let <macro var name > = <unquoted
text>;
Unlike %Let, Call Symput allows you to assign the value of a or
variable or other expression to a macro variable. This makes Call Symput(<macro varname>,
it possible to “share” information between different steps in a <expression>);
program. • With Call Symput you can assign an expression,
variable name, or quoted text to a macro
Returning to our book store example, suppose we wanted to variable.
add the name of the top selling book into our proc print title. • Macro variable names must start with a letter or
After the sort the top selling book will be the first book in the underbar but can also contain numbers. Macro
data set. But how do we pass this information to the proc variable names are NOT limited to 8 characters.
print? One solution is to create a macro variable containing • Values assigned to macro variables can be up to
the name of the top selling book (Top_Selling_Book) and its 32K characters.
author (Top_Author) using Call Symput: • Macros created using %Let and Call Symput
(outside of a macro execution) are by default
data _null_; global. These values are in effect until the end
/* A _null_ processes data without of the SAS session or until the user assigns a
creating a data set.*/ new value to the macro variable.
Set sort&month (obs = 1);
/* uses first obs only */
Call Symput('Top_Selling_Book', Using:
trim(book) ); • Same syntax as for automatic variables (i.e.,
Call Symput('Top_Author', &MYVAR).
trim(author) );
/* trim trailing blanks */ Displaying values:
run;
• %put _user_;

Not only are we able to assign the value of the character


variable “BOOK” to the macro variable
4. PERFORMING REPETITIVE TASKS BY WRITING Although this is more efficient when you call the
A MACRO same macro many times, it can make debugging
The program in Section 2 is fairly flexible. But if we wanted difficult. There are several global options which you
to repeat the task five times (one time for each of five types of can use to send more information to the log when
books), we would have to do a lot of cutting and pasting. We you call a macro:
could accomplish the task but our code would be awkward
and difficult to maintain. A better solution is to use macros OPTION DESCRIPTION
rather than just macro variables. Macros substitute whole MLOGIC traces the beginning of macro
sections of code and can be used to simplify repetitive tasks. execution and any parameter values
assigned
The format for defining a macro is: MPRINT displays the SAS statements
generated by macros
SYMBOLGEN writes a message for the resolution of
%MACRO <macro name>;
...
each macro variable
SAS code To see what is really happening when we submit
.... “%PRINT_IT”, we can first turn on all three
%MEND; options:

The format calling a macro is: options mlogic symbolgen mprint;


%PRINT_IT
%<macro name>
This produces the following messages in our Log:
(Note that we do not need a semicolon after a macro call.)
Below is an example of a simple macro called “PRINT_IT” MLOGIC(PRINT_IT): Ending execution.
which prints out the last data set created. 653 options mlogic symbolgen
mprint;
654 %PRINT_IT
%MACRO PRINT_IT; MLOGIC(PRINT_IT): Beginning execution.
SYMBOLGEN: Macro variable SYSLAST
proc print data = &SYSLAST; resolves to WORK.SORT06
title “Last Data: &syslast”; MPRINT(PRINT_IT): proc print data =
run; WORK.SORT06 ;
SYMBOLGEN: Macro variable SYSLAST
%MEND; resolves to WORK.SORT06
MPRINT(PRINT_IT): title "Last Data
Set Created: WORK.SORT06
Anywhere in our program where we type: ";
MPRINT(PRINT_IT): run;
%PRINT_IT NOTE: PROCEDURE PRINT used:
real time 0.71 seconds
the following code will be substituted:
Part of the power of macros stems from our ability to
proc print data = &SYSLAST; “parameterize” macros. Macro parameters are the
title “Last Data: &syslast”; macro variables associated with a macro. When we
run; call a macro we can “feed” it different parameters
each time. Macro parameters need to be defined
Looking at our Output after submitting the “%PRINT_IT” we when you define a macro. Below is an example of
will see a printout of the data set. how we could parameterize our %PRINT_IT. These
parameters allow us to specify the data set name and
However if we look at our Log, we will only see: the number of observations to print.

588 %PRINT_IT /* Positional Parameter Example */


%MACRO PRINT_IT(dataname, num_obs);
NOTE: PROCEDURE PRINT used:
real time 0.71 seconds proc print data =&dataname
(obs = &num_obs);
run;
By default the code generated by a macro is not shown in the
Log. Only NOTES and ERROR messages appear in the Log. %MEND;
Notice that in the first execution of the macro the
The example above uses Positional Parameters. Positional NUM_OBS had a value of 5 and in the second it had
Parameters match the values you specify to the names of the a value of 10. Also note that after the macro has
macro variables based on their position in the list: the first finished running, NUM_OBS and DATANAME no
value is assigned to the first variable, etc. After defining the longer exist. By default, macro parameters are in
revised macro, we could print out the first 5 observations of effect only during the execution of the macro.
WORK.SALES06 and the first 10 observations of
WORK.SORT06 using: Below are the suggested Steps for moving from a
program with macro variables to a full macro. The
%PRINT_IT(work.sales06, 5) Steps integrate debugging with the writing process.
%PRINT_IT(work.sort06, 10) This is to avoid one of the most frustrating aspects of
writing macros: trying to figure out what is wrong
Alternatively, we could have used Keyword Parameters to with your macro. By making sure your code is
define the macro parameters: working properly at each step, you can help reduce
frustrating bugs.
/* Keyword Parameter Example */
%MACRO PRINT_IT(dataname=, num_obs=10);

proc print data =&dataname


(obs = &num_obs); STEPS FROM MACRO VARIABLES TO
run; MACROS
1. Write your program and assign macro variables
%put Within the Macro;
using a %Let.
%put _user_ ;
2. Test run, debug and fix your code as necessary.
%MEND; 3. Enclose your code between a “%MACRO <macro
name>” and a “%MEND.”
%PRINT_IT(num_obs =5, 4. Convert your “%Let” statements to Keyword
dataname= work.sales06) Parameters and retain the values as defaults.
%PRINT_IT(dataname = work.sort06)
5. Turn on the debugging options:
%put Outside of the Macro; options mlogic symbolgen mprint;
%put _user_ ; 6. Test run your new macro without specifying any
parameters (i.e., the default parameters will be used):
With Keyword Parameters, values and macro names are %<macro name>()
explicitly linked using an equals sign to link the two (rather 7. Debug and fix your code as necessary. Use
than rely on the order of the values). Notice the “num_obs = “%put” to trace values if necessary.
10” in the DEFINITION of the macro. This assigns a default 8. Once the code is working, add any repetitions of
value of “10” to the macro variable “num_obs.” In our the macro call and more advanced macro statements
second %PRINT_IT call we did not specify a “num_obs =” so as needed.
the default value of 10 will be assigned. 9. Turn off the debugging options (to avoid
producing a massive log):
Our %put statements allow us to look at the macro options nomlogic nosymbolgen
assignments within the macro (while the macro was running) nomprint;
and outside of the macros. Our log shows: 10. Run your full program.

Within the Macro At the start of this section, we set out to print our top
PRINT_IT DATANAME work.sales06 book list for each of five different types of books.
PRINT_IT NUM_OBS 5
GLOBAL MONTH 06
Applying the steps above, we can create a TOPLIST
... macro and easily call it five times:
Within the Macro
PRINT_IT DATANAME work.sort06 %MACRO TOPLIST(month = 06,
PRINT_IT NUM_OBS 10 type = Fiction,
GLOBAL MONTH 06 criteria=Volume,
... topnum= 10);
Outside of the Macro
GLOBAL MONTH 06 proc sort data = sales&month
(where = (type = "&type") )
out = sort&month;
by descending &criteria;
run;
5. CONDITIONALLY EXECUTING DATA
proc print data = sort&month STEPS
(obs = &topnum) noobs; Macros allow you to conditionally execute entire
var book author &criteria;
data steps or sections of code. One way of
title1 "Top &topnum &type
Books for &month.00"; conditionally executing code within a macro is to use
title2 "Based on &criteria"; a %IF statement. Suppose that some of the time we
run; want to create the data set (i.e., create
%MEND; SORT06.SD2), but not print out the list of books.
We could add a parameter to our macro called
%TOPLIST (type = Fiction) “PRINTLIST” to indicate whether or not we want to
%TOPLIST (type = Sci-Fi) print. We would also need to enclose our proc print
%TOPLIST (type = Reference) within an %IF block. The format for %IF is:
%TOPLIST (type = Mystery)
%TOPLIST (type = Non-Fiction)
%IF <condition> %THEN %DO;
...
Clearly, this is a lot neater than if we had cut and pasted the SAS Code
program five times! Note that the “%TOPLIST” calls must ...
be placed in the program AFTER (but not necessarily %END;
immediately after) the definition of the macro (“%MACRO..
%MEND”). A macro must be compiled (submitted) before it
is called in your program.

We could also use the macro calls to specify different Adding %IF to our code:
numbers of “top” books to print depending on the type of
book: %MACRO TOPLIST(month = 06,
type = Fiction,
%TOPLIST (type = Fiction, topnum=25) criteria=Volume,
%TOPLIST (type = Sci-Fi, topnum = 3) topnum= 10,
printlist=Y);

Macros and Macro Parameters At-A-Glance proc sort data = sales&month


Defining Macros: (where = (type = "&type") )
• Begin with: out = sort&month;
%MACRO <macro name> (<parameter by descending &criteria;
definition>); run;
• End with:
%IF “PRINTLIST” = “Y” %THEN %DO;
%MEND;
proc print data = sort&month
(obs = &topnum) noobs;
Defining Macro Parameters: var book author &criteria;
• Specified when defining the macro. This can be done title1 "Top &topnum &type Books
with either Positional Parameters (variables separated by for &month.00";
title2 "Based on &criteria";
commas) or Keyword Parameters (variables separated by
run;
an equal sign and a comma). %END;
• Macro parameters are, by default, only in effect while the
macro is running. (Note that this rule also applies to %MEND;
macro variables created using “%Let” within a macro.)
The default parameter for PRINTLIST is “Y” (i.e.,
Using Macro Parameters: print the list). But we can easily turn off the printing
• Same syntax as for automatic variables and user-defined by calling the macro and giving “PRINTLIST”
macro variables (i.e., &DATANAME). another value:

To display values within the macro execution: %TOPLIST (type = Sci-Fi,


printlist=N)
• %put _user_;
• option symbolgen;
• option mlogic;
The result of the above call will be to create
SORT06.SD2 but not print anything. With
MLOGIC turned on, SAS will write a message to the Log month = 06,
indicating whether or not the %IF condition was true: type = Fiction,
criteria=Volume,
topnum= 10);
MLOGIC(TOPLIST): %IF condition "PRINTLIST" =
"Y" is FALSE
proc sort data=&saledata....
....
In this case it was false. SAS CODE
.....
%MEND;
6. PERFORMING HIGHLY REPETITIVE TASKS
WITH LOOPS AND NESTING
%MACRO PRINTSALES(first_store = ,
If you need to repeat a task under a dozen times, then
last_store= );
repeatedly calling a macro works fine. However, for
repeating a task 1000 times this will not work. %local i;
%DO i = &first_store %TO
Luckily, we can use loops and nesting of macros to perform &last_store;
highly repetitive tasks. Imagine for example that we wanted
%TOPLIST (type = Fiction,
to print out the sales figures for each of 500 stores across the
saledata= store&i)
country. The sales data for each store could be contained in a
%TOPLIST (type = Sci-Fi,
data set of the form: STORE<store number>. Also imagine saledata= store&i)
that while we currently have 500 stores, next month they may
add more stores. Below is the format for creating a DO loop %TOPLIST (type = Reference,
within a macro: saledata= store&i)
%TOPLIST (type = Mystery,
%local i; saledata= store&i)
/*creates a local macro var i*/ %TOPLIST(type=Non-Fiction,
%DO i = <start number> %TO <stop number>; saledata= store&i)
...
SAS Code %END;
... %MEND;
%END;
%PRINTSALES(first_store=01,
last_store=500);
Using this format we could print out the sales files for all 500
stores:
This code will print out 2,500 hundred reports on
%MACRO PRINTSALES(first_store = , book sales. It is hard how this could have been
last_store= ); accomplished without macros!
%local i;
%DO i = &first_store %TO
&last_store;
7. GENERATING DATA DEPENDENT CODE
Generating data dependent code is a powerful
proc print data = store&i; capability of the macro facility but well beyond the
run; scope of an introduction to macros. For examples of
how to generate data dependent code, please see the
%END; References.
%MEND;

%PRINTSALES(first_store=01, One caveat: in examples of how to generate data


last_store=500); dependent code, you will probably encounter double
ampersands. To understand what a double
If we wanted to print out five different reports (one for each ampersand is you need to understand a little bit
type of book), we could “nest” one macro inside of another. about how the macro facility works. On each pass,
For this example, we need to modify TOPLIST so that the the macro facility resolves one level of ampersands.
name of the sales data is a parameter (SALEDATA). Our Using double or even triple ampersands is a way of
PRINTSALES macro would now call our TOPLIST macro embedding one macro variable name in another. For
for each type of book: example:

%let i=3;
%MACRO TOPLIST(saledata=,
%let type1=Fiction;
%let type2=Sci-Fi;
%let type3=Mystery;

%put with one ampersand: &type&i;


%put with two ampersands: &&type&i;

Will generate the following messages to the Log:

WARNING: Apparent symbolic reference TYPE not


resolved.
with one ampersand: &type3
106 %put with two ampersands: &&type&i;
with two ampersands: Mystery

In the double ampersand case, “&&type&i” resolves to


“&type3” on the first pass and then “&type3” resolves to
“Mystery” on the second pass.

CONCLUSION
The macro facility is a text substitution mechanism. Through
macro variables and macros, the macro facility offers many
opportunities for flexible programming of complex and
repetitive tasks. Macro variables are easy to use. With macro
variables, even a beginning programmer can add flexibility to
his or her programs. Once macro variables have been
mastered, it is a short step to writing actual macros. Moving
from writing basic macros to writing complex macros,
however, takes more time.

REFERENCES

Carpenter, Art. Carpenter's Complete Guide to the SAS


Macro Language, Cary, NC, SAS Institute., 1998.

SAS Institute, Inc. SAS Macro Language: Reference,


Version 8, Cary, NC, SAS Institute., 1999.

CONTACT INFORMATION
Lisa Sanbonmatsu
Harvard University
Malcolm Wiener Center for Social Policy
79 JFK Street
Cambridge, MA 02138
Work Phone: 617-495-5131
Email: [email protected]

SAS is a registered trademark or trademark of SAS Institute


Inc. in the USA and other countries. ® indicates USA
registration.

Other brand and product names are registered trademarks or


trademarks of their respective companies.

You might also like