CACHE access method - sas

I'm debugging a Stored Process Web Application that writes a PDF to _webout.
I'd like to avoid the following ERROR when running in a workspace session:
ERROR: Function is only valid for filerefs using the CACHE access method.
I've tried filename _webout temp and filename _webout dummy, to no avail.
Any tips from the field?

Have you tried
filename _webout cache;
That seems to work in my version, but I have no idea where the output goes.

An alternative is to check the engine type of the _webout fileref and avoid using the header functions if the xengine is STREAM.
This macro serves: https://core.sasjs.io/mf__getxengine_8sas.html
Usage:
%if %mf_getxengine(_WEBOUT)=STREAM %then %do;
/* set header function */
%end;

Related

How to overcome variable length limitation of 32767 characters, when handling output from a function in a SAS data step?

I want to put a html-generating macro function inside a html document and write the html document to _webout via a data step that uses resolve, see below pseudocode for details. However when the output of the html_generating_macro function exceeds 32767 characters, it gets truncated. As I understand it, the limitation is within the datastep variable text. How can I overcome this limitation and get the output of resolve (and the html_generating_macro) to _webout?
data _null_;
file _webout;
length text $32767;
retain text;
infile index.html flowover end=last;
input;
text = resolve(_infile_);
put text;
run;
%macro html_generating_macro();
%do i=1 %to 10000;
some html code
%end;
%mend;
<html>
<body>
%html_generating_macro
</body>
</html>
As Tom says in comments, PROC STREAM is the gold standard here, and largely does what you're doing.
If you're not able to use PROC STREAM, then you can break it up. What you'd want to do is write your html generating macro in such a way that it wrote to _WEBOUT itself, rather than returning text, or failing that, so that it was self-aware of length and returned 32000 bytes at a time.
This latter is a common way APIs work; for example, an API I use returns only 100 records at a time, but I call it for 2000 or so. What they do typically is return the first 100 records, and either a URL for the next 100 or a token that is sort of a bookmark for where they left off.
So you'd write something like...
%macro html_generating_macro(startat=0);
%global returnparm;
%if &startat ne 0 %then %do;
(code to skip to startat)
%end;
... stuff ...
%if &calculated_length_var gt 32000 %then %do;
%let returnparm = %eval(&startat. + 32000); *or, if it has to cut off at particular spots, calculate how much you are sending;
%end;
%else %do;
%let returnparm = 0;
%end;
%mend html_generating_macro;
Something like that, though the 'skip to startat' and the bit at the end is a bit more complicated, depending on what you're actually doing. Main idea is to have the ability to start at an arbitrary spot, and the ability to know when you are at a particular length and stop there, returning the spot you're at. Then the next call returns that to be used as the next startat value.
This might work for a very basic page, but proc stream is subpar if you're looking to build a more complex application.
If you really must embed HTML content in SAS code, far better to compile it outside SAS and keep the web content fully separated from your SAS programs in source. Compiling the frontend in this way is a concept we term "streaming apps".
A demonstration of this is available here: https://sasapps.io/sas-streamed-apps
Disclaimer - my team built the (open source) framework that makes this approach possible.
More examples of streaming apps here: https://sasjs.io/apps/

SAS Rename file on SFTP

my task in SAS is to upload a file via SFTP with extension tmp and after the upload is finished to rename it to csv. As I am no adminstrator of the server my debugging output is limited and I am struggeling with the correct implementation. The following code generates no error in the SAS Log but does not rename the file:
%let host=...;
%let sftpUser = ...;
%let filename_tmp=20160301-test01-sas.tmp;
%let filename=20160301-test01-sas.csv;
%let sftpPath=...;
FILENAME test SFTP "&sftpPath.&filename_tmp."
HOST="&host."
USER="&sftpUser."
DEBUG;
proc export data=.... outfile=test dbms=csv replace;
run;
data _null_;
rc=rename("test&sftpPath.&filename_tmp.", "test&sftpPath.&filename.", 'file');
run;
I already read the docs for filename and rename but I could not get a clue on how to combine both statements - any help or hints or alternatives are greatly appreciated.
Thanks
Stephan
There is a script running on the destination server scanning for csv files and move them every minute so there is the danger that a file is moved when the upload is not completed.
I'm pretty sure that there's no danger of the file being moved, as it should be locked until the upload completes. Just try it.

Ending a SAS-Stored process properly

I have a SAS-Storedprocess which does some html-output via webout.
Under some circumstances (e.G.: no data available) i want to put a custom error message and stop the process from further execution.
I have a makro-solution which had worked fine so far:
%if &varnobs = 0 %then %do;
data _null_;
file _webout;
put " &text1";
put " &text2";
put " &text3";
run;
ENDSAS;
%end;
But now i was informed that the use of ENDSAS is not allowed within our company because it can have various side-effects and also can stop not only the process, but also the complete session.
Now i am looking for alternatives, i had also tried the abort statement with several options, but one problem there was, that abort puts an error message in the log, so that not my custom message is shown, but a sas error message. Also in the abort documentation is stated (abort), that abort not only stops the process, but also the session.
I know there are programatically alternatives like if-else or goto instead of stopping the process, but that is not an option for my specific problem.
So the question is:
How to stop a stored process during execution, without stopping the session, without other side effects and without an SAS error-message?
Indeed, endsas; can cause unrecoverable issues in some SAS environments, particularly 9.4m3
For that reason in the SASjs Core library we had to resort to an 'out of the box' method for preventing further execution
We call it "skippy" - simply open a macro (or two) and don't close them!
filename skip temp;
data _null_;
file skip;
put '%macro skip(); %macro skippy();';
run;
%inc skip;
This is the full macro we use in SASjs: https://github.com/sasjs/core/blob/main/base/mp_abort.sas
Have you tried turning setting NOERRORABEND? Might be a possible option.
ERRORABEND / NOERRORABBEND
Turn off ERRORABEND for an optional, potentially unstable part of a
SAS
program
This is an interesting, tricky area. In some settings, setting system options:
options obs=0 noreplace;
might work as proxy.
you can do if else approach to prevent extra code from running and print a custom message error as
%if &data ne "" %then;
*your code;
%else
data _null_;
file _webout;
put 'html code of custom message';
run;
SAS is horrible at error handling so my suggestion is to avoid errors whenever possible ;-)
Jokes aside, yeah, there's limited options aside from endsas and %abort cancel;
You could try moving all code after the check into an %include statement that gets conditionally executed. Something like:
%macro conditional_execute;
%if &some_condition %then %do;
%include "rest_of_logic_to_perform.sas";
%end;
%else %do;
%put ERROR: CUSTOM ERROR MESSAGE HERE;
%end;
%mend;
The reason for an include rather than putting all the code inside of the macro is that I'm assuming there's a substantial amount of processing still to be performed. If it is a short amount of code then yes you could just put it within the %if condition.

