regex capturing inside a macro definition (SAS 9.4) - regex

Here is a paraphrased version of my code:
options nofmterr symbolgen;
%macro data_checks(dset,newset);
%if &newset= %then
%let newset = new;
proc contents data=&dset out=work.&newset noprint;
run;
%local re yr;
%let re = %sysfunc(prxparse('/_y(\d\d?)$/'));
%let yr = %sysfunc(prxposn(&re, 1, &dset));
%put &yr;
%mend data_checks
%data_checks(owners.ownersclean_y10, new_dataset);
As far as I can tell, the re string I have is fine. SYMBOLGEN output in the log is telling me that &re is what appears to be a random integer between 1 and 10 each time I run it and &yr appears to resolve to an empty string each time.
What am I doing wrong? &yr is supposed to resolve to 10.

You've got two problems here:
You must use PRXMATCH before PRXPOSN (or a few other match-making functions). See the documentation for PRXPOSN.
You should not use ' in the expression.
This works:
options nofmterr symbolgen;
%macro data_checks(dset,newset);
%if &newset= %then
%let newset = new;
%local re yr;
%let re = %sysfunc(prxparse(/_y(\d\d?)$/));
%let rc = %sysfunc(prxmatch(&re,&dset));
%let yr = %sysfunc(prxposn(&re, 1, &dset));
%put &=yr;
%mend data_checks;
%data_checks(owners.ownersclean_y10, new_dataset);

Related

Iterate date in loop in SAS

need help on one query , I have to iterate date in do loop that is in format of yymmd6.(202112) so that once the month reach to 12 then its automatically change to next year first month.
///// code////////
%let startmo=202010 ;
%let endmo= 202102;
%macro test;
%do month= &startmo %to &endmo;
Data ABC_&month;
Set test&month;
X=&month ;
%end;
Run;
%mend;
%test;
//////////
Output should be 5 dataset as
ABC_202010
ABC_202011
ABC_202012
ABC_202101
ABC_20210
I need macro variable month to be resolved 202101 once it reached to 202012
Those are not actual DATE values. Just strings that you have imposed your own interpretation on so that they LOOK like dates to you.
Use date values instead and then it is easy to generate strings in the style you need by using a FORMAt.
%macro test(startmo,endmo);
%local offset month month_string;
%do offset = 0 to %sysfunc(intck(month,&startmo,&endmo));
%let month=%sysfunc(intnx(month,&startmo,&offset));
%let month_string=%sysfunc(putn(&month,yymmn6.));
data ABC_&month_string;
set test&month_string;
X=&month ;
format X monyy7.;
run;
%end;
%mend;
%test(startmo='01OCT2020'd , endmo='01FEB2021'd)
And if you need to convert one of those strings into a date value use an INFORMAT.
%let date=%sysfunc(inputn(202010,yymmn6.));
I would prefer to use a do while loop.
check whether the last 2 characters are 12, if so, change the month part to 01.
code
%let startmo=202010 ;
%let endmo= 202102;
%macro test;
%do %while(&startmo <= &endmo);
Data ABC_&startmo;
Set test&startmo;
X=&startmo ;
Run;
%end;
%let mon = %substr(&startmo, 5, 2);
%let yr = %substr(&startmo, 1, 4);
%if &mon = 12 %then %do;
%let m = 01;
%let startmo = %sysfunc(cat(%eval(&yr + 1), &m));
%end;
%else %do;
%let startmo = %eval(&startmo + 1);
%end;
%mend;
%test;

Loop between dates

