BookmarkSubscribeRSS Feed
☑ This topic is solved. Need further help from the community? Please sign in and ask a new question.
acordes
Rhodochrosite | Level 12

I'm going crazy. I cannot figure out how to resolve this issue. 

I want to save a file under the condition that the latest modified date is greater than the last saved date. 

It works but it does not update correctly the macro vars _mod and _last. 

Like a headless chicken I'm trying to solve this by brute force doing things like calling twice the macro, usage of symget, call symputx, select :into, call symdel...

 

Without success. At run time it doesn't get the latest updated values of _mod (last modified) and _last (last saved). 

 

cas mySession sessopts=(caslib=casuser timeout=1800 locale="en_US");
caslib _all_ assign;

/* --- Begin Edit --- */
%let BASE_URI=%sysfunc(getoption(servicesbaseurl));

%put &base_uri.;

%let baseurl=https:/xxxxxxxxx;

%let ppath=/Users/xxxxxxxx@comp.com/My Folder/in_pdf/;

filename myfldr filesrvc folderPath="&ppath.";

data filenames;
length  fname $200;
did = dopen('myfldr');
do i = 1 to dnum(did);
fname = dread(did,i);
if index(lowcase(fname), 'json') then output;
end;
did = dclose(did);
keep fname;
run;
 
filename folders temp;
proc http
url = "https://xxxxxxxxx"
out= folders
oauth_bearer = sas_services;
headers
'Accept'= 'application/vnd.sas.collection+json';
run;
libname folders clear;
libname folders json;
 
%let hostname = xxxxxxxxx;
%let endpoint = /folders/folders;
%let path = "/Public/ODAP_REPORTS" ;
%let output = "/out_json_backup";
%let currentDateTime = %sysfunc(datetime(), datetime20.);
%put &currentDateTime;
 
/**********************************************************/
/* Retrieve the ID of the folder where the report resides */
/**********************************************************/
filename folders clear;
filename folders temp;
 
proc http
url = "&hostname.&endpoint/@item"
query = ("path"=&path)
out= folders
oauth_bearer = sas_services;
headers
'Accept'= 'application/vnd.sas.content.folder+json';
run;
 
libname folders clear;
libname folders json;
 
/*************************************************/
/* Get a list of objects in that specific folder */
/*************************************************/
/* Identify the endpoint to be used */
proc sql ;
select href, type into :endpoint, :type
from folders.links
where method="GET" and rel = "members";
quit;
 
/* Retrieve the list of objects */
filename memList clear;
filename memList temp;
 
proc http
url = "&hostname.%trim(&endpoint)"
out= memList
oauth_bearer = sas_services;
headers
"Accept"= "%trim(&type)+json";
run;
 
libname memList clear;
libname memList json;
 
/******************************************************/
/* Extract report content (structure) for each report */
/******************************************************/
/* Macro to write the output to a json file */
%sysmacdelete writeJson;
%macro writeJson(name, path);
filename outcsv FILESRVC FOLDERPATH="&ppath."  FILENAME="_&name._&currentDateTime..json";
proc json out=outcsv nosastags pretty noscan   ;
export casuser.info;
run;
%mend;

%macro get_bu_date(name);
proc sql;
select substr(fname, 1, prxmatch("m/(?<=_)(\d{2}.*)(?=\.json)/oi",fname)-2) as rep_name,  floor(max(input(substr(fname, prxmatch("m/(?<=_)(\d{2}.*)(?=\.json)/oi",fname), 18), anydtdtm.))) format=best12. into :nom, :_last 
from WORK.FILENAMES
where prxmatch("m/_&name/oi",fname)
group by 1;
quit;

data _null_;
set WORK.FILENAMES;
where prxmatch("m/_&name/oi",fname);
retain valor;
valor=max(valor, floor(max(input(substr(fname, prxmatch("m/(?<=_)(\d{2}.*)(?=\.json)/oi",fname), 18), anydtdtm.))));
call symputx('_last', valor);
run;
%put &_last.;
%mend;
 
/* Macro to read the report content and generate the output file */
%sysmacdelete readReportContent;
%macro readReportContent (url, name, path, endpoint, output);
filename f FILESRVC FOLDERPATH="&path."  FILENAME="text.txt" lrecl=2000000;
/* filename f temp lrecl=2000000; */
 