Importing single value from a CSV file not working in SAS

I'm trying to use a Macro that retrieves a single value from a CSV file. I've written a MACRO that works perfectly fine if there is only 1 CSV file, but does not deliver the expected results when I have to run it against more than one file. If there is more than one file it returns the value of the last file in each iteration.
%macro reporting_import( full_file_route );
%PUT The Source file route is: &full_file_route;
%PUT ##############################################################;
PROC IMPORT datafile = "&full_file_route"
out = file_indicator_tmp
dbms = csv
replace;
datarow = 3;
RUN;
data file_indicator_tmp (KEEP= lbl);
set file_indicator_tmp;
if _N_ = 1;
lbl = "_410 - ACCOUNTS"n;
run;
proc sql noprint ;
select lbl
into :file_indicator
from file_indicator_tmp;
quit;
%PUT The Source Reporting period states: &file_indicator;
%PUT ##############################################################;
%mend;
This is where I execute the Macro. Each excel file's full route exists as a seperate record in a dataset called "HELPERS.RAW_WAITLIST".
data _NULL_;
set HELPERS.RAW_WAITLIST;
call execute('%reporting_import('||filename||')');
run;
In the one example I just ran, The one file contains 01-JUN-2015 and the other 02-JUN-2015. But what the code returns in the LOG file is:
The Source file route is: <route...>\FOO1.csv
##############################################################
The Source Reporting period states: Reporting Date:02-JUN-2015
##############################################################
The Source file route is: <route...>\FOO2.csv
##############################################################
The Source Reporting period states: Reporting Date:02-JUN-2015
##############################################################
Does anybody understand why this is happening? Or is there perhaps a better way to solve this?
UPDATE:
If I remove the code from the MACRO and run it manually for each input file, It works perfectly. So it must have something to do with the MACRO overwriting values.
CALL EXECUTE has tricky timing issues. When it invokes a macro, if that macro generates macro variables from data set variables, it's a good idea to wrap the macro call in %NRSTR(). That way call execute generates the macro call, but doesn't actually execute the macro. So try changing your call execute statement to:
call execute('%nrstr(%%)reporting_import('||filename||')');
I posted a much longer explanation here.
I'm not too clear on the connections between your files. But instead of importing the CSV files and then searching for your string, couldn't you use a pipe command to save the results of a grep search on your CSV files to a dataset and then read just in the results?
Update:
I tried replicating your issue locally and it works for me if I set file_indicator with a call symput as below instead of your into :file_indicator:
data file_indicator_tmp (KEEP= lbl);
set file_indicator_tmp;
if _N_ = 1;
lbl = "_410 - ACCOUNTS"n;
data _null_ ;
set file_indicator_tmp ;
if _n_=1 then call symput('file_indicator',lbl) ;
run;

Determine the folder of a SAS source file

When I open a SAS file in enterprise guide and run it, it is executed on the server. The source file itself is located either on the production site or the development site. In both cases, it is executed the same server however. I want to be able to tell my script to store results in a relative folder. But if I write something like
libname lib_out xport "..\tmp\foobar.xpt";
I get an error, because the working folder of the SAS Enterprise Guide process is not the location of my source file, but a folder on the server. And the folder ..\tmp does not exist there. Even if it would, the server process does not have write permission in that folder.
I would like to determine from which folder the .sas file was loaded and set the working folder accordingly. In one case it's S:\Development\myproject\sas\foobar.sas and in the other case it's S:\Production\myproject\sas\foobar.sas
It this possible at all? Or how would you do this?
Depending on the way EG is configured, you may be able to use something like the syshostname global macro variable to determine where to save your results:
%macro sasdir;
%global sasdir;
%if "&syshostname" eq "mydevelopmenthost" %then %do;
%let sasdir = S:\Development;
%end;
%else %if "&syshostname" eq "myproductionhost" %then %do;
%let sasdir = S:\Production;
%end;
%mend;
%sasdir;
libname lib_out xport "&sasdir\myproject\sas\tmp\foobar.xpt";
If not, try looking at what other global or automatic macro variables may be able to help you by doing a:
%put _all_;
Hope this helps
Cheers
Rob
OK, this isn't going to answer your question exactly, but I have this macro easily available so I thought I would share it. From here you would just need to do a little string processing.
%macro progName;
%* Returns the name of current program;
%let progPath = %sysfunc(GetOption(SysIn));
%* if running in interactive mode, the above line will not work, and the next line should;
%if %length(&progPath) = 0 %then %let progPath = %sysget(SAS_ExecFilePath);
%str(&progPath)
%mend progName;