I created below macro to generate few datasets based on date macro.
%macro pull(date);
proc sql;
create table new&date as
select * from acct
where date=&date.;
quit;
%mend;
So if i want to create dataset for 20170101 20170201 20170301 20170401 20170501, all i can do is use below macro
%macro pull(20170101)
%macro pull(20170201)
%macro pull(20170301)
%macro pull(20170401)
%macro pull(20170501)
What i am planning now is create two macro variables
%let begin=20170101;
%let end =20170501;
and create datasets based on begin and end using loop. Is it possible to do that.So what i am trying to do is give start and end date as macro variable and pull records between begin and end date from acct dataset and create separate datasets for each month between start and end dates
Note dataset have monthly dates for each year.
Below is the code i am trying
%let beg="01jan2000"d;
%let end="01jan2001"d;
%macro Test;
%do date=&beg. %to &end.;
proc sql;
create table IPw_&date. as
select *
from sample
where date=&date. quit;
%end;
%mend;
%Test;
When date information must be inferred from values that are not SAS date values you will need to input the information to get a date value, and put the values iterated over to get the desired non date representation.
This example demonstrates
INPUTN function to parse the YYYYMMDD arguments into date values using informat YYMMDD8.
INTNX function to compute 1st of the month of the date values
PUTN function to convert a date value to a YYYYMMDD representation using format YYMMDDN8.
%DO %WHILE statement for iterating
INTNX function to advance the iteration variable to the start of the next month
Code
%macro pull(yyyymmdd);
%local out;
%let out = pull_&yyyymmdd;
data &out;
pull_date = input ("&yyyymmdd", yymmdd8.);
format pull_date yymmdd10.;
run;
%mend;
%macro pull_each_month(begin=, end=);
%local
begin_date end_date
begin_month end_month
pull_date pull_ymd
;
%put NOTE: &=begin &=end;
%let begin_date = %sysfunc(inputn(&begin,yymmdd8.));
%let end_date = %sysfunc(inputn(&end,yymmdd8.));
%put NOTE: &=begin_date &=end_date;
%let begin_month = %sysfunc(intnx(month,&begin_date,0));
%let end_month = %sysfunc(intnx(month,&end_date,0));
%put NOTE: &=begin_month &=end_month;
%let pull_month = &begin_month;
%do %while (&pull_month <= &end_month);
%let pull_ymd = %sysfunc(putn(&pull_month,yymmddn8.));
%put NOTE: Invoking pull for &=pull_month &=pull_ymd;
%pull (&pull_ymd)
%let pull_month = %sysfunc(INTNX(MONTH,&pull_month,1));
%end;
%mend;
%pull_each_month (
begin = 20170101
, end = 20170501
)
%macro pull_each_month(begin=, end=);
%local
begin_date end_date
begin_month end_month
pull_date pull_ymd
;
%put NOTE: &=begin &=end;
%let begin_date = %sysfunc(inputn(&begin,yymmdd8.));
%let end_date = %sysfunc(inputn(&end,yymmdd8.));
%put NOTE: &=begin_date &=end_date;
%let begin_month = %sysfunc(intnx(month,&begin_date,0));
%let end_month = %sysfunc(intnx(month,&end_date,0));
%put NOTE: &=begin_month &=end_month;
%let pull_month = &begin_month;
%do %while (&pull_month <= &end_month);
%let pull_ymd = %sysfunc(putn(&pull_month,yymmddn8.));
%put NOTE: Invoking pull for &=pull_month &=pull_ymd;
%let pull_month = %sysfunc(INTNX(MONTH,&pull_month,1));
%end;
%mend;
%pull_each_month (
begin = 20170101
, end = 20170501
)
%macro pull(begin,end);
%let i=0;
%let begin=%sysfunc(inputn(&begin,anydtdte9.));
%let end=%sysfunc(inputn(&end,anydtdte9.));
%do %until (&begin=&end);
%let begin=%sysfunc(intnx(month,&begin,&i));
%let date=%sysfunc(putn(&begin,yymmddn8.));
proc sql;
create table new&date as
select * from acct where date=&date.;
quit;
%let i=%eval(&i+1);
%end;
%mend;
%pull(20170101,20170501)

Error in Macro Function