proc http
url="&url.%trim(&endpoint)/content"
out=f
oauth_bearer=sas_services;
headers
"Accept"="application/vnd.sas.report.content+json";
quit;

%let n=60;

data casuser.info;
length report $128 path $1024 c1-c&n. varchar(32767) content varchar(2000000);
infile f lrecl=2000000 recfm=v  truncover;
input (c1-c&n.) ($32767.);

content=cats(of c1-c&n.);
array c[*] c1-c&n. content;
report="&name";
path="&path";
drop c2-c&n.;
run;

proc sql;
select compress(substr(content, 1, 1000), '"') into :inf
from casuser.info;
run;


data _null_;
pid=prxparse('/(?<=datemodified:)(.{19})/io');
format want $26. fecha datetime19.;
string="&inf.";
put string=;
s=1;e=length(string);
 call prxnext(pid,s,e,string,p,l);
 do while(p>0);
   	want=catx(' ',want,substr(string,p+1,l));
	want=tranwrd(want,'T', ' ');
	fecha=input(want, anydtdtm.);
    call prxnext(pid,s,e,string,p,l);
	put string= want= p= l= fecha=;
	call symputx('_mod', fecha);
 end;
	temp=symget('_last');
	call symputx('lasted', temp);

run;

%put &_mod. &_last. &lasted. ;

%if %sysevalf(&_mod. > &_last.,boolean) %then %do;
%put "executed";
data _null_;
set casuser.info;
where path ne '';
location=tranwrd(path, "/", "_");
mcall=cat('%writeJson(', trim(report), ',', trim(location), ')');
call execute(mcall);
run;

%end;
 
%mend;
 

/* Generate a view containing information to call readReportContent */
proc sql;
create table merged as
select "&hostname" as url,
a.ordinal_items,
a. name,
a.uri,
b.href,
b.type ,
b.method
from memlist.items as a
left join memlist.items_links as b
on a.ordinal_items=b.ordinal_items
having a.contentType="report" and b.rel="getResource";
quit;

options mlogic;
 
/* Call readReportContent macro for each report */
data _null_;
length mcall $1024;
set merged(obs=3 firstobs=3);
out=&output;
path=&path;
/* call symdel("_last"); */
/* call symdel("lasted"); */
/* call symdel("_mod"); */
mstart=cat('%get_bu_date(', trim(name), ')');
call execute (%nrstr(mstart));
/* mcall=cat('%readReportContent(', trim(url), ",", trim(name), ",", trim(path),",", trim(href), ",", trim(out), ')'); */
/* call execute(mcall); */
run;

data _null_;
length mcall $1024;
set merged(obs=3 firstobs=3);
out=&output;
path=&path;
temp= symget("_last");
call symputx('_last', temp);
/* call symdel("lasted"); */
/* call symdel("_mod"); */
mstart=cat('%get_bu_date(', trim(name), ')');
call execute (%nrstr(mstart));
mcall=cat('%readReportContent(', trim(url), ",", trim(name), ",", trim(path),",", trim(href), ",", trim(out), ')');
call execute(mcall);
run;
1 ACCEPTED SOLUTION

Accepted Solutions
Tom
Super User Tom
Super User

