Iterative %DO loops and %LOCAL %GLOBAL scope in SAS macros
Why iteration and scope matter in macros
The previous lesson introduced macro definitions, parameters, default values, and basic conditional logic with `%if`/`%then`
This lesson covers two additional topics that are essential for building practical and reliable macros
The first topic is iterative `%do` loops, which allow a macro to repeat a block of SAS code for a range of values or while a condition holds
The second topic is variable scope, which controls whether a macro variable is visible only inside the macro that created it or visible to the whole session
Understanding scope prevents accidental overwriting of macro variables that were set earlier in a program and helps keep macros self-contained
Create sample data
We create a small sales dataset and a study visits dataset to use across the examples in this lesson
The sales dataset spans three years and will be used to demonstrate how loops generate repeated code patterns
The visits dataset contains one row per subject per visit and will be used in scope examples
SAS Log
Confirm that `all_sales` has 6 rows covering years 2022 to 2024
Confirm that `study_visits` has 9 rows with three subjects each having three visits
Dataset View
Iterative %DO %TO %END loops
What is a %DO loop in a macro
A `%do` loop inside a macro tells SAS to repeat the code between `%do` and `%end` for each value of a loop index
The loop index is a macro variable that automatically increments from a start value to an end value
This is the macro equivalent of a DATA step `do i=1 to N` loop, but it generates SAS statements rather than processing data rows
The syntax is: `%do variable = start %to end;` followed by the code to repeat and then `%end;`
Use a %DO loop to create one dataset per year
In this example, we loop from 2022 to 2024
On each iteration, the macro variable `yr` holds the current year value
We use `&yr` inside a DATA step and a PROC PRINT to generate three separate dataset-and-report combinations from a single macro call
Notice that the entire DATA step and PROC PRINT are inside the loop, so they execute once per year
SAS Log
This single macro call produces three datasets named `sales_2022`, `sales_2023`, and `sales_2024`, and prints each one
Without the loop, you would need to write the DATA step and PROC PRINT three times separately
Inspect each dataset and confirm each contains only the two rows for the corresponding year
Dataset View
Use %BY to control the loop step size
By default, the loop index increments by 1 each iteration
You can change this using the optional `%by` clause after the end value
In this example, we count from 0 to 100 in steps of 25 and write each value to the log
SAS Log
Check the log and confirm you see five NOTE lines with values 0, 25, 50, 75, and 100
The `%by` clause is useful when you need to loop over a sequence that is not consecutive integers
%DO %WHILE and %DO %UNTIL loops
%DO %WHILE - repeat while a condition is true
A `%do %while` loop repeats as long as the condition in parentheses evaluates to true
The condition is checked before each iteration, so if it is false at the start, the body never runs
This is useful when the number of iterations is not known in advance
Always ensure the loop variable changes inside the loop body to avoid an infinite loop
SAS Log
The log should show four NOTE lines with counter values 1, 2, 3, and 4
`%eval` is needed here because macro variables are text by default and arithmetic requires explicit evaluation
%DO %UNTIL - repeat until a condition becomes true
A `%do %until` loop runs the body first and then checks the condition
This guarantees the body runs at least once, which is different from `%do %while`
The loop stops when the condition becomes true rather than while it stays true
SAS Log
The log output is the same as the `%do %while` example here because both loops count from 1 to 4
The key difference is the timing of the condition check: `%while` checks before the body, `%until` checks after
Macro variable scope - %LOCAL and %GLOBAL
What is scope in SAS macros
Every macro variable lives in a symbol table that determines where it can be seen and used
The global symbol table holds variables that are visible everywhere in the SAS session
Each macro execution also has its own local symbol table that holds variables visible only inside that macro
When a macro variable is referenced, SAS searches the local table first, then works outward to the global table
If you create a variable inside a macro without declaring it as `%local`, it defaults to the global table, which can cause unexpected side effects in complex programs
Default scope behaviour - a variable leaks to global
In this example, we create a macro that sets a variable `report_year` without any scope declaration
After the macro runs, we check whether `report_year` is still available outside the macro
Because no `%local` declaration was made, the variable was written to the global symbol table and persists after the macro ends
SAS Log
Both `%put` statements should resolve to 2024, confirming the variable survived outside the macro
This is usually undesirable because the macro is affecting the session's global namespace
Use %LOCAL to keep a variable private to the macro
Declaring a variable with `%local` at the top of a macro creates it in the local symbol table
Once the macro finishes executing, the local variable is discarded and is no longer accessible
This is the recommended practice for loop indices, intermediate values, and any variable that is only needed inside the macro
SAS Log
The first `%put` inside the macro resolves to 2024
The second `%put` outside the macro produces a WARNING because `report_year` no longer exists in the global table
This is the correct behaviour for a well-scoped macro
Always declare loop variables and working variables as `%local` to avoid polluting the global symbol table
Use %GLOBAL to explicitly create a session-wide variable
Sometimes a macro is deliberately designed to produce a result that is accessible after the macro ends
In that case, declare the variable with `%global` before assigning it a value
This makes the intent clear and avoids relying on accidental global leakage
SAS Log
`max_year` is declared global, so it survives after the macro ends and can be used in subsequent code
Both `%put` statements should print 2024, which is the maximum year in `all_sales`
Using `%global` makes it immediately obvious to anyone reading the macro that producing this variable is part of the macro's purpose
Combining a loop with local scope - a complete example
Best practice is to declare all working variables as `%local` at the top of the macro
This includes loop index variables and any intermediate macro variables created during processing
The macro below generates a dataset per visit for the study_visits data using a loop, with all intermediate variables kept local
SAS Log
`i`, `visit_list`, and `visit_name` are all declared `%local`, so they do not affect the global symbol table
`%scan` is used here as a macro function to extract the nth word from the visit list string — it will be covered in detail in the next lesson
Three datasets are created: `visits_BASELINE`, `visits_WEEK4`, and `visits_WEEK8`
Confirm in the log that three NOTE lines appear, and inspect each dataset to verify its contents
Dataset View
Key points to remember
Use `%do variable = start %to end;` to loop over a numeric range — the index is a macro variable
Add `%by n` to control the step size when you do not want to increment by 1 each time
`%do %while(condition)` checks the condition before each iteration and stops when it becomes false
`%do %until(condition)` checks the condition after each iteration and stops when it becomes true
Macro variables created inside a macro without a scope declaration go into the global table — this is rarely what you want
Use `%local` for all working variables inside a macro to keep macros self-contained and avoid side effects
Use `%global` only when the macro is deliberately designed to produce a result that should persist after the macro ends
Always declare `%local` variables at the very top of the macro, before any `%let` statements