4
votes

I am writing a macro that at some point calls some proc SQL code. I want the user to be able to specify arbitrary proc sql options (e.g. inobs=100 could be one of the input arguments to my macro).

I am having a very hard time quoting an argument that has an equality '=' character.

One of the issues is that I should also check if the macro argument is empty or not, and if it is not empty, only then add the specified options to the sql statement.

Below is an example non-working test that does not work and throws the

ERROR: The keyword parameter INOBS was not defined with the macro.

I have read this (http://www2.sas.com/proceedings/sugi28/011-28.pdf) and other SUGI's and tried many possible ways to quote and call the macro.

If somebody could provide a working example of the below function it would be greatly appreciated.

options mprint mlogic;

data have;
    length x $8;
    input x;
    datalines;
one
two
three
;

proc sql inobs=2;
    create table sql_output as 
            select * 
            from have;
quit;


%macro pass_parameter_with_equal_sign(table=, sqlOptions=);
    proc sql 
%if "%left(%trim(&sqlOptions.))" ne "" %then %do;
    &sqlOptions.
%end;
    /* the semicolon to end the proc sql statement */
    ;
        create table macro_output as 
            select * 
            from have;
    quit;
%mend;

%pass_parameter_with_equal_sign(table=have, sqlOptions=%str(inobs=2))

title "SQL output:";
proc print data=sql_output; run;
title "Macro output:";
proc print data=macro_output; run;
3
As a side-note... There is no need to macro quote the equals sign. SAS is smart enough to know that the equals sign is simply part of the parameter's value. You would have to quote commas though as these are what delimits the macro parameters.Robert Penridge

3 Answers

4
votes

If you remove the %if condition as follows it should work:

%macro pass_parameter_with_equal_sign(table=, sqlOptions=);
    proc sql 
    &sqlOptions.
    /* the semicolon to end the proc sql statement */
    ;
        create table macro_output as 
            select * 
            from have;
    quit;
%mend;

The %if you have used is to check if &sqlOptions is not blank, this shouldn't matter if you use it as it is because its unconditional usage will give either:

proc sql inobs=2; /* in the case of &sqlOptions=inobs=2 */

or if there is no value supplied for &sqlOptions then you should see:

proc sql; /* i.e. no options specified */

So it should work with or without an argument.

3
votes

Amir's solution is probably correct for your particular use case. But to answer the more general question, we need to look to the seminal paper on macro parameter testing, Chang Chung's Is This Macro Parameter Blank?.

His example C8 is the right one for you here, though some of the others will also work.

%if %sysevalf(%superq(param)=,boolean) %then ... /* C8 */ 

For example:

%macro test_me(param=);

  %if %sysevalf(%superq(param)=,boolean) %then %put Empty;
  %else %put Not Empty;;
%mend test_me;

%test_me(param=);
%test_me(param=MyParam);
%test_me(param=param=5);

%SUPERQ is most useful here because it avoids resolving the macro parameter. Instead, it keeps it as a macro parameter value - fully unresolved - and allows you to work with it in that fashion; so you have no risk of that pesky equal sign bothering you.

His C4 (just using SUPERQ without SYSEVALF) also works in this case, although he explains a few situations where it may have difficulty.

1
votes

Ahh this was actually a tricky little problem you ran into. The issue was actually being caused by the calls to %trim() and %left().

Removing these results in code that works as intended (note I also removed the macro quoting around the parameter):

%macro pass_parameter_with_equal_sign(table=, sqlOptions=);
    proc sql 
    %if "&sqlOptions" ne "" %then %do;
        &sqlOptions
    %end;
    /* the semicolon to end the proc sql statement */
    ;
        create table macro_output as 
            select * 
            from &table;
    quit;
%mend;

%pass_parameter_with_equal_sign(table=sashelp.class, sqlOptions= inobs=2);

We can re-create the issue you were experiencing like so:

%put %trim(inobs=1);

Because the parameter was resolving to inobs=1, and %trim() doesn't have any named parameters, it was throwing a hissy fit. To correctly pass in a string that contains "inobs=1" we can do so like this:

%let param = inobs=1;
%put %trim(%str(&param));

Note: Amir's solution of removing the %if statement altogether is also the best way to design code like this. I'm just providing more details as to why you were having this issue.


Additional Explanation 1 - Why %left() and %trim are not needed

The top code snippet provides the same intended functionality as your original code that had the "%left(%trim(&sqlOptions.))". This is because beginning and ending whitespace is dropped from macro variables (including macro parameters) unless it is explicitly retained by using macro quoting. A simple example to show this is:

%let param =      lots      of     spaces        ;
%put ***&param***;

Gives:

***lots      of     spaces***

You can see that the internal whitespace is kept, but the left and right padding are gone. To keep whitespace, we can simply use the %str() function.

%let param = %str(     lots      of     spaces        );
%put ***&param***;

Gives:

***     lots      of     spaces        ***

Additional Explanation 2 - Working with macros containing whitespace

If you actually did have whitespace on a macro variable that you needed to remove because it was quoted, and you wanted to use %left() and %trim() to do so, then things get a little wacky. Our variable can be created like so:

%let param = %str(     inobs = 2        );

You can see we already have quoted the value with %str() in order to create it. This means we can now call one of the functions without having to quote it again:

%put %trim(&param);  * ALREADY QUOTED AT CREATION SO THIS WORKS FINE;

However, if we then try and feed the result into the %left() function we're back to the original issue:

%put %left(%trim(&param));  * OOPS. DOESNT WORK;

Now I'm guessing here but I believe this is most likely because the %trim() function removes any macro quoting prior to returning a result. Kind of like this:

%put %unquote(%trim(&param));

This can be circumvented by re-quoting the returned result using %str() again:

%put %left(%str(%trim(&param)));

... or wrapping the original parameter with a %nrstr():

%let param = %str(     inobs = 2        );
%put %left(%trim(%nrstr(&param)));

... or using %sysfunc() to call a datastep function:

%put %sysfunc(compress(&param));