I'm using SAS Enterprise Guide. New to writing SAS macro functions. Nested a proc sql inside a macro. I'm trying to first check if a column exists and return the column number and then using the column number, to get the column name so that I can call this macro function in a query builder. However I'm getting ERROR 180-322 under the 'select cats' part of the code. Listed below is the code I have written so far:
%macro varexist(ds,var);
%local rc dsid result dynvar;
%let dsid = %sysfunc(open(&ds));
%let result = %sysfunc(varnum(&dsid,&var));
%let rc =%sysfunc(close(&dsid));
proc sql;
select cats('t1.',name) into :dynvar separated by ', '
from dictionary.columns
where libname = 'WORK' and
memname = 'TRANSPOSE_DATA' and
varnum = "&result";
quit;
&dynvar
%mend varexist;
%put %varexist(WORK.TRANSPOSE_DATA,VAR1);
You have not coded a "MACRO FUNCTION", since the macro emits multiple statements (proc sql; select ... quit;). So if you tried to use it as if it was a function like this:
%let myvar=%varexist(work.transpose_data,age);
then you will end up generating code like:
%let myvar=proc sql;
select cats('t1.',name) ... ;
quit;
So the reason the the select... generates an error is because it is not within a PROC SQL step as the PROC SQL statement has become the value assigned by the %LET statement.
As mentioned in one our my comments, I changed the logic and got it work as follows:
%macro varexist(ta,ds,var);
%local rc dsid result col_name;
%let dsid = %sysfunc(open(&ds));
%let result = %sysfunc(varnum(&dsid,&var));
%let rc =%sysfunc(close(&dsid));
%if &result > 0 %then %let col_name = &ta&var;
%else %if &ta = t1. %then %let col_name = '';
&col_name
%mend varexist;

How to prevent CATx functions from evaluating expression