I do not see any place where you are trying to compile a macro definition inside another macro defintion. (Something you shouldn't do anyway as macros use a flat name space unlike macro variables where you can use LOCAL macro variables that only exist while a macro is running.)

 

But I suspect your issue is not understanding the scoping rules of MACRO VARIABLES.

 

I think you shared too much code as I am having a hard time locating which part of that long program you are having issue with.

Can you just show to part of the code that is trying to set macro variables named _MOD and _LAST?  

Remember if _MOD and _LAST do not exist and you run code to make them inside a macro, like this:

%macro mymac;
%let _mod=1;
%mend;
%mymac;
%put &=_mod ;

Then they will be defined as LOCAL to the macro and disappear when the macro finishes running.

164  %macro mymac;
165  %let _mod=1;
166  %mend;
167  %mymac;
168  %put &=_mod ;
WARNING: Apparent symbolic reference _MOD not resolved.
_mod

You can either define the macro variable BEFORE calling the macro so that the existing macro variable will be used by the %LET sstatement.

169  %let _mod=before macro call;
170  %put &=_mod;
_MOD=before macro call
171  %mymac;
172  %put &=_mod;
_MOD=1

Or make the macro smart enough the create a GLOBAL macro variable when there is no one already defined.

%macro mymac;
%if not %symexist(_mod) %then %global _mod;
%let _mod=1;
%mend;

View solution in original post

7 REPLIES 7
Tom
Super User Tom
Super User

I do not see any place where you are trying to compile a macro definition inside another macro defintion. (Something you shouldn't do anyway as macros use a flat name space unlike macro variables where you can use LOCAL macro variables that only exist while a macro is running.)

 

But I suspect your issue is not understanding the scoping rules of MACRO VARIABLES.

 

I think you shared too much code as I am having a hard time locating which part of that long program you are having issue with.

Can you just show to part of the code that is trying to set macro variables named _MOD and _LAST?  

Remember if _MOD and _LAST do not exist and you run code to make them inside a macro, like this:

%macro mymac;
%let _mod=1;
%mend;
%mymac;
%put &=_mod ;

Then they will be defined as LOCAL to the macro and disappear when the macro finishes running.

164  %macro mymac;
165  %let _mod=1;
166  %mend;
167  %mymac;
168  %put &=_mod ;
WARNING: Apparent symbolic reference _MOD not resolved.
_mod

You can either define the macro variable BEFORE calling the macro so that the existing macro variable will be used by the %LET sstatement.

169  %let _mod=before macro call;
170  %put &=_mod;
_MOD=before macro call
171  %mymac;
172  %put &=_mod;
_MOD=1

Or make the macro smart enough the create a GLOBAL macro variable when there is no one already defined.

%macro mymac;
%if not %symexist(_mod) %then %global _mod;
%let _mod=1;
%mend;
Quentin
Super User

Agree with Tom. I would focus on this macro from your example:

 

%macro get_bu_date(name);
proc sql;
select substr(fname, 1, prxmatch("m/(?<=_)(\d{2}.*)(?=\.json)/oi",fname)-2) as rep_name,  floor(max(input(substr(fname, prxmatch("m/(?<=_)(\d{2}.*)(?=\.json)/oi",fname), 18), anydtdtm.))) format=best12. into :nom, :_last 
from WORK.FILENAMES
where prxmatch("m/_&name/oi",fname)
group by 1;
quit;

data _null_;
set WORK.FILENAMES;
where prxmatch("m/_&name/oi",fname);
retain valor;
valor=max(valor, floor(max(input(substr(fname, prxmatch("m/(?<=_)(\d{2}.*)(?=\.json)/oi",fname), 18), anydtdtm.))));
call symputx('_last', valor);
run;
%put &_last.;
%mend;

If you run that macro, I assume the %PUT &_last statement inside the macro works and the macro variable resolves.

But if after running the macro, you submit  another %PUT  &_last statement= you probably get a message that &_last does not resolve.  This is because &_last was created as a local macro variable, and does not exist outside of the macro. To make it a global macro variable (which may not be the best approach, but it's an option), you could add a %global statement or tell CALL SYMPUTX to make it global:

call symputx('_last', valor,G);
The Boston Area SAS Users Group (BASUG) is hosting an in person Meeting & Training on June 27!
Full details and registration info at https://www.basug.org/events.
acordes
Rhodochrosite | Level 12

When I simplify the code it still keeps unresolving.

 

%macro get_bu_fecha(name);

data _null_;
set WORK.FILENAMES;
where prxmatch("m/_&name/oi",fname);
retain valor;
valor=max(valor, floor(max(input(substr(fname, prxmatch("m/(?<=_)(\d{2}.*)(?=\.json)/oi",fname), 18), anydtdtm.))));
call symputx('_last', valor, G);
run;
%put &_last.;
%mend;

%get_bu_fecha(S1_AEIT_LS01_ModelSelection);

%put &_last.;
80   %get_bu_fecha(S1_AEIT_LS01_ModelSelection);
NOTE: Numeric values have been converted to character values at the places given by: (Line):(Column).
80:234
NOTE: There were 1 observations read from the data set WORK.FILENAMES.
WHERE PRXMATCH('m/_S1_AEIT_LS01_ModelSelection/oi', fname);
NOTE: DATA statement used (Total process time):
real time 0.01 seconds
cpu time 0.00 seconds

1990008012
WARNING: Apparent symbolic reference _LAST not resolved.
81
82 %put &_last.;
&_last.
FreelanceReinh
Jade | Level 19

@acordes wrote:

When I simplify the code it still keeps unresolving.

This is because of the missing quotation marks in the third argument of the CALL SYMPUTX routine:

call symputx('_last', valor, 'G');

The note

NOTE: Numeric values have been converted to character values at the places given by: (Line):(Column).
      80:234   

in the log documents the conversion of the value of the (most likely uninitialized) numeric variable G to a (missing) character value probably like '           .' (a period, right-aligned in a 12-character string).

acordes
Rhodochrosite | Level 12

The first time I run the code I don't get a result and several alerts like:

 

WARNING: Apparent symbolic reference _LAST not resolved
WARNING: Apparent symbolic reference INF not resolved
WARNING: Apparent symbolic reference _MOD not resolved
WARNING: Apparent symbolic reference _LAST not resolved
WARNING: Apparent symbolic reference LASTED not resolved
WARNING: Apparent symbolic reference _MOD not resolved
WARNING: Apparent symbolic reference _LAST not resolved
WARNING: Apparent symbolic reference _MOD not resolved
WARNING: Apparent symbolic reference _LAST not resolved

The second time it produces the desired result writing an additional file to the target folder as the %sysevalf(_mod>_last, boolean) resolved to true. 

The third time I run this code it keeps writing to the destination folder but it shouldn't if it fetched correctly the &_mod and &_last. 

The fourth time onwards it stops creating additional files.  

 

So my second and fourth time are the correct ones. 

The macro variables resolve delayed or out of context. 

So the second file is created correctly, the third shouldn't be there. 

 

PIC.png

 

I added the global option the call symputx. 

acordes
Rhodochrosite | Level 12

I found the solution following on this blog post.

https://communities.sas.com/t5/SAS-Programming/Macro-variable-not-getting-resolved-when-use-Call-exe... 

PIC.png

And the final code is this one: 

 

cas mySession sessopts=(caslib=casuser timeout=1800 locale="en_US");
caslib _all_ assign;

/* --- Begin Edit --- */
%let BASE_URI=%sysfunc(getoption(servicesbaseurl));

%put &base_uri.;

%let baseurl=xxxxxxxxxxx;

/* %let ppath=/Users/xxxxx@comp.com/My Folder/in_pdf/; */
%let ppath=/Public/ODAP_REPORTS/out_json_backup/;

filename myfldr filesrvc folderPath="&ppath.";

data filenames;
length  fname $200;
did = dopen('myfldr');
do i = 1 to dnum(did);
fname = dread(did,i);
if index(lowcase(fname), 'json') then output;
end;
did = dclose(did);
keep fname;
run;
 
filename folders temp;
proc http
url = "https://xxxxxxxxx"
out= folders
oauth_bearer = sas_services;
headers
'Accept'= 'application/vnd.sas.collection+json';
run;
libname folders clear;
libname folders json;
 
%let hostname = xxxxxxxxxx;
%let endpoint = /folders/folders;
%let path = "/Public/ODAP_REPORTS" ;
%let output = "/out_json_backup";
%let currentDateTime = %sysfunc(datetime(), datetime20.);
%put &currentDateTime;
 
/**********************************************************/
/* Retrieve the ID of the folder where the report resides */
/**********************************************************/
filename folders clear;
filename folders temp;
 
proc http
url = "&hostname.&endpoint/@item"
query = ("path"=&path)
out= folders
oauth_bearer = sas_services;
headers
'Accept'= 'application/vnd.sas.content.folder+json';
run;
 
/* libname folders clear; */
libname folders json;
 
/*************************************************/
/* Get a list of objects in that specific folder */
/*************************************************/
/* Identify the endpoint to be used */
proc sql ;
select href, type into :endpoint, :type
from folders.links
where method="GET" and rel = "members";
quit;
 
/* Retrieve the list of objects */
/* filename memList clear; */
filename memList temp;
 
proc http
url = "&hostname.%trim(&endpoint)"
out= memList
oauth_bearer = sas_services;
headers
"Accept"= "%trim(&type)+json";
run;
 
/* libname memList clear; */
libname memList json;
 
/******************************************************/
/* Extract report content (structure) for each report */
/******************************************************/
/* Macro to write the output to a json file */
/* %sysmacdelete writeJson; */
%macro writeJson(name, path);
filename outcsv FILESRVC FOLDERPATH="&ppath."  FILENAME="_&name._&currentDateTime..json";
proc json out=outcsv nosastags pretty noscan   ;
export casuser.info;
run;
%mend;

/* Macro to read the report content and generate the output file */
/* %sysmacdelete readReportContent; */
%macro readReportContent (url, name, path, endpoint, output);
filename f FILESRVC FOLDERPATH="&path."  FILENAME="text.txt" lrecl=2000000;
/* filename f temp lrecl=2000000; */
 
proc http
url="&url.%trim(&endpoint)/content"
out=f
oauth_bearer=sas_services;
headers
"Accept"="application/vnd.sas.report.content+json";
quit;

%let n=60;

data casuser.info;
length report $128 path $1024 c1-c&n. varchar(32767) content varchar(2000000);
infile f lrecl=2000000 recfm=v  truncover;
input (c1-c&n.) ($32767.);

content=cats(of c1-c&n.);
array c[*] c1-c&n. content;
report="&name";
path="&path";
drop c2-c&n.;
run;

data _null_;
pid=prxparse('/(?<=datemodified":")(.{19})/io');
format want $26. fecha datetime19.;
set casuser.info;
string=compress(substr(content, 1, 1000));
/* string=symget('inf'); */
put string=;
s=1;e=length(string);
 call prxnext(pid,s,e,string,p,l);
 do while(p>0);
   	want=catx(' ',want,substr(string,p+1,l));
	want=tranwrd(want,'T', ' ');
	fecha=input(want, anydtdtm.);
    call prxnext(pid,s,e,string,p,l);
	put string= want= p= l= fecha=;
	if fecha then call symputx('_mod', fecha, 'G');
 end;

run;

data filenames;
length  fname $200;
did = dopen('myfldr');
do i = 1 to dnum(did);
fname = dread(did,i);
if index(lowcase(fname), 'json') then output;
end;
did = dclose(did);
keep fname;
run;

data _null_;
set WORK.FILENAMES;
where prxmatch("m/_&name/oi",fname);
retain valor;
valor=max(valor, floor(max(input(substr(fname, prxmatch("m/(?<=_)(\d{2}.*)(?=\.json)/oi",fname), 18), anydtdtm.))));
call symputx('_last', valor, 'G');
run;

%put &_mod. &_last. ;

%if %sysevalf(&_mod. > &_last.,boolean) %then %do;
%put "executed";
data _null_;
set casuser.info;
where path ne '';
location=tranwrd(path, "/", "_");
mcall=cat('%writeJson(', trim(report), ',', trim(location), ')');
call execute(mcall);
run;

%end;
 
%mend;
 

/* Generate a view containing information to call readReportContent */
proc sql;
create table merged as
select "&hostname" as url,
a.ordinal_items,
a. name,
a.uri,
b.href,
b.type ,
b.method
from memlist.items as a
left join memlist.items_links as b
on a.ordinal_items=b.ordinal_items
having a.contentType="report" and b.rel="getResource";
quit;

options mlogic;

data _null_ ;
set merged;
/* (obs=3 firstobs=3); */
out=&output;
path=&path;
  call execute(cats(
     '%nrstr(%%readReportContent)'
    ,'(url =',trim(url)
    ,',name =',trim(name)
    ,',path=',trim(path)
    ,',endpoint =',trim(href)
    ,',output=',trim(out)
    ,')'
  ));
run;
Tom
Super User Tom
Super User

There is no need for TRIM() if you are using CATS() function.  That already removes the trailing spaces from the values. It also removes the leading spaces.

data _null_ ;
  set merged;
/* (obs=3 firstobs=3); */
  out=&output;
  path=&path;
  call execute(cats(
     '%nrstr(%readReportContent)'
    ,'(url=',url
    ,',name=',name
    ,',path=',path
    ,',endpoint=',href
    ,',output=',out
    ,')'
  ));
run;

hackathon24-white-horiz.png

The 2025 SAS Hackathon Kicks Off on June 11!

Watch the live Hackathon Kickoff to get all the essential information about the SAS Hackathon—including how to join, how to participate, and expert tips for success.

YouTube LinkedIn

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 7 replies
  • 1763 views
  • 7 likes
  • 4 in conversation