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;
Related
I've just finished the main macro for a project that I'm on. It generates a line to enter into another table. So, my next step is to write another macro that calls this one. One of the arguments for this next macro is the name of the dataset in which to insert this new observation, which leads to my question...
I'd like for my next macro to check and see if the named dataset exists. If so, it will insert the new calculated line into the dataset. If it dose not yet exist, I'd like to save the new line as a dataset with this name.
To get a little bit more concrete, let's suppose I have the macro
%calculate_for(ARG1, ARG2, ARG3) that creates a single-observation dataset NEXT_LINE. I want to write a macro that does something like:
%macro do_for(ARG1, ARG2, ARG3, DATASET_NAME);
%calculate_for(&ARG1, &ARG2, &ARG3)
{if DATASET_NAME exists then do:}
data &DATASET_NAME;
set &DATASET_NAME
NEXT_LINE;
run;
{if DATASET_NAME doesn't exist yet then do:}
data &DATASET_NAME;
set NEXT_LINE;
run;
%mend;
How might I go about doing this in SAS?
The macro function %SYSFUNC can be used to invoke almost any DATA step function.
For example
%macro …;
data &out;
set
%if %sysfunc (EXIST(&OUT,DATA)) %then %do;
&OUT
%end;
NEXT_LINE;
;
run;
%mend;
Likewise, the %SYSCALL routine can be used to invoke almost any CALL routine.
As #Reeza comments, for the specific coding case in your question, Proc APPEND could be the better choice. The pattern shown in your sample code would cause an entire rewrite of the base table.
Other coding patterns that do not rewrite the entire data set include
DATA Step : MODIFY statement with subsequent OUTPUT, REPLACE or REMOVE statements
Proc SQL : INSERT INTO … SELECT … FROM
If you are doing a lot of development, perhaps don't recreate the wheel at every step. Look around for SAS macro libraries that have common utility features, one example Roland's SAS® Macros
I'm trying to convert a SAS dataset column to a list of macro variables but am unsure of how indexing works in this language.
DATA _Null_;
do I = 1 to &num_or;
set CondensedOverrides4 nobs = num_or;
call symputx("Item" !! left(put(I,8.))
,"Rule", "G");
end;
run;
Right now this code creates a list of macro variables Item1,Item2,..ItemN etc. and assigns the entire column called "Rule" to each new variable. My goal is to put the first observation of "Rule" in Item1, the second observation in that column in Item2, etc.
I'm pretty new to SAS and understand you can't brute force logic in the same way as other languages but if there's a way to do this I would appreciate the guidance.
Much easier to create a series of macro variables using PROC SQL's INTO clause. You can save the number of items into a macro variable.
proc sql noprint;
select rule into :Item1-
from CondensedOverrides4
;
%let num_or=&sqlobs;
quit;
If you want to use a data step there is no need for a DO loop. The data step iterates over the inputs automatically. Put the code to save the number of observations into a macro variable BEFORE the set statement in case the input dataset is empty.
data _null_;
if eof then call symputx('num_or',_n_-1);
set CondensedOverrides4 end=eof ;
call symputx(cats('Item',_n_),rule,'g');
run;
SAS does not need loops to access each row, it does it automatically. So your code is really close. Instead of I, use the automatic variable _n_ which can function as a row counter though it's actually a step counter.
DATA _Null_;
set CondensedOverrides4;
call symputx("Item" || put(_n_,8. -l) , Rule, "G");
run;
To be honest though, if you're new to SAS using macro variables to start isn't recommended, there are usually multiple ways to avoid it anyways and I only use it if there's no other choice. It's incredibly powerful, but easy to get wrong and harder to debug.
EDIT: I modified the code to remove the LEFT() function since you can use the -l option on the PUT statement to left align the results directly.
EDIT2: Removing the quotes around RULE since I suspect it's a variable you want to store the value of, not the text string 'RULE'. If you want the macro variables to resolve to a string you would add back the quotes but that seems incorrect based on your question.
I posted a question a while back about trimming a macro variable down that I am using to download a CSV from Yahoo Finance that contains variable information on each pass to the site. The code that was suggested to me to achieve this was as follows:
data _null_;
a = "&testvar.";
call symputx('svar',trim(input(a,$8.)));
run;
That worked great, however I have since needed to redesign the code so that I am declaring multiple macro variables and submitting multiple ones at the same time.
To declare multiple macros at the same time I have used the following lines of code:
%let svar&e. = &svar.;
%put stock_ticker = &&svar&e.;
The varible &e. is an iterative variable that goes up by one everytime. This declares what looks to be an identical macro to the one called &svar. everytime they are put into the log, however the new dynamic macro is now throwing up the original warning message of:
WARNING: The quoted string currently being processed has become more than 262 characters long. You
may have unbalanced quotation marks.
That i was getting before i started using the symputx option suggested in my original problem.
The full code for this particular nested macro is listed below:
%macro symbol_var;
/*here the start row and end row created in the macro above are passed to this nested macro and then passed through the*/
/*source dataset. at the end of the loop each ticker macro variable is defined in turn for use in the following nested*/
/*macro, symbol by metric.*/
%do e = &beg_point. %to &end_point. %by 1;
%put stock row in dataset nasdaq ticker = &e.;
%global svar&e;
proc sql noprint;
select symbol
into :testvar
from nasdaq_ticker
where monotonic() = &e.;
quit;
/*convert value to string here*/
data _null_;
a = "&testvar.";
call symputx('svar',trim(input(a,$8.)));
run;
%let svar&e. = &svar.;
%put stock_ticker = &&svar&e.;
%end;
%mend;
%symbol_var;
Anyone have any suggestions how I could declare the macro &&svar&e. directly into the call synputx step? It currently throws up an error saying that the macro variable being created cannot contain any special characters. Ive tried using "E, %NRQUOTE and %NRBQUOTE but either I have used the function in an invalid context or I haven't got the syntax exactly right.
Thanks
Isn't this as simple as the following two line data step?
%macro symbol_var;
/*here the start row and end row created in the macro above are passed to this nested macro and then passed through the*/
/*source dataset. at the end of the loop each ticker macro variable is defined in turn for use in the following nested*/
/*macro, symbol by metric.*/
data _null_;
set nasdaq_ticker(firstobs=&beg_point. obs=&end_point.);
call symputx('svar' || strip(_n_), symbol);
run;
%mend;
%symbol_var;
Or the following (which includes debugging output)
%macro symbol_var;
/*here the start row and end row created in the macro above are passed to this nested macro and then passed through the*/
/*source dataset. at the end of the loop each ticker macro variable is defined in turn for use in the following nested*/
/*macro, symbol by metric.*/
data _null_;
set nasdaq_ticker(firstobs=&beg_point. obs=&end_point.);
length varname $ 32;
varname = 'svar' || strip(_n_);
call symputx(varname, symbol);
put varname '= ' symbol;
run;
%mend;
%symbol_var;
When manipulating macro variables and desiring bullet-proof code I often find myself reverting to using a data null step. The original post included the problem about a quoted string warning. This happens because the SAS macro parser does not hide the value of your macro variables from the syntax scanner. This means that your data (stored in macro vars) can create syntax errors in your program because SAS attempts to interpret it as code (shudder!). It really makes the hair on the back of my neck stand up to risk my program at the hands of what might be in the data. Using the data step and functions protects you from this completely. You will note that my code never uses an ampersand character other than the observation window points. This makes my code bullet proof regarding what dirty data there may be in the nasdaq_ticker data set.
Also, it is important to point out that both Dom and I wrote code that makes one pass over the nasdaq_ticker data set. Not to bash the original posted code, but looping in that way causes a proc sql invocation for every observation in the result set. This will create very poor performance for large result sets. I recommend developing an awareness of how many times a macro loop is going to cause you to read a data set. I have been bitten by this many times in my own code.
Try
call symputx("svar&e",trim(input(a,$8.)));
You need double quotes ("") to resolve the e macro.
As an aside, I am not sure you need the input statement if $testvar is a string and not a number.
I would have written this as
%macro whatever();
proc sql noprint;
select count(*)
into :n
from nasdaq_ticker;
select strip(symbol)
into :svar1 - :svar%left(&n)
from nasdaq_ticker;
quit;
%do i=1 %to &n;
%put stock_ticker = &&svar&i;
%end;
%mend;
I want to perform some regression and i would like to count the number of nonmissing observation for each variable. But i don't know yet which variable i will use. I've come up with the following solution which does not work. Any help?
Here basically I put each one of my explanatory variable in variable. For example
var1 var 2 -> w1 = var1, w2= var2. Notice that i don't know how many variable i have in advance so i leave room for ten variables.
Then store the potential variable using symput.
data _null_;
cntw=countw(¶meters);
i = 1;
array w{10} $15.;
do while(i <= cntw);
w[i]= scan((¶meters"),i, ' ');
i = i +1;
end;
/* store a variable globally*/
do j=1 to 10;
call symput("explanVar"||left(put(j,3.)), w(j));
end;
run;
My next step is to perform a proc sql using the variable i've stored. It does not work as
if I have less than 10 variables.
proc sql;
select count(&explanVar1), count(&explanVar2),
count(&explanVar3), count(&explanVar4),
count(&explanVar5), count(&explanVar6),
count(&explanVar7), count(&explanVar8),
count(&explanVar9), count(&explanVar10)
from estimation
;quit;
Can this code work with less than 10 variables?
You haven't provided the full context for this project, so it's unclear if this will work for you - but I think this is what I'd do.
First off, you're in SAS, use SAS where it's best - counting things. Instead of the PROC SQL and the data step, use PROC MEANS:
proc means data=estimation n;
var ¶meters.;
run;
That, without any extra work, gets you the number of nonmissing values for all of your variables in one nice table.
Secondly, if there is a reason to do the PROC SQL, it's probably a bit more logical to structure it this way.
proc sql;
select
%do i = 1 %to %sysfunc(countw(¶meters.));
count(%scan(¶meters.,&i.) ) as Parameter_&i., /* or could reuse the %scan result to name this better*/
%end; count(1) as Total_Obs
from estimation;
quit;
The final Total Obs column is useful to simplify the code (dealing with the extra comma is mildly annoying). You could also put it at the start and prepend the commas.
You finally could also drive this from a dataset rather than a macro variable. I like that better, in general, as it's easier to deal with in a lot of ways. If your parameter list is in a data set somewhere (one parameter per row, in the dataset "Parameters", with "var" as the name of the column containing the parameter), you could do
proc sql;
select cats('%countme(var=',var,')') into :countlist separated by ','
from parameters;
quit;
%macro countme(var=);
count(&var.) as &var._count
%mend countme;
proc sql;
select &countlist from estimation;
quit;
This I like the best, as it is the simplest code and is very easy to modify. You could even drive it from a contents of estimation, if it's easy to determine what your potential parameters might be from that (or from dictionary.columns).
I'm not sure about your SAS macro, but the SQL query will work with these two notes:
1) If you don't follow your COUNT() functions with an identifier such as "COUNT() AS VAR1", your results will not have field headings. If that's ok with you, then you may not need to worry about it. But if you export the data, it will be helpful for you if you name them by adding "...AS "MY_NAME".
2) For observations with fewer than 10 variables, the query will return NULL values. So don't worry about not getting all of the results with what you have, because as long as the table you're querying has space for 10 variables (10 separate fields), you will get data back.
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