How does SAS macro quoting interact with format literals? - sas

Executing locally in a clean session:
%let x = %str(put(age, best.));
proc sql;
select &x from sashelp.class;
quit;
This generates the following error:
1 put(age, best.)
----
22
----
76
ERROR 22-322: Syntax error, expecting one of the following: a format name, ?.
ERROR 76-322: Syntax error, statement will be ignored.
But this "manually-resolved" version runs without notes, warnings or errors:
proc sql;
select put(age, best.) from sashelp.class;
quit;
Can somebody explain exactly what %str() is doing in this program that causes an issue at execution time? Apologies for the vague question, but I am unsure what the relevant interactions are; I cannot replicate using equivalent data-step syntax so perhaps proc SQL peculiarities are involved?

The %str() function masks a character string during macro compilation. Remove the %str() function in the let statement or add an %unquote() function in the sql select to have if resolve correctly.

Answered at this question on runsubmit.com:
I'm going to mark this answer as
correct because it led me to this page
of documentation:
http://support.sas.com/documentation/cdl/en/mcrolref/61885/HTML/default/viewer.htm#tw3514-unquote.htm
- "In rare cases, masking text with a macro quoting function changes the way
the word scanner tokenizes the text
... The word scanner does not use it
as the boundary of a literal token in
the input stack". Sounds like a bug,
frankly, but if the tokenizer
algorithm is as ancient and hairy as I
imagine, I'd spin it as a quirk too!

Can you use a format statement instead? For example, this works just fine.
%let x = %str( age format=best.);
proc sql;
select &x. from sashelp.class;
quit;

For some reason SAS doesn't like the "best." format.
i.e. when I try this, your code works
%let x = %str(put(age, 8.));
????

If you add this to your code
%put _user_ ;
you will see how &x is quoted by %str, in the log. That is why the proc sql code doesn't work. Using %Unquote in the select portion of the proc sql statement will allow the code to run.
http://www2.sas.com/proceedings/forum2007/152-2007.pdf

Related

