The correct code is as follows. But I used almost an hour to figure it out and still have questions about it. (ps: I made these mistakes while doing the practice questions on page 285 in Macro1: essentials course notes pdf, that was a complex question.)
1) The correct code and results
%macro ctrylist(ctry1)/minoperator;
%let list1=AT AU CA CH;
%put &list1;
%if &ctry1 in &list1 %then %do;
%put &ctry1;
%end;
%else %do;
%put ERROR: &ctry1 is an invalid country code.;
%put ERROR: Valid country codes include &list1.;
%end;
%mend ctrylist;
%ctrylist(AU);
%ctrylist(zz);
2) How I used almost an hour to make mistakes and correct it. At first I wrote something like this (I did not know what's wrong with my mind then and forgot to write the semicolon ; behind the two %put statements in the %else %do statement, also when debugging I kept ignore this obvious mistake and costed me a lot of time to figure out the problem):
%macro ctrylist(ctry1);
%let list1=AT AU CA CH;
%put &list1;
%if &ctry1 in &list1 %then %do;
%put &ctry1;
%end;
%else %do;
%put ERROR: &ctry1 is an invalid country code.
%put ERROR: Valid country codes include &list1.
%end;
%mend ctrylist;
%ctrylist(AU);
%ctrylist(zz);
And I got error message like this:
I am not good at using error message to help debug, also did not pay attention to where did the error occur. The possible mistakes came into my mind were: 1) maybe the %if &ctry1 in &list1 statement has problem, maybe I should try a &list with commas that separate the values, and 2) maybe the first %put in the first %if %then statement is not necessary. Then I tried something like this:
%macro ctrylist2(ctry2);
%let list2='AT','AU','CA','CH';
%if &ctry2 in (&list2) %then %do;
&ctry2;
%end;
%else %do;
%put ERROR: &ctry2 is an invalid country code.
%put ERROR: Valid country codes include &list2.
%end;
%mend ctrylist2;
%ctrylist(AU);
%ctrylist(zz);
And I got the same error message:
Then I did not know what to do. The only thing came into my mind was, maybe an option minoperator should be added (because in that section in the material there was content of minoperator), then I tried something like this:
%macro ctrylist2(ctry2)/minoperator;
%let list2='AT','AU','CA','CH';
%if &ctry2 in (&list2) %then %do;
%put &ctry2;
%end;
%else %do;
%put ERROR: &ctry2 is an invalid country code.
%put ERROR: Valid country codes include &list2.
%end;
%mend ctrylist2;
%ctrylist(AU);
%ctrylist(zz);
And I got the exact same error message. I did not know why I kept ignore the obvious mistake that there was no semicolon ; after the two %put statements, I even did not put my eyesight at the two statements, like I kept ignore an obvious, major mistake intentionally. Then I think I cannot figure it out and was going to post the problem into this community. But right before I post the problem my attention and eyesight somehow switched to and see the mistake: I did not write semicolon ; after the two statements. And I corrected it.
3) What I learnt from the experience: a) do not in a hurry and too eager to find out what is wrong, just look into every details of the code, b)look at the error message, especially where does the error message occur, c) when writing complex macros, keep relax and keep attention focused and enjoy, do not rush. d) When writing macro, it is easy for me to forget the semicolon, or sometimes added unnecessary semicolon or put semicolon at wrong place, I made this mistake twice before, the scenario is like(I forget another example which use a %if %then and where statement in a data step, which has the similar usage of the semicolon, could anyone come up with a similar example, many thanks) this, for this incorrect code I should put the semicolon after the &&b&i right before run; statement and after the %end; statement:
data a;
set %do i=1 to &sqlobs;
&&b&i;
%end;
run;
4) I still have questions about the codes:
a) do %if &ctry1 in &list1 and %if &ctry2 in (&list2) work the same way? I mean the syntax of the %if in statement, the first one does not need the brackets and the second one does need the brackets, isn't it?
%let list1=AT AU CA CH;
%put &list1;
%if &ctry1 in &list1 %then %do;
%put &ctry1;
%end;
%let list2='AT','AU','CA','CH';
%if &ctry2 in (&list2) %then %do;
%put &ctry2;
%end;
b) what does the minoperator options do? why do I need it after the macro name? Without it I got a similar error message (only without the error message about %put).
%macro ctrylist2(ctry2);
%let list2='AT','AU','CA','CH';
%if &ctry2 in (&list2) %then %do;
%put &ctry2;
%end;
%else %do;
%put ERROR: &ctry2 is an invalid country code.;
%put ERROR: Valid country codes include &list2.;
%end;
%mend ctrylist2;
%ctrylist(AU);
%ctrylist(zz);
In general, you do not use quotes to surround text in the macro variable value. In general, quotes here are problematic and can make the logic fail.
%let list=AU CH AT UR;
is the proper way to do this. If the macro is fed an argument which is AU, then this matches the first "word" of &LIST; in other words, AU (which is the argument of the macro) is an exact match to AU (the first "word" in &LIST).
If you use quotes
%let list='AU' 'CH' 'AT' 'UR';
and the macro is fed an argument such as AU without the quotes, then there is no match. Why? Because the text string AU does not match the text in &LIST which is 'AU' in quotes.
AU (a 2 character text string) does not match 'AU' (a four character text string).
So leave the quotes out of the macro variable value in almost all situations.
I forgot to say with this code (if I use %if &ctry1 in &list1 statement, with blank as the delimiter in the &list), I got different error message when I do not use minoperator option, does this mean whenever I use such a &list in my macro I have to add the minoperator option?
%macro ctrylist(ctry1);
%let list1=AT AU CA CH;
%put &list1;
%if &ctry1 in &list1 %then %do;
%put &ctry1;
%end;
%else %do;
%put ERROR: &ctry1 is an invalid country code.;
%put ERROR: Valid country codes include &list1.;
%end;
%mend ctrylist;
%ctrylist(AU);
%ctrylist(zz);
A minor issue, however
%macro ctrylist2(ctry2);
%let list2='AT','AU','CA','CH';
it is unnecessary and potentially harmful to the proper operation of the code to put quotes around the country abbreviations. The commas are also unnecessary. I doubt your %ctrylist2 macro will work with the quotes there.
P.S.: Better to include the log as text in the window that appears when you click on the </> icon
Hi Paige thanks a lot for reply! It's not a minor issue for me, it is where I make mistakes, thanks a lot for telling me this! The code in my previous thread was a little messed up because while debugging, I messed up the three macros %ctrylist, %ctrylist1, %ctrylist2 when test them. Today I tried both (with commas and quotes and without), the code and results are as follows:
%macro ctrylist2(ctry2)/minoperator;
%let list2='AT','AU','CA','CH';
%if &ctry2 in (&list2) %then %do;
%put &ctry2;
%end;
%else %do;
%put ERROR: &ctry2 is an invalid country code.;
%put ERROR: Valid country codes include &list2.;
%end;
%mend ctrylist2;
%ctrylist2(AU);
%ctrylist2(zz);
69 %macro ctrylist2(ctry2)/minoperator; 70 %let list2='AT','AU','CA','CH'; 71 %if &ctry2 in (&list2) %then %do; 72 %put &ctry2; 73 %end; 74 %else %do; 75 %put ERROR: &ctry2 is an invalid country code.; 76 %put ERROR: Valid country codes include &list2.; 77 %end; 78 %mend ctrylist2; 79 %ctrylist2(AU); ERROR: AU is an invalid country code. ERROR: Valid country codes include 'AT','AU','CA','CH' 80 %ctrylist2(zz); ERROR: zz is an invalid country code. ERROR: Valid country codes include 'AT','AU','CA','CH'
%macro ctrylist1(ctry1)/minoperator;
%let list1=AT AU CA CH;
%put &list1;
%if &ctry1 in &list1 %then %do;
%put &ctry1;
%end;
%else %do;
%put ERROR: &ctry1 is an invalid country code.;
%put ERROR: Valid country codes include &list1..;
%end;
%mend ctrylist1;
%ctrylist1(AU);
%ctrylist1(zz);
69 %macro ctrylist1(ctry1)/minoperator; 70 %let list1=AT AU CA CH; 71 %put &list1; 72 %if &ctry1 in &list1 %then %do; 73 %put &ctry1; 74 %end; 75 %else %do; 76 %put ERROR: &ctry1 is an invalid country code.; 77 %put ERROR: Valid country codes include &list1..; 78 %end; 79 %mend ctrylist1; 80 %ctrylist1(AU); AT AU CA CH AU 81 %ctrylist1(zz); AT AU CA CH ERROR: zz is an invalid country code. ERROR: Valid country codes include AT AU CA CH.
You are right about that the one with commas and quotes actually did not work. So this answers my question of the correct syntax of creating a &list to be used in a macro in a %if %then statement, that is, do not include any commas and quotes in the &list (i.e, use blank as delimiter among the values), and when resolve it in a %if %then statement, write it this way (do not need brackets here): %if &name in &list %then %do;. @PaigeMiller , am I right about this? Thank you!
In general, you do not use quotes to surround text in the macro variable value. In general, quotes here are problematic and can make the logic fail.
%let list=AU CH AT UR;
is the proper way to do this. If the macro is fed an argument which is AU, then this matches the first "word" of &LIST; in other words, AU (which is the argument of the macro) is an exact match to AU (the first "word" in &LIST).
If you use quotes
%let list='AU' 'CH' 'AT' 'UR';
and the macro is fed an argument such as AU without the quotes, then there is no match. Why? Because the text string AU does not match the text in &LIST which is 'AU' in quotes.
AU (a 2 character text string) does not match 'AU' (a four character text string).
So leave the quotes out of the macro variable value in almost all situations.
You need MINOPERATOR in order use IN with the macro language. Otherwise, the macro processor does not know what to do with it.
And in this case, you are calling a macro %ctrylist right after storing the macro %ctrylist2, so that will lead to an error:
%macro ctrylist2(ctry2);
%let list2='AT','AU','CA','CH';
%if &ctry2 in (&list2) %then %do;
&ctry2;
%end;
%else %do;
%put ERROR: &ctry2 is an invalid country code.
%put ERROR: Valid country codes include &list2.
%end;
%mend ctrylist2;
%ctrylist(AU);
%ctrylist(zz);
Read more about MINOPERATOR here:
https://support.sas.com/documentation/cdl/en/mcrolref/61885/HTML/default/viewer.htm#a003092012.htm
Hi @svh thanks a lot for reply! Through the link you provide I have downloaded the material about macro for further learning. However, for the explanations about minoperator, I still do not understand. In the materials I can find so far, the descriptions are similar:
1) descriptions in SAS 9.4 guide (built in documentation): "specifies that the macro processor recognizes and evaluates the mnemonic IN and the special character # as logical operators when evaluating arithmetic or logical expressions during the execution of the macro".
2) descriptions in SAS Macro1: essentials course notes: "A null value cannot be provided in the list of values after the macro IN operator. Null values must be processed first with a separate %if condition. to enable the macro IN operator, add the MINOPERATOR options".
3) descriptions in SAS(R) 9.2 Macro Language Reference (the material on the link you provide): "causes the macro processor to recognize the evaluate both the mnemonic operator In or the special character # as a logical operator in expressions".
So far my understanding is like this: whenever I use a macro &list or do some arithmetic calculation inside a macro, if my macro does not run through and other syntax is correct, I need to add this minoperator to see if it solves problem. @svh am I right about this, thanks a lot!
And you are right about I was "calling a macro %ctrylist right after storing the macro %ctrylist2", I messed up the two macros myself while debugging and test them😅.
First point. MINOPERATOR and MINDELIMITER are the system options to control WHETHER you can use IN as an operator in macro code and if you do WHAT character is used to delimit the items in the list.
When writing a macro you would normally want to use the MINOPERATOR and MINDELIMITER options of the %MACRO statement to enable the IN operator and set the delimiter your macro expects. Otherwise the macro will behave differently depending on the setting of the system options with those same names.
Your point number 2 is explaining about what constitutes a valid use of the IN operator. Basically it is just saying you cannot test if something is in nothing and you cannot test if nothing is in a list.
1 options minoperator mindelimiter=' '; 2 %put %eval(x in a b c); 0 3 %put %eval(a in a b c); 1 4 %put %eval(x in ); ERROR: Operand missing for IN operator in argument to %EVAL function. 5 %put %eval( in a b c); ERROR: Operand missing for IN operator in argument to %EVAL function.
Just because you have list of values does not imply that you NEED to use the IN operator. Just like with other things there are other ways to do things. You could use FINDW() function for example. (Note it is hard to use %SYSFUNC() to call the FINDW() function and use only space as the delimiter.)
6 %put %eval(0<%sysfunc(findw(a b c,x,/,st))); 0 7 %put %eval(0<%sysfunc(findw(a b c,a,/,st))); 1
Note that the FINDW() function does not mind empty arguments. The result is FALSE.
8 %put %eval(0<%sysfunc(findw(,x,|,st))); 0 9 %put %eval(0<%sysfunc(findw(a b c,,|,st))); 0
Hi @Tom thanks a lot for the explanations in your thread, I'll take more time to practice those techniques. I have tried the usage of minoperator together with mindelimiter for a &list of values separated by commas. The code and results are as follows, this is also a handy and simple usage I think.
%macro customerlist(ctry)/minoperator mindelimiter=',';
proc sql noprint;
select distinct country
into :ctrylist separated by ","
from mc1.customers;
quit;
%if %superq(ctry) in %superq(ctrylist) %then %do;
title "customers from &ctry";
proc sgplot data=mc1.customers;
vbar group;
yaxis grid;
where country="&ctry";
run;
%end;
%else %do;
%put ERROR: &ctry is an invalid country code.;
%put ERROR: Valid country codes include &ctrylist..;
%end;
%mend customerlist;
options symbolgen mprint mlogic;
%customerlist(AU);
%customerlist(zz);
options nosymbolgen nomprint nomlogic;
69 %macro customerlist(ctry)/minoperator mindelimiter=','; 70 proc sql noprint; 71 select distinct country 72 into :ctrylist separated by "," 73 from mc1.customers; 74 quit; 75 %if %superq(ctry) in %superq(ctrylist) %then %do; 76 title "customers from &ctry"; 77 proc sgplot data=mc1.customers; 78 vbar group; 79 yaxis grid; 80 where country="&ctry"; 81 run; 82 %end; 83 %else %do; 84 %put ERROR: &ctry is an invalid country code.; 85 %put ERROR: Valid country codes include &ctrylist..; 86 %end; 87 %mend customerlist; 88 options symbolgen mprint mlogic; 89 %customerlist(AU); MLOGIC(CUSTOMERLIST): Beginning execution. MLOGIC(CUSTOMERLIST): Parameter CTRY has value AU MPRINT(CUSTOMERLIST): proc sql noprint; MPRINT(CUSTOMERLIST): select distinct country into :ctrylist separated by "," from mc1.customers; MPRINT(CUSTOMERLIST): quit;
MLOGIC(CUSTOMERLIST): %IF condition %superq(ctry) in %superq(ctrylist) is TRUE SYMBOLGEN: Macro variable CTRY resolves to AU MPRINT(CUSTOMERLIST): title "customers from AU"; MPRINT(CUSTOMERLIST): proc sgplot data=mc1.customers; MPRINT(CUSTOMERLIST): vbar group; MPRINT(CUSTOMERLIST): yaxis grid; SYMBOLGEN: Macro variable CTRY resolves to AU MPRINT(CUSTOMERLIST): where country="AU"; MPRINT(CUSTOMERLIST): run; NOTE: PROCEDURE SGPLOT used (Total process time):
NOTE: There were 90 observations read from the data set MC1.CUSTOMERS. WHERE country='AU'; MLOGIC(CUSTOMERLIST): Ending execution. 90 %customerlist(zz); MLOGIC(CUSTOMERLIST): Beginning execution. MLOGIC(CUSTOMERLIST): Parameter CTRY has value zz MPRINT(CUSTOMERLIST): proc sql noprint; MPRINT(CUSTOMERLIST): select distinct country into :ctrylist separated by "," from mc1.customers; MPRINT(CUSTOMERLIST): quit;
MLOGIC(CUSTOMERLIST): %IF condition %superq(ctry) in %superq(ctrylist) is FALSE MLOGIC(CUSTOMERLIST): %PUT ERROR: &ctry is an invalid country code. SYMBOLGEN: Macro variable CTRY resolves to zz ERROR: zz is an invalid country code. MLOGIC(CUSTOMERLIST): %PUT ERROR: Valid country codes include &ctrylist.. SYMBOLGEN: Macro variable CTRYLIST resolves to AT,AU,BE,CA,CH,CI,DE,DK,EG,ES,FI,FR,GB,GR,HR,IE,IL,IN,IT,LT,LU,MZ,NL,NO,NZ,PL,PT,SE,SI,TR,
US,YU,ZA ERROR: Valid country codes include AT,AU,BE,CA,CH,CI,DE,DK,EG,ES,FI,FR,GB,GR,HR,IE,IL,IN,IT,LT,LU,MZ,NL,NO,NZ,PL,PT,SE,SI,TR,US,YU,ZA. MLOGIC(CUSTOMERLIST): Ending execution. 91 options nosymbolgen nomprint nomlogic;
A word of warning. Using COMMA as the delimiter in macro variables will make your code harder to write and maintain. That is because SAS uses comma already in a lot of places. For example as the delimiter between arguments to a function call or between parameter values in a macro call.
1 %macro x_in_y(x,y) / minoperator mindelimiter=','; 2 %if &x in &y %then %put &=x is in &=y; 3 %else %put &=x is NOT in &=y; 4 %mend x_in_y; 5 6 %x_in_y(AB,AB,CD); ERROR: More positional parameters found than defined. 7
It is best to use some other character instead. Spaces are the easiest to type (and read) but might be impractical if the values in the list might contain spaces. In that case use some other character like | that will not appear in the items in the list.
8 %macro x_in_y(x,y) / minoperator mindelimiter='|'; 9 %if &x in &y %then %put &=x is in &=y; 10 %else %put &=x is NOT in &=y; 11 %mend x_in_y; 12 13 %x_in_y(AB,AB|CD); X=AB is in Y=AB|CD
Also your use of %INDEX() will risk false matches when the value entered forms part of one of the words in the list. It might be better to use %SYSFUNC() to call the SAS function INDEXW() or FINDW() instead to prevent matches on parts of a value. See the earlier post I did in this thread on that topic.
You define a macro ctrylist2, but try to invoke ctrylist (no 2).
Hi @Kurt_Bremser thanks a lot for reply! You are right😃, I made several mistakes(also without knowing what were the mistakes) when writing macro ctrylist, and then I wrote ctrylist1 and ctrylist2 to help debug and find out what's wrong, and then I messed up the three macros when test them 😃.
@dxiao2017 wrote:
The correct code is as follows. But I used almost an hour to figure it out and still have questions about it. (ps: I made these mistakes while doing the practice questions on page 285 in Macro1: essentials course notes pdf, that was a complex question.)
I would say that stating "correct code" without any description of what the code is supposed to do does not help at all in why "debugging" might be difficult.
I would also add that quite often the biggest issue is unfamiliarity with the basic code needed to perform something. We get a fair number of questions on this forum asking to debug macro code only to find that the poster never had basic code working that would accomplish the desired task(s). The macro processor generates code. If you do not know what the code to generate should look like then macros are extremely difficult to program.
And a fair number of things asked about macros can be accomplished with no macro programming at all. Many of the "loop" type questions about macros can be solved with By group processing for example.
Hi @svh thanks a lot for telling this! I have not learnt much about CALL EXECUTE yet, but from your comments I can see that when I am thinking write a macro to solve the problem, maybe I can consider use some CALL EXECUTE first. I will learn to use CALL EXECUTE more later on!
Catch the best of SAS Innovate 2025 — anytime, anywhere. Stream powerful keynotes, real-world demos, and game-changing insights from the world’s leading data and AI minds.
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.
Ready to level-up your skills? Choose your own adventure.