When calling CATT() function with %sysfunc, is there a way to stop it from evaluating an expression?
For example given the code:
%let date=10-13-2015;
%put %sysfunc(catt(The date Is:,&date));
I would like it to return:
The date Is:10-13-2015
Because 10-13-2015 is just a text string. But instead CATT() sees hyphen as a subtraction sign and evaluates it as a numeric expression, returning:
The date Is:-2018
I have tried macro quoting, but doesn't change anything, I suppose because I need to somehow hide the values from CATT(). Seems if any argument to CATT looks like an expression, it will be treated as such.
Another example:
%let value=2 and 3;
%put %sysfunc(catt(The value Is:,&value));
The value Is:1
Provided you can do so, just remove the comma - there's no need to separate it into an individual parameter (unless you're using catx() rather than catt():
%let date=10-13-2015;
%put %sysfunc(catt(The date Is: &date));
Personally, I think the best way to work is to store the date as a SAS date value and then use the second (optional) parameter of %sysfunc to apply the formatting. This provides better flexibility.
%let date = %sysfunc(mdy(10,13,2015));
%put The date Is: %sysfunc(sum(&date),mmddyyd10.);
If you are insistent on the original approach and are using catx(), then I don't know how to do it exactly. The closest I could get was to insert a piece of text so it couldn't be interpreted as an expression, and then remove that text afterwards using tranwrd. Pretty, ugly, and it leaves a space:
%let date=10-13-2015;
%let tmp=%sysfunc(catx(#, The date Is: , UNIQUE_STRING_TO_REMOVE&date ));
%let want=%sysfunc(tranwrd(&tmp, UNIQUE_STRING_TO_REMOVE, ));
%put &want;
Gives:
The date Is:# 10-13-2015
I also tried every combination of macro quoting, and scanned through the entire SAS function list and couldn't see any other viable options.
I don't see an easy way around this, unfortunately. I do see that you could in theory pass this through an FCMP function, though since FCMP doesn't allow true variable arguments, that isn't ideal either, but...
proc fcmp outlib=work.funcs.funcs;
function catme(delim $, in_string $) $;
length _result $1024;
length _new_delim $1;
_new_delim = scan(in_string,1,delim);
do _i = 1 to countc(in_string,delim);
_result = catx(_new_delim, _result, scan(in_string,_i+1,delim));
end;
return(_result);
endfunc;
quit;
options cmplib=work.funcs;
%let date=10-13-2015;
%put %sysfunc(catme(|,:|The date Is| &date.));
Or add quotes to the argument and then remove them after the CATx.
%sysfunc(dequote(%sysfunc(catt(.... ,"&date."))))
All messy.
The problem with %SYSFUNC() evaluating the arguments is not limited to the CAT() series of functions. Any function that accepts numeric values will result in SAS attempting to evaluate the expression provided.
This can be a useful feature. For example:
%let start_dt=10OCT2012 ;
%put %sysfunc(putn("&start_dt"d +1,date9));
You don't need to use CAT() functions to work with macro variables. Just expand the values next to each other and the are "concatenated".
%let date=10-13-2015;
%put The date Is:&date;
If you want to make a macro that works like the CATX() function then that is also not hard to do.
%macro catx /parmbuff ;
%local dlm return i ;
%if %length(&syspbuff) > 2 %then %do;
%let syspbuff = %qsubstr(&syspbuff,2,%length(&syspbuff)-2);
%let dlm=%qscan(&syspbuff,1,%str(,),q);
%let return=%qscan(&syspbuff,2,%str(,),q);
%do i=3 %to %sysfunc(countw(&syspbuff,%str(,),q));
%let return=&return.&dlm.%qscan(&syspbuff,&i,%str(,),q);
%end;
%end;
&return.
%mend catx;
%put %catx(|,a,b,c);
a|b|c
%put "%catx(",",a,b,c,d)";
"a","b","c","d"
Slightly less insane function-style macro without the dosubl:
%macro catx() /parmbuff;
%local rc dlm i params OUTSTR QWORD outstr;
%let SYSPBUFF = %qsubstr(&SYSPBUFF,2,%length(&SYSPBUFF)-2);
%let dlm = %qscan(&SYSPBUFF,1,%str(,));
%let params = %qsubstr(&SYSPBUFF,%index(&SYSPBUFF,%str(,))+1);
%let i = 1;
%let QWORD = %scan(&PARAMS,&i,%str(,));
%let OUTSTR = &QWORD;
%do %while(&QWORD ne);
%let i = %eval(&i + 1);
%let QWORD = %scan(&PARAMS,&i,%str(,));
%if &QWORD ne %then %let OUTSTR = &OUTSTR.&DLM.&QWORD;
%end;
%unquote(&OUTSTR)
%mend catx;
%put %catx(%str( ),abc,10 - 1 + 2,def);
Somewhat more insane but apparently working option - use %sysfunc(dosubl(...)) and lots of macro logic to create a function-style macro that takes input in the same way as %sysfunc(catx(...)), but forces catx to treat all input as text by quoting it and calling it in a data step.
%macro catxt() /parmbuff;
%local rc dlm i params QPARAMS QWORD outstr;
%let SYSPBUFF = %qsubstr(&SYSPBUFF,2,%length(&SYSPBUFF)-2);
%let dlm = %qscan(&SYSPBUFF,1,%str(,));
%let params = %qsubstr(&SYSPBUFF,%index(&SYSPBUFF,%str(,))+1);
%let i = 1;
%let QWORD = "%scan(&PARAMS,&i,%str(,))";
%let QPARAMS = &QWORD;
%do %while(&QWORD ne "");
%let i = %eval(&i + 1);
%let QWORD = "%scan(&PARAMS,&i,%str(,))";
%if &QWORD ne "" %then %let QPARAMS = &QPARAMS,&QWORD;
%end;
%let rc = %sysfunc(dosubl(%str(
data _null_;
call symput("OUTSTR",catx("&dlm",%unquote(&QPARAMS)));
run;
)));
&OUTSTR
%mend catxt;
%put %catxt(%str( ),abc,10 - 1 + 2,def);
Although this uses a data step to execute catx, dosubl allows the whole thing to be run in any place where you could normally use %sysfunc(catx(...)).

A macro function to produce a macro variable from a data variable

data sample;
input x $;
datalines;
one
two
three
;
%macro variable_to_macvar(variable=, dataset=);
proc sql noprint;
select &variable into : outlist separated by ' '
from &dataset;
quit;
&outlist
%mend variable_to_macvar;
%put %variable_to_macvar(variable=x, dataset=sample);
Expected output: one two three. Instead I get an error. Why? Is this fixable?
I've successfully created other macros of a very similar form, where the function "returns" a value using the &macrovariable at the end of the macro without a semicolon. For example, here is a similar type of function that works:
%macro zcat(first=5, last=15, prefix=freq);
%let x=;
%do i = &first %to &last;
%let x=&x &prefix.&i;
%end;
&x
%mend zcat;
%put %zcat();
You cannot execute a macro that involves running a proc or a data step in the way that you're trying to do here. You would need to use something like %sysfunc(dosubl(proc sql...)) in order for that to work (assuming you have SAS 9.3+ - see Joe's answer above). Otherwise, you can't use proc sql within a function-style macro.
More details about dosubl:
http://support.sas.com/documentation/cdl/en/lefunctionsref/67398/HTML/default/viewer.htm#p09dcftd1xxg1kn1brnjyc0q93yk.htm
It would be a bit fiddly, but if you really wanted to make this work as a function-style macro in earlier versions of SAS, you could construct it using the open, fetchobs and getvarc functions instead.
Update: Here's an example (using call set rather than getvarc, as this turned out to be simpler), in case anyone needs to do this in SAS 9.2 or earlier.
%macro variable_to_macvar(var,ds);
%local rc dsid i;
%let &var =;
%global outlist;
%let outlist=;
%let dsid = %sysfunc(open(&ds,i));
%syscall set(dsid);
%let rc = 0;
%let i = 0;
%do %while(&rc = 0);
%let i = %eval(&i + 1);
%let rc = %sysfunc(fetchobs(&dsid,&i));
%if &rc = 0 %then %let outlist = &outlist &&&var;
%end;
%let rc = %sysfunc(close(&dsid));
&outlist
%mend;
%put %variable_to_macvar(var=x, ds=sample);
Now works for views as well as ordinary datasets.
DOSUBL is available (but experimental) in 9.3 (at least, 9.3TS1M2, which I have). This is how you'd do it.
data sample;
input x $;
datalines;
one
two
three
;
%macro variable_to_macvar(variable=, dataset=);
%let rc=%sysfunc(dosubl(%str(
proc sql noprint;
select &variable into : outlist separated by ' '
from &dataset;
quit;
)));
&outlist
%mend variable_to_macvar;
%put %variable_to_macvar(variable=x, dataset=sample);;
If you can't use DOSUBL, or want to avoid experimental things, you can do this with PROC FCMP rather than a macro. If you like to write functions, PROC FCMP is probably for you: actually being able to write functions, rather than having to deal with the annoyances of the macro language.
Alter your code at the end to
%global outlist;
%variable_to_macvar(variable=x, dataset=sample);
%put &outlist;
The %put wants to resolve only a macro variable or a single value. It cannot call a procedure. So call your macro and then print the result.
Also, delete the &outlist from the macro definition. Sorry I missed that initially.
EDIT: Alternative.
Change your macro definition to
%macro variable_to_macvar(variable=, dataset=);
proc sql noprint;
select &variable into : outlist separated by ' '
from &dataset;
quit;
%put &outlist
%mend variable_to_macvar;
Just do the %put inside the macro.
%variable_to_macvar(variable=x, dataset=sample);
will print the string to the log.
We have a utility macro that is probably one of our most used pieces of code that does this for us. It is similar to the code that #user667489 provided but includes some nice features including error catching, allows both character and numeric vars, allows you to specify seperators, quotes, quote characters, filters to the dataset, etc....
We just put this macro in our autocall library so that it's avaialble to all of our programs. Some examples of running the macro:
Example 1 - Default behaviour:
%put %variable_to_macvar(var=x, ds=samplex);
Result 1:
one,two,three
Not quite the desired output as the default seperator is a comma, this is easily changed though...
Example 2 - Specify to use a space character as a delimiter:
%put %ds2list(iDs=samplex, iField=x, iDelimiter=%str( ));
Result 2:
one two three
Example 3 - Quoting & example usage
data names;
input name $;
datalines;
John
Jim
Frankie
;
run;
%put %ds2list(iDs=names, iField=name, iQuote=1);
proc sql noprint;
create table xx as
select *
from sashelp.class
where name in (%ds2list(iDs=names, iField=name, iQuote=1))
;
quit;
Result 3:
The below is printed to the log:
'John','Jim','Frankie'
Notice how we don't need to even save the result to a macro variable to use it in the SQL statement! Swweeet! This works just as well for SQL passthrough queries, and any other data step or proc statement that you can throw it at. In the above example, a single row is returned as 'John' is the only match found...
Anyway, that's our solution here... been using this for >10 years and works well for me. Here is the macro:
/***************************************************************************
** PROGRAM: MACRO.DS2LIST.SAS
**
** UTILITY PROGRAM THAT DETECTS RETURNS A LIST OF FIELD VALUES FROM A
** DATASET IN DELIMITED FORMAT.
**
** PARAMETERS:
** iDs : THE LIBNAME.DATASET NAME THAT YOU WANT TO CHECK.
** iField : THE FIELD THAT CONTAINS THE VALUES YOU WANT RETURNED IN A
** DELIMITED FORMAT.
** iDelimiter: DEFAULT IS A COMMA. THE DELIMITER TO USE FOR THE RETURNED LIST.
** iDsOptions: ANY STANDARD DATASET OPTIONS THAT YOU WOULD LIKE TO APPLY SUCH
** AS A WHERE STATEMENT.
** iQuote : (0=NO,1=YES). DEFAULT=0/NO. DETERMINES WHETHER THE RETURNED
** LIST IS QUOTED OR NOT.
** iQuoteChar: (SINGLE,DOUBLE) DEFAULT=SINGLE. SPECIFIES WHETHER SINGLE
** OR DOUBLE QUOTES ARE USED WHEN QUOTING THE RETURNED LIST
**
*****************************************************************************
** VERSION:
** 1.8 MODIFIED: 11-OCT-2010 BY: KN
** ALLOW BLANK CHARACTER VALUES AND ALSO REMOVED TRAILING
** ALLOW PARENTHESES IN CHARACTER VALUES
*****************************************************************************/
%macro ds2list(iDs=, iField=, iDsOptions=, iDelimiter=%str(,), iQuote=0, iQuoteChar=single);
%local dsid pos rc result cnt quotechar;
%let result=;
%let cnt=0;
%if &iQuote %then %do;
%if "%upcase(&iQuoteChar)" eq "DOUBLE" %then %do;
%let quotechar = %nrstr(%");
%end;
%else %if "%upcase(&iQuoteChar)" eq "SINGLE" %then %do;
%let quotechar = %nrstr(%');
%end;
%else %do;
%let quotechar = %nrstr(%");
%put WARNING: MACRO.DS2LIST.SAS: PARAMETER IQUOTECHAR INCORRECT. DEFAULTED TO DOUBLE;
%end;
%end;
%else %do;
%let quotechar = ;
%end;
/*
** ENSURE ALL THE REQUIRED PARAMETERS WERE PASSED IN.
*/
%if "&iDs" ne "" and "&iField" ne "" %then %do;
%let dsid=%sysfunc(open(&iDs(&iDsOptions),i));
%if &dsid %then %do;
%let pos=%sysfunc(varnum(&dsid,&iField));
%if &pos %then %do;
%let rc=%sysfunc(fetch(&dsid));
%do %while (&rc eq 0);
%if "%sysfunc(vartype(&dsid,&pos))" = "C" %then %do;
%let value = %qsysfunc(getvarc(&dsid,&pos));
%if "%trim(&value)" ne "" %then %do;
%let value = %qsysfunc(cats(%nrstr(&value)));
%end;
%end;
%else %do;
%let value = %sysfunc(getvarn(&dsid,&pos));
%end;
/* WHITESPACE/CARRIAGE RETURNS REMOVED IN THE BELOW LINE */
/* TO ENSURE NO WHITESPACE IS RETURNED IN THE OUTPUT. */
%if &cnt ne 0 %then %do;%unquote(&iDelimiter)%end;%unquote(&quotechar&value&quotechar.)
%let cnt = %eval(&cnt + 1);
%let rc = %sysfunc(fetch(&dsid));
%end;
%if &rc ne -1 %then %do;
%put WARNING: MACRO.DS2LIST.SAS: %sysfunc(sysmsg());
%end;
%end;
%else %do;
%put ERROR: MACRO.DS2LIST.SAS: FIELD &iField NOT FOUND IN DATASET %upcase(&iDs).;
%end;
%end;
%else %do;
%put ERROR: MACRO.DS2LIST.SAS: DATASET %upcase(&iDs) COULD NOT BE OPENED.;
%end;
%let rc=%sysfunc(close(&dsid));
%end;
%else %do;
%put ERROR: MACRO.DS2LIST.SAS: YOU MUST SPECIFY BOTH THE IDS AND IFIELD PARAMETERS TO CALL THIS MACRO.;
%end;
%mend;