Why am I getting SAS ERROR 22-322 syntax error [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
My codes are as below, the problem comes from codes after the note /now you can apply your logic to the dataset/, the problem in the log is Testwyf.. and ERROR 22-322: syntax error, I don't know why there are two dots, and I want to make it like libname.memname, but it just doesn't work.
Hoping someone could help me solve this problem.
input dataset Testwyf.varlst:
Error update:
Error:
MPRINT(LOOPOVERDATASETS): data Testwyf." ."N(drop=TradingTime);
NOTE: Line generated by the macro variable "INDSN".
1 .." ."N
-
22
200
enter image description here
ERROR 22-322: Syntax error, expecting one of the following: a name, a quoted string, ;, CUROBS, END, INDSNAME, KEY, KEYRESET,
KEYS, NOBS, OPEN, POINT, DATA, LAST, NULL.
ERROR 200-322: The symbol is not recognized and will be ignored.
%macro loopOverDatasets();
/*imho good practice to declare macro variables of a macro locally*/
%local datasetCount iter inLibref inMember outLibref;
/*get number of datasets*/
proc sql noprint;
select count(*)
into :datasetCount
from Testwyf.Varlst;
quit;
/*initiate loop*/
%let iter=1;
%do %while (&iter.<= &datasetCount.);
/*get libref and dataset name for dataset you will work on during this iteration*/
data _NULL_;
set Testwyf.Varlst (firstobs=&iter. obs=&iter.); *only read 1 record;
*write the libname and dataset name to the macro variables;
call symputx("inLibref",strip(libname));
call symputx("inMember",nliteral(memname));
call symputx("outLibref","Testwyf");
*NOTE: i am always mortified by the chance of trailing blanks torpedoing my code, hence the strip function;
run;
/*now you can apply your logic to the dataset*/
%let indsn = &inLibref..&inMember.;
%let outdsn = &outLibref..&inMember.;
data &outdsn.(drop=TradingTime);
set &indsn.(keep=Symbol ShortName TradingTime LastPrice OpenPrice);
Date=put(TradingTime,DATETIME15.);
run;
/*** ANY OTHER PROCS/DATA STEPS ***/
/*just remember to use &inLibref..&inMember. to refer to the current dataset*/
/*increment the iterator of the loop*/
%let iter=%eval(&iter.+1);
%end;
%mend;
/*call the macro*/
%loopOverDatasets()
Your updated information shows that you have observations in your input dataset Testwyf.Varlst with empty values of LIBNAME and MEMBER. Also having a period in the value of MEMBER is probably indication that it is either a numeric variable (or does not even exist in that dataset) or was generated from a numeric variable that had a missing value. So fix your input dataset.
Sometimes the macro processor interferes with SAS's ability to parse your code into tokens for evaluation and a single token that it being built with macro code is seen as two tokens instead of one. One simple solution is to build the token into another macro variable and then use that macro variable to generate the code.
In your case make macro variables to hold the full dataset names and then use those to generate the SAS code.
%let indsn = &inLibref..&inMember.;
%let outdsn = &outLibref..&inMember.;
data &outdsn.(drop=TradingTime);
set &indsn.(keep=Symbol ShortName TradingTime LastPrice OpenPrice);
Also protect yourself from non-standard member names.
call symputx("inMember",nliteral(memname));
I think Tom's generally right for finding the problem with this macro, so I'll suggest a totally different approach. %do loops are hard to troubleshoot, because the macro language isn't as good at logging errors.
Instead, my preferred approach is to do this as separate units.
Write a macro that does what you want done, once.
Write code that calls that macro once for each row in your dataset.
(1) is easy to do, hopefully. It's just the last part of your code.
(2) has a lot of different approaches, all of which are fine, depending on some details. If you're doing a reasonably small number of these, then the best approach in my opinion is proc sql select into, with the caveat that it has two majors issues: if there is a call execute up the line of it, you might not have the results you expect; and there is a 64000 character limit to the text of the select into, so if you have a lot of observations (or a smaller lot of very long calls) it can run into trouble. But if you're running this for a few hundred observations, you're probably cool doing it this way.
Here's one example of how to do this. Feel free to read my paper Writing Code With Your Data for a more detailed take on this.
*First, set up example dataset;
data change;
length var $32 expr $1024;
dataset='sashelp.class';
outset='class';
var='bmi';
expr='703*weight/(height**2)';
output;
dataset='sashelp.cars';
outset='cars';
var='mpg_avg';
expr='mean(mpg_city,mpg_highway)';
output;
run;
*Here, define the macro that will be run on each row of the dataset;
%macro change_ds(dataset=, outset=, var=, expr=);
data &outset;
set &dataset;
&var. = &expr. ;
run;
%mend change_ds;
*Now, use PROC SQL to select the macro calls into a macro variable;
*I leave off NOPRINT because I like to see the list in the results, but you can add it for production if you want;
proc sql;
select cats('%change_ds(dataset=',dataset,',outset=',outset,',var=',var,',expr=%nrstr(',expr,'))')
into :runlist separated by ' '
from change
where not missing(dataset) and not missing(outset) and not missing(var) and not missing(expr);
quit;
*Now, run it. Add the symbolgen/mprint options if it is helpful for debugging;
options symbolgen mprint;
&runlist.;
*Note, the semicolon above is not needed - I always include it to fix the syntax highlighting, but make sure you know it is not useful;

Getting an error whiile using %Dropmiss Macro in SAS

I am trying to use the %Dropmiss Macro in sas, which I found from an official PDF https://support.sas.com/resources/papers/proceedings10/048-2010.pdf
however, when I try to use it, I always get the same error:
ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was:
"%qtrim(&&CHARMAX&I)" eq ""
Being a newbie in Macros, I have tried to find a solution everywhere , but nothing so far. What is possibly the problem? What should I do or check in my data to fix the problem.
Thank you,
Not sure about your macro but you don't need a macro. That paper is a decade old so here's a more modern approach.
ods select none;
ods output nlevels=temp;
proc freq data=have nlevels;
tables _all_;
run;
proc sql;
select tablevar into : drop separated by ','
from temp
where NNonMissLevels=0;
alter table data&i
drop &drop;
quit;
Original source, since not my code:
https://communities.sas.com/t5/SAS-Programming/deleting-empty-columns/m-p/308045

SAS - Dynamically creating macro variable via concatenation of text to a current macro variable

I'm looking to create a macro variable. The macro variable name needs to be made by concatenating text to an already existing variable after it has resolved. Specifically, I want to do this in a PROC SQL block using INTO:. Here's a snippet to explain what I want to do.
%macro MyMacro(process);
PROC SQL;
SELECT
COUNT(*) INTO: &process._new_text
FROM
DataSetHere
;QUIT;
%mend MyMacro;
If I call on this macro and I pass the word "cat" into process, I want to have now declared/initialized a variable with the name cat_new_text, and it should return the COUNT(*) selected in that query whenever &cat_new_text is referenced.
I've done a little reading around, looked into using multiple ampersands, trying to resolved &process within quotes first - nothing has really solved my exact problem. Does anyone know of a clear cut way to accomplish this?
Thanks in advance!
Your code seems fine and it seems like it would do exactly what you describe.
However if your trying to "access" that new macro variable outside the macro, e.g.,
%MyMacro(cat);
%put &cat._new_text.;
then indeed it will not work because the variable was created locally in your macro and does not exist outside the scope of that macro.
To fix that, you simply need to add a %global statement in your macro definition:
%macro MyMacro(process);
%global &process._new_text;
PROC SQL;
SELECT COUNT(*)
INTO: &process._new_text
FROM DataSetHere
;
QUIT;
%mend MyMacro;

SAS Proc Datasets - Change dataset name using macro variable

I want to change dataset names in SAS using concatenated macro variables. Using the example code below I get an error. Is my syntax wrong or is it not possible to use a concatenating function in this way?
Code:
%let term=201610;
%let emp='bob';
Proc Datasets library=work;
change testset = cat(&emp,&term);
run;
Errors Received:
ERROR 22-322: Syntax error, expecting one of the following: ALTER, MEMTYPE, MT, MTYPE, PROTECT, PW, READ, WRITE.
ERROR 76-322: Syntax error, statement will be ignored.
You don't need to concatenate macro variables, it's like a find and replace text.
Because you have quotes right now this is what would happen:
change testset = cat('bob', 201610)
But the CAT function is not valid there. You could technically use %SYSFUNC() to use the CAT function but there's an easier way.
%let term=201610;
%let emp='bob';
Proc Datasets library=work;
change testset = &emp&term.;
run;quit;
Note that PROC DATASETS requires a QUIT to terminate.

SAS: How to assign the output value of a function-like macro?

I am very new using SAS and Im having hard time trying to assign the output value of a function-like macro to a macro variable. After testing, I have check that the value is computed correctly, but once I tried to assign it the program crashes. here you can find the code
%MACRO TP_BULLET(ZCURVE,TAU,YF=1);
/* KEEP ONLY THE ZERO CURVE UNTIL MATURITY*/
DATA _TEMP;
SET &ZCURVE;
IF MATURITY > &TAU THEN DELETE;
RUN;
PROC SQL NOPRINT;
SELECT DISTINCT 1- DF
INTO :NUME
FROM _TEMP
GROUP BY MATURITY
HAVING MATURITY = MAX(MATURITY);
QUIT;
PROC SQL NOPRINT;
SELECT SUM(DF)
INTO :DENO
FROM _TEMP;
QUIT;
PROC DELETE DATA=_TEMP;RUN;
%LET TP = %SYSEVALF(&YF*&NUME / &DENO);
&TP
%MEND TP_BULLET;
%MACRO TESTER2;
%LET K = %TP_BULLET(ZCURVE,TAU,YF=1);
%PUT .......&K;
%MEND TESTER2;
%TESTER2;
The error I am getting is the following
WARNING: Apparent symbolic reference DENO not resolved.
WARNING: Apparent symbolic reference NUME not resolved.
ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was:
1*&NUME / &DENO
So I suppose that the DATA Step is failing to create the sas table _TEMP, but I have no idea how to solve it. Thanks in advance for any help or sugestion
This is NOT a 'function like' macro - you are mixing SAS Macro language and Base SAS. Remember that the SAS Macro language is a code generator - and you are generating code, which is currently something like:
%let K=data _temp; set ZCURVE; ....
note how the above is not what you wanted to assign to the macro variable K.
To help with this, try running your macro with options mprint - this will show you the code being generated by your macro.
If you want your macro to act like a function, then (at a minimum) you should find NO code being generated via the mprint option..
All philosophical issues aside, you could add a parameter to your macro that specifies the new macrovariable (mv) that you want to create. So instead of
%Let k = %TP_BULLET(ZCURVE,TAU,YF=1);
you could call
%TP_BULLET(ZCURVE,TAU,mvOutput=k,YF=1);;
Your macro would need to be modified slightly with
%MACRO TP_BULLET(ZCURVE,TAU,mvOutput,YF=1);
%GLOBAL &mvOutput;
........ Same code as above .........
%Let &mvOutput = &TP; *Instead of final line with '&TP';
%MEND;
It is not a very SAS-y way to accomplish it, but it can help keep things more modular and comprehensible if you're working with more programming backgrounds, rather than SAS.