Paging through rest api results? - sas

I am using the following code to pull a json file from a url :
options NOQUOTELENMAX;
filename usage "/folders/myfolders/sasuser.v94/usage.json";
%let AccessKey = reallylongstring;
proc http
url="https://a.url"
method="GET" out=usage;
headers
"Authorization"="Bearer &AccessKey.";
run;
libname usage json "/folders/myfolders/sasuser.v94/usage.json";
data usage;
set usage.data;
run;
proc print data=usage noobs;
run;
however now the results are returning more that 1000 results and i need to check for nextLink property somehow?
in .net I could use something like this:
$usagerest = Invoke-Restmethod -url $usageurl -header $authheaders -method get
while ($null -ne $usageRest.nextLink) {
$usageRest = Invoke-Restmethod -uri $usagerest.nextlink -headers $authheaders -method get
}
is something like this possible with proc http in sas?
heres a tree view of the actual json if of help?
so far i have tried a quick dirty version:
libname usage1 JSON fileref=resp1;
data x;
set usage1.root;
call symputx('nextLink',nextLink);
run;
proc http
url="%superq(nextLink)"
method="GET" out=resp2;
headers
"Authorization"="Bearer &AccessKey.";
run;
libname usage2 JSON fileref=resp2;
data y;
set usage2.root;
call symputx('nextLink',nextLink);
run;
proc http
url="%superq(nextLink)"
method="GET" out=resp3;
headers
"Authorization"="Bearer &AccessKey.";
run;
libname usage3 JSON fileref=resp3;
data z;
set usage3.root;
call symputx('nextLink',nextLink);
run;
Libraries overview:
usage1.data sample:
work.x sample:
Thanks

