I am trying to write a macro wherein my paramter has multiple values and a few of those are prefxed with space. I want to be able to read in the strings along with the space, but space being the default delimiter is causing issues.
`%macro ab(where_p=);
data want;
set have;
%DO I =1 %TO %SYSFUNC(COUNTW(&WHERE_P));
%IF %LENGTH(&WHERE_P) > 0 %THEN %DO;
B_&I=%SCAN(%STR(&WHERE_P),&I);
%end;
%end;
run;
%mend;
%ab(WHERE_P=" ATF" " TRUST");`
Here it is not able to read in values as is, it reads a space as one string then ATF as next and then space again and TRUST as next. Wherein, it should read ' ATF' as one string and 'TRUST'as second.
Can someone help read in such data using scan function.
Thanks
Just use the functionality of the %SCAN() function to handle this. If the data includes the delimiter then the values need to be quoted.
%let WHERE_P=" ATF" " TRUST";
%let word1 = %scan(&where_p,1,%str( ),q);
So your loop should look like:
%IF %LENGTH(&WHERE_P) %THEN %DO I =1 %TO %SYSFUNC(COUNTW(&WHERE_P,%str( ),q));
B_&I=%SCAN(&where_p,&I,%str( ),q);
%end;
...
%ab(WHERE_P=" ATF" " TRUST");`
Or you could use a different delimiter that does NOT appear in the data. If you want to pass in leading spaces without actual quotes then you need to use macro quoting.
%IF %LENGTH(&WHERE_P) %THEN %DO I =1 %TO %SYSFUNC(COUNTW(&WHERE_P,|));
B_&I=%sysfunc(quote(%qSCAN(&where_p,&I,|)));
%end;
...
%ab(WHERE_P=%str( ATF| TRUST));
Try:
%macro ab(where_p=);
%let array_size = %EVAL(%SYSFUNC(COUNTC(&WHERE_P, '"'))/2);
data want;
set have;
array B_(&array_size) $20 (&where_p);
run;
%mend;
%ab(WHERE_P=" ATF" " TRUST" );
You first find the number of items which is number of quotes divided by 2.
Then create an array of that size and assign values using &WHERE_P directly.
If you want to allow strings in WHERE_P longer than 20 chars, you need to change the length in the array line.
Related
I have a libY.tableX that have for each record some SQL strings like the ones below and other fields to write the result of their execution.
select count(*) from libZ.tableK
select sum(fieldV) from libZ.tableK
select min(dsitact) from libZ.tableK
This my steps:
the user is prompted to select a lib and table and the value is passed to the vars &sel_livraria and &sel_tabela;
My 1st block is a proc sql to get all the sql string from that record.
My 2nd block is trying to concrenate all that strings to use further on to update my table with the results. The macro %isBlank is the one recommended by Chang CHung and John King in their sas papper;
My 3th block is to execute that concrenated sql string and update the table with results.
%macro exec_strings;
proc sql noprint ;
select livraria, tabela, sql_tot_linhas, sql_sum_num, sql_min_data, sql_max_data
into :livraria, :tabela, :sql_tot_linhas, :sql_sum_num, :sql_min_data, :sql_max_data
from libY.tableX
where livraria='&sel_livraria'
and tabela='&sel_tabela';
quit;
%LET mystring1 =%str(tot_linhas=(&sql_tot_linhas));
%LET separador =%str(,);
%if %isBlank(&sql_sum_num) %then %LET mystring2=&mystring1;
%else %LET mystring2= %sysfunc(catx(&separador,&mystring1,%str(sum_num=(&sql_tot_linhas))));
%if %isBlank(&sql_min_data) %then %LET mystring3=&mystring2 ;
%else %LET mystring3= %sysfunc(catx(&separador,&mystring2,%str(min_data=(&sql_min_data))));
%if %isBlank(&sql_max_data) %then %LET mystring0=&mystring3;
%else %LET mystring0= %sysfunc(catx(&separador,&mystring3,%str(max_data=(&sql_min_data))));
%PUT &mystring0;
proc sql noprint;
update libY.tableX
set &mystring0
where livraria='&sel_livraria'
and tabela='&sel_tabela';
quit;
%mend;
My problem with the code above is that iam getting this error in my final concrenated string, &mystring0.
tot_linhas=(&sql_tot_linhas),sum_num=(&sql_tot_linhas),min_data=(&sql_min_data),max_data=(&sql_min_data)
_ _ _ _
ERROR 22-322: Syntax error, expecting one of the following: a name, a quoted string, a numeric constant, a datetime constant, a missing value, BTRIM, INPUT, PUT, SUBSTRING, USER.
Any help appreciated
Ok, so i follow Tom comments and ended with a proc sql solution that works!
proc sql;
select sql_tot_linhas,
(case when sql_sum_num = '' then "0" else sql_sum_num end),
(case when sql_min_data = '' then "." else sql_min_data end),
(case when sql_max_data = '' then "." else sql_max_data end)
into:sql_linhas, :sql_numeros, :sql_mindata, :sql_mxdata
from libY.tableX
where livraria="&sel_livraria"
and tabela="&sel_tabela";
quit;
proc sql;
update libY.tableX
set tot_linhas = (&sql_linhas),
sum_num =(&sql_numeros),
min_data = (&sql_mindata),
max_data = (&sql_mxdata)
where livraria="&sel_livraria"
and tabela="&sel_tabela";
quit;
Tks Tom :)
It is very hard to tell from your description what it is you are trying to do, but there are some clear coding issues in the snippets of code you did share.
First is that macro expressions are not evaluated in string literals bounded by single quotes. You must use double quotes.
where livraria="&sel_livraria"
Second is you do not want to use any of the CAT...() SAS functions in macro code. Mainly because you don't need them. If you want to concatenate values in macro code just type them next to each other. But also because they do not work well with %SYSFUNC() because they allow their arguments to be either numeric or character so %SYSFUNC() will have to guess from the strings you pass it whether it should tell the SAS function those strings are numeric or character values.
So perhaps something like:
%let mystring=tot_linhas=(&sql_tot_linhas);
%if not %isBlank(&sql_sum_num) %then
%LET mystring=&mystring,sum_num=(&sql_tot_linhas)
;
%if not %isBlank(&sql_min_data) %then
%LET mystring=&mystring,min_data=(&sql_min_data)
;
%if not %isBlank(&sql_max_data) %then
%LET mystring=&mystring,max_data=(&sql_max_data)
;
Note that I also cleaned up some obvious errors when modifying that code. Like the extra & in the value passed to the %ISBLANK() macro and the assignment of the min value to the max variable.
But it would probably be easier to generate the strings in a data step where you can test the values of the actual variables and if needed actually use the CATX() function.
Suppose that I have a library save which contains daily files thefile_dly_yyyymmdd,
e.g. save.thefile_dly_20150831, save.thefile_dly_20150901, ... , save.thefile_dly_20210731.
I want to perform some manipulation on the historical data in this library, however, I only want to extract the files between a specific date, and also only want to keep the file which corresponds to the last file for each month, e.g. I want to extract save.thefile_dly_20150831, save.thefile_dly_20150930, save.thefile_dly_20151031, etc.
Something like the following.
%macro loop_through(start,end);
%do i = &start. %to &end.;
%if %sysunc(exist(SAVE.THEFILE_DLY_&i.)) %then %do;
/* Do some data processing on the file */
%end;
%end;
%mend;
%loop_though(20150831,20210731);
The problem is that the abovementioned code will loop through every single integer between 20150831 and 20210731, which is not optimal, and also, it will process every single file that exists for the month, and not just the file corresponding to the last day of each month.
How can I adjust? Any advice will be appreciated.
To loop over calendar intervals iterate over the number of intervals. Use the INTNX() function to calculate the next interval's date. Use the INTCK() function to calculate the number of intervals requested.
%macro loop_through(start,end);
%local offset ymd dsname ;
%do offset = 0 %to %sysfunc(intck(month,&start,&end));
%let ymd=%sysfunc(intnx(month,&start,&offset,end),yymmddn8.);
%let dsname=SAVE.THEFILE_DLY_&ymd;
%if %sysunc(exist(&dsname)) %then %do;
/* Do some data processing on the file */
%end;
%end;
%mend;
%loop_though('01AUG2015'd,'01JUL2021'd);
If you really want to allow the user of the macro to pass in YYYYMMDD digit strings instead of actual SAS date values then add some logic to your macro to convert the digit strings into actual date values. For example:
%let start=%sysfunc(inputn(&start,yymmdd8.));
so I have a code like below
%let THIS_YEAR=2020;
%macro programall;
%do i = 2016 %to &THIS_YEAR;
%let num2 =%eval(&i-2000);
%let xxx= CAT("MP",&num2);
data t_&i.;
set table1;
where GROUP in ("&xxx");
run;
%end;
for example
when i=2016
num2 = 2016-2000;
num2 = 16;
and try to concatenate with "MP", so it should create xxx=MP16.
and try to use in where statement.
but it is causing error.
how can I create Macro Variable like "MP16"correctly, so I can use it in where clause?
Thanks
You cannot use functions in macro code, they are just treated as any other text to the macro processor. But there is no need to use a function to concatenate text in macro code. Just expand the macro variable where you want to use the text it contains.
%let xxx= MP&num2 ;
Macro variables are just text (not strings, text as in the thing you type in). So to concatenate macro variables, just put them next to each other.
%let var1=Banana;
%let var2=Pepper;
%let var3=&var1. &var2.;
%put &=var3;
You don't actually have to use the third variable of course, you could just use "&var1. &var2." or whatever in your code directly.
Try
%let THIS_YEAR=2020;
%macro programall;
%local year;
%do year = 2016 %to &THIS_YEAR;
data t_&year.;
set table1;
where GROUP in ("MP%eval(&year-2000)");
run;
%end;
%mend;
options mprint;
%programall
I have defined macro variable
%let data_names = fuzzy_Data_segment EMWS2.Clus_TRAIN;
Then I have written a macro to extract and print the values from the above macro variable as:
%macro calling_data;
%do i = 1 %to 2;
%let data_name&i = %qscan(&data_names,&i);
%put &&data_name&i;
%end;
%mend;
%calling_data;
My macro code is able print the first name(fuzzy_Data_segment), but, it is only printing the part of the second name(EMWS2). what should I do to print the entire second name
Your issue is that SAS considers a period to be one of the default delimiters in macro variables. In this case, it looks like you want to be using a space to delimit items in data_names, so specify that:
%let data_name&i= %qscan(&data_names,&i., %str( ));
You're also missing semicolons in your %let statement and in your call to calling_data.
This is my first macro, so my apologies if I missed something simple.
I need to do the same data step six (or more) times and append each one to the first, so I tried a do-loop within a macro. Everything works with the loop removed, but once the do-loop is added, I get errors that either say I have an extra %end or an extraneous %mend. All ideas welcome. Thanks!
%macro freeze_samples(orig_file=, samples= , Start_Freeze_Incr=,
End_Freeze_Incr= );
%do i = 1 %to &samples;
data freeze_slice_&i;
set &orig_file;
(do stuff)
run;
* If we have more than one slice, append to previous slice(s).;
%if &i > 1 %then %do;
proc append base = temp_1 data = temp_&i;
run;
%end;
%end;
%mend;
I think you either have a problem you didn't include in the text (ie, in the 'do stuff' section) or you have a bad session (ie, you fixed the problem but there's something from a previous run messing up something now). This runs fine (given I don't know what you're doing):
%macro freeze_samples(orig_file=, samples= , Start_Freeze_Incr=,
End_Freeze_Incr= );
%do i = 1 %to &samples;
data freeze_slice_&i;
set &orig_file;
*(do stuff);
run;
* If we have more than one slice, append to previous slice(s).;
%if &i > 1 %then %do;
proc append base = freeze_slice_1 data = freeze_slice_&i;
run;
%end;
%end;
%mend;
%freeze_samples(orig_file=sashelp.class,samples=2,start_freeze_incr=1,end_freeze_incr=5);
I would note that you're probably better off not doing whatever you're doing this way; in SAS, there is usually a better way than splitting data off into multiple datasets. But since I don't know what you're doing I can't really suggest the better way beyond recommending reading this article and keeping it in mind (even if you're doing something different than bootstrapping, the concept applies to almost everything in SAS).