For the context of the resolved macro variable needing to be double quoted, such as url= option value, place the %superq inside double quotes -- "%superq(<macro-var-name>)"
Try
…
url = "%superq(nextLink)"
If the link continues to be troublesome you can try
url = "%qsysfunc(urlencode(%superq(nextLink)))"
Invoking a macro once per row in a control data set
There are numerous ways, such as
Stack the 1,000 invocations via data _null_; and call execute(…
Proc SQL select nextLink into :link1- to create 1,000 macro variables
Open the data set with %let ds=%sysfunc(open(… and loop over the 1,000 rows — %do %while (%sysfunc(fetch(&ds) = 0))
Sample code for second bullet item:
This sample passes a macro variable from the 'looping' macro to the 'worker' macro. When macro variable name is passed, the macro var does not have to be resolved and quoted for passage into the worker. Instead the work gets the macro var (aka symbol name) and has the superq resolve it in quoted manner for use in source code generation. Essentially, passing the macro var is akin to a traditional language's pass-by-reference concept.
data have;
do linknum = 1 to 25;
link = cats("place=", byte(64+linknum),'&extra="zoom=',linknum,'"&key=MYAPIKEY');
output;
end;
run;
%macro processLink(link_mvar=);
%put url="%sysfunc(urlencode(%superq(&link_mvar)))";
%mend;
%macro processLinks (data=);
proc sql;
reset noprint;
select link into :link1- from &data;
quit;
%local i;
%do i = 1 %to &sqlobs;
%* pass name of macro variable to macro;
%processLink (link_mvar=link&i);
%end;
%mend;
%processLinks(data=have)

The question seems clearer. The process can be macro-ized to repeatedly call Proc HTTP inside a %do loop. Each page of data can be appended to a data set that will grow with each page retrieved.
Untested
%macro getThatPagedJsonData (url=, accesskey=, out=);
%local page guard;
%let page = 1;
%Let guard = 50; * just in case - prevent infinite/excessive looping during development/testing;
filename response temp;
%do %until (%length(%superq(url)) eq 0 or &page > &guard);
* clear libname, releasing any locks on json repsonse file;
libname page;
* prior response will be over written;
proc http url="%superq(url)" method="GET" out=response;
headers "Authorization"="Bearer &AccessKey.";
run;
* magic json engine;
libname page JSON fileref=response;
if &page = 1 %then %do;
* first page starts the output data set;
data &out;
set page.data;
run;
%end;
%else %do;
* append subsequent pages of data;
proc append base=&out data=page.data;
run;
%end;
* track number of pages processed;
%let page = %eval (&page + 1);
* reset url for %until test;
%let url=;
* fetch the nextlink as the url for next iteration of %do %until;
* might need error handling here when last page has no nextlink;
data _null_;
set page.root;
call symput('url', trim(nextlink));
run;
%end;
%mend;
%getThatPagedJsonData (url=...., accesskey=...., out=serviceAggreements);

Related

How to trigger a macro for every data set containing a keyword

I'm doing a crash course on SAS macros and I'm stuck at one exercise. I have to create a macro, that will create a proc contents tables for every data set, that contains a keyword. I know how to do that using call execute, but I need this using proc sql and %do loop.
My attempt:
%macro contents(data=&syslast);
proc contents data=&data;
title "&data";
run;
%mend contents;
%macro ContentsAll(keyword);
select libname||'.'||memname
into :dsn1-
from sashelp.vstabvw
where upcase(memname) like %upcase("%quote(%)&&keyword%")
;
quit;
%do i=1 %to &sqlobs;
%contents(data=&&dsn&i);
%end;
%mend ContentsAll;
options mlogic mprint;
%ContentsAll(class);
options nomlogic nomprint;
I know there is some issue with a select statement, but I have no idea how to fix it. And where statement has an unprotected variable (my attempts at fixing it just break the where clause alltogether.
First of all, good job. It's so good that I'm almost sorry you're only missing the Proc SQL Statement :-)
%macro contents(data=&syslast);
proc contents data=&data;
title "&data";
run;
%mend contents;
%macro ContentsAll(keyword);
proc sql noprint;
select libname||'.'||memname
into :dsn1-
from sashelp.vstabvw
where upcase(memname) like %upcase("%quote(%)&&keyword%")
;
quit;
%do i=1 %to &sqlobs;
%contents(data=&&dsn&i);
%end;
%mend ContentsAll;
options mlogic mprint;
%ContentsAll(class);
options nomlogic nomprint;
There is no need to create all of those macro variables. Just keep the list of names in actual data. You can use CALL EXECUTE() to generate the code you want to run for each member in the list.
Note that the variables LIBNAME and MEMNAME will already be in uppercase when pulled from the DICTIONARY.MEMBERS metadata that the view SASHELP.VSTABVW uses. But the user passing in a value for the KEYWORD parameter might not have entered uppercase letters.
%macro ContentsAll(keyword);
data _null_;
set sashelp.vstabvw ;
where memname contains "%qupcase(&keyword)" ;
call execute(cats('%nrstr(%contents)(data=',libname,'.',memname,')'));
run;
%mend ContentsAll;

Derive unique libref and/or fileref in SAS

I often need to assign (and de-assign) temporary librefs/filerefs as part of utility macros - and thus I need to avoid naming conflicts with any existing librefs/filerefs. I know I could query the sashelp or dictionary tables and use conditional logic to iterate until I find one that isn't assigned, but I wondered if there was some easier way?
For instance, the following will create a uniquely named dataset in the work library:
data;run;
Is there some equivalent for librefs / filerefs?
The FILENAME() function already provides a method for this. When you call it with a missing value for the fileref it generates one using format #LNnnnnn.
6 data test;
7 length fileref $8 ;
8 rc=filename(fileref,,'temp');
9 put rc= fileref=;
10 run;
rc=0 fileref=#LN00056
NOTE: The data set WORK.TEST has 1 observations and 2 variables.
NOTE: DATA statement used (Total process time):
real time 0.02 seconds
cpu time 0.01 seconds
11 %global fileref ;
12 %let rc=%sysfunc(filename(fileref,,temp));
13 %put &=rc &=fileref ;
RC=0 FILEREF=#LN00058
The undocumented monotonic function, invoked in the open execution space, is very handy for obtaining an unused value.
libname mylib "C:\temp\sandbox";
data mylib.data%sysfunc(monotonic());
…
run;
Or code a macro to deliver a name for a libref. The macro can also check for existence if so desired:
%macro nextName(lib=,base=data,check=1);
%local index name;
%if %length(&lib) %then %let lib=&lib..;/* handle non-empty lib */
%do %until (&check and not %sysfunc(exist(&name,data)));
%let name = &lib.&base.%sysfunc(monotonic());
%end;
&name
%mend;
data data3;run;
data data4;run;
%put %nextName();
%put %nextName();
%put %nextName();
%put %nextName();
proc sort data=sashelp.class out=%nextname();
by age;
run;
You could go robust macro implementation and test for lib existence and valid check value.
The LIBNAME function has a similar feature as FILENAME but it does not populate the reference name variable as with FILENAME. The only way I can think of would be to compare SASHELP.VLIBNAM before and after. The librefs are in the form WC00000n.
79 data _null_;
80 length libref $32.;
81 libref = ' ';
82 rc = libname(libref,'.');
83 msg = sysmsg();
84 put _all_;
85 run;
libref= rc=-70004 msg=NOTE: Libref refers to the same physical library as WC000004. _ERROR_=0 _N_=1
actually writing one was fairly trivial, and didn't involve querying the (expensive) dictionary table:
libname mcore0 (work);
libname mcore1 (work);
libname mcore2 (work);
%macro mf_getuniquelibref(prefix=mcore,maxtries=1000);
%local x;
%let x=0;
%do x=0 %to &maxtries;
%if %sysfunc(libref(&prefix&x)) ne 0 %then %do;
%put Libref &prefix&x is available!;
&prefix&x
%return;
%end;
%end;
%put unable to find available libref in range &prefix.0-&maxtries;
%mend;
%let libref=%mf_getuniquelibref();
%put &=libref;
which returns;
UPDATE:
I've added macros for both of these to the MacroCore library and they can be utilised as follows:
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/macrocore.sas";
%inc mc;
%let libref=%mf_getuniquelibref();
%let fileref=%mf_getuniquefileref();

Execute proc step only if it doesn't cause log error (inside macro) SAS

I'm trying to test different covariance structures inside macro with Proc Mixed.
%macro cov(type);
proc mixed data=tmp order=data;
class sub trt visit;
model var = trt visit trt*visit / S cl;
repeated visit /subject=sub type=&type.;
FitStatistics=min_var_&type.;
run;
%mend;
Some of the covariance structures I need to fit in model causes errors and I'm trying to find a way to execute this proc mixed statement only, if it doesn't cause error with value of &type.
I have been working with %sysfunc and but haven't been able to resolve this yet.
%IF %SYSFUNC(EXIST(min_var_&type.)) %THEN %DO;
data help_&type.;
set min_var_&type.;
run;
%end;
This produces these datasets correctly, but still log errors exists in log for those macro variables that can not be fitted.
You can redirect the log to a file like that :
filename logfile "\\SERVER\LOG\mylog.log";
proc printto log=logfile new;
run;
And then when your PROC MIXED is finished, you can filter on the log file for the string "ERROR" :
....YOUR PROC MIXED...
/*come back to normal log*/
proc printto;
run;
/*check the log file*/
DATA CHECKLOG;
LENGTH ROWS $200;
LABEL ROWS = 'Messages from LOG';
INFILE "\\SERVER\LOG\mylog.log" TRUNCOVER;
INPUT ROWS &;
LINE = _N_;
IF SUBSTR(ROWS,1,5)='ERROR' /*OR SUBSTR(ROWS,1,7)='WARNING'*/ THEN
OUTPUT;
RUN;
You will have all the ERROR and (or WARNING if needed) in a dataset.
Then you have to check if the table is empty.
If YES, you can continue your script.
You can do it via this method
proc sql;
select * from checklog;
run;
%put n=&sqlobs;
If sqlobs is greater than 0, then you have errors.
You can check the sqlobs via a macro function like this :
%macro checklog;
proc sql;
select * from checklog;
run;
%if (&sqlobs>0) %then ...
%else ...
%mend checklog;

Get data filtered by dynamic column list in SAS stored process

My goal is to create a SAS stored process is to return data for a single dataset and to filter the columns in that dataset based on a multi-value input parameter passed into the stored process.
Is there a simple way to do this?
Is there a way to do this at all?
Here's what I have so far. I'm using a macro to dynamically generate the KEEP statement to define which columns to return. I've defined macro variables at the top to mimic what gets passed into the stored process when called through SAS BI Web Services, so unfortunately those have to remain as they are. That's why I've tried to use the VVALUEX method to turn the column name strings into variable names.
Note - I'm new to SAS
libname fetchlib meta library="lib01" metaserver="123.12.123.123"
password="password" port=1234
repname="myRepo" user="myUserName";
/* This data represents input parameters to stored process and
* is removed in the actual stored process*/
%let inccol0=3;
%let inccol='STREET';
%let inccol1='STREET';
%let inccol2='ADDRESS';
%let inccol3='POSTAL';
%let inccol_count=3;
%macro keepInputColumns;
%if &INCCOL_COUNT = 1 %then
&inccol;
%else
%do k=1 %to (&INCCOL_COUNT);
var&k = VVALUEX(&&inccol&k);
%end;
KEEP
%do k=1 %to (&INCCOL_COUNT);
var&k
%end;
;
%mend;
data test1;
SET fetchlib.Table1;
%keepInputColumns;
run;
/*I switch this output to _WEBOUT in the actual stored process*/
proc json out='C:\Logs\Log1.txt';
options firstobs=1 obs=10;
export test1 /nosastags;
run;
There are some problems with this. The ouput uses var1, var2 and var3 as the column names and not the actual column names. It also doesn't filter by any columns when I change the output to _webout and run it using BI Web Services.
OK, I think I have some understanding of what you're doing here.
You can use KEEP and RENAME in conjunction to get your variable names back.
KEEP
%do k=1 %to (&INCCOL_COUNT);
var&k
%end;
;
This has an equivalent
RENAME
%do k=1 %to (&INCCOL_COUNT);
var&k = &&inccol&k.
%end;
;
and now, as long as the user doesn't separately keep the original variables, you're okay. (If they do, then you will get a conflict and an error).
If this way doesn't work for your needs, and I don't have a solution for the _webout as I don't have a server to play with, you might consider trying this in a slightly different way.
proc format;
value agef
11-13 = '11-13'
14-16 = '14-16';
quit;
ods output report=mydata(drop=_BREAK_);
proc report data=sashelp.class nowd;
format age agef.;
columns name age;
run;
ods output close;
The first part is just a proc format to show that this grabs the formatted value not the underlying value. (I assume that's desired, as if it's not this is a LOT easier.)
Now you have the data in a dataset a bit more conveniently, I think, and can put it out to JSON however you want. In your example you'd do something like
ods output report=work.mydata(drop=_BREAK_);
proc report data=fetchlib.Table1 nowd;
columns
%do k=1 %to (&INCCOL_COUNT);
&&inccol&k.;
%end;
;
run;
ods output close;
And then you can send that dataset to JSON or whatever. It's actually possible that you might be able to go more directly than that even, but I don't know almost anything about PROC JSON.
Reading more about JSON, you may actually have an easier way to do this.
On the export line, you have the various format options. So, assuming we have a dataset that is just a subset of the original:
proc json out='C:\Logs\Log1.txt';
options firstobs=1 obs=10;
export fetchlib.Table1
(
%do k=1 %to (&INCCOL_COUNT);
&&inccol&k.;
%end;
)
/ nosastags FMTCHARACTER FMTDATETIME FMTNUMERIC ;
run;
This method doesn't allow for the variable order to be changed; if you need that, you can use an intermediate dataset:
data intermediate/view=intermediate;
set fetchlib.Table1;
retain
%do k=1 %to (&INCCOL_COUNT);
&&inccol&k.;
%end;
;
keep
%do k=1 %to (&INCCOL_COUNT);
&&inccol&k.;
%end;
;
run;
and then write that out. I'm just guessing that you can use a view in this context.
It turns out that the simplest way to implement this was to change the way that the columns (aka SAS variables) were passed into the stored process. Although Joe's answer was helpful, I ended up solving the problem by passing in the columns to the keep statement as a space-separated column list, which greatly simplified the SAS code because I didn't have to deal with a dynamic list of columns.
libname fetchlib meta library="lib01" metaserver="123.12.123.123"
password="password" port=1234
repname="myRepo" user="myUserName";"&repository" user="&user";
proc json out=_webout;
export fetchlib.&tablename(keep=&columns) /nosastags;
run;
Where &columns gets set to something like this:
Column1 Column2 Column3

List only the column names of a dataset

I am working on SAS in UNIX env and I want to view only the column name of a dataset. I have tried proc contents and proc print but both of them list a lot of other irrevelant information that I do not want as it fills up my putty screen and the information ultimately is lost.
I also tried to get this thing frm the sas metadata but that is not working either.
I tried :
2? proc sql;
select *
from dictionary.tables
where libname='test' and memname='sweden_elig_file_jul';
quit;
5?
NOTE: No rows were selected.
6?
NOTE: PROCEDURE SQL used (Total process time):
real time 0.27 seconds
cpu time 0.11 seconds
You're using the wrong dictionary table to get column names...
proc sql ;
select name
from dictionary.columns
where memname = 'mydata'
;
quit ;
Or using PROC CONTENTS
proc contents data=mydata out=meta (keep=NAME) ;
run ;
proc print data=meta ; run ;
Here's one I've used before to get a list of columns with a little bit more information, you can add the keep option as in the previous answer. This just demonstrates how to create a connection to the metadata server, in case that is useful to anyone viewing this post.
libname fetchlib meta
library="libraryName" metaserver="metaDataServerAddress"
password="yourPassword" port=1234
repname="yourRepositoryName" user="yourUserName";
proc contents data=fetchlib.YouDataSetName
memtype=DATA
out=outputDataSet
nodetails
noprint;
run;
For a pure macro approach, try the following:
%macro mf_getvarlist(libds
,dlm=%str( )
)/*/STORE SOURCE*/;
/* declare local vars */
%local outvar dsid nvars x rc dlm;
/* open dataset in macro */
%let dsid=%sysfunc(open(&libds));
%if &dsid %then %do;
%let nvars=%sysfunc(attrn(&dsid,NVARS));
%if &nvars>0 %then %do;
/* add first dataset variable to global macro variable */
%let outvar=%sysfunc(varname(&dsid,1));
/* add remaining variables with supplied delimeter */
%do x=2 %to &nvars;
%let outvar=&outvar.&dlm%sysfunc(varname(&dsid,&x));
%end;
%End;
%let rc=%sysfunc(close(&dsid));
%end;
%else %do;
%put unable to open &libds (rc=&dsid);
%let rc=%sysfunc(close(&dsid));
%end;
&outvar
%mend;
Usage:
%put List of Variables=%mf_getvarlist(sashelp.class);
Returns:
List of Variables=Name Sex Age Height Weight
source: https://github.com/sasjs/core/blob/main/base/mf_getvarlist.sas
proc sql;
select *
from dictionary.tables
where libname="TEST" and memname="SWEDEN_ELIG_FILE_JUL";
quit;