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.
Related
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/
As part of my work, I am going through this SAS code. I have never worked on SAS before. Could anybody explain the usage of '%' in front of the SAS lines in the code below?
%if &i>0 %then %do;
or
%put ##### calling formula;
%formula();
% signs indicate macros or macro code. Macros in SAS are similar to functions in other programming languages, but not quite. You can treat them like functions though. They deal exclusively with text and only text.
The macro facility exists to handle more generalized problems. Things in SAS are done in PROCs and the DATA Step. Each PROC and DATA Step is like its own little self-contained environment - stuff that happens in there stays in there. The Macro facility gives you tools to do things like conditionally call PROCs, the DATA Step, or system options.
I highly recommend taking one of the free training courses on SAS programming. If you want to get a jump start on Macro language, start with this free course on Coursera.
To help get you started, here's what this code is doing, line-by-line.
%if &i>0 %then %do;
If the macro variable, i, is greater than 1, run some code. All macro variables start with &. The value of the macro variable i can be reviewed by typing %put &i.
%put ##### calling formula;
%put writes a line to the log in open code. This is likely being used to help debug things.
%formula();
This is a macro function that holds some code and runs it. It has no arguments. Macro functions are created with the following syntax:
%macro myMacro();
<macro or SAS code here>;
%mend;
% is the key symbol to invoke a macro. If we wanted to invoke myMacro, we can do so by prefixing it with a %:
%myMacro;
I came across SAS code recently that looks something like this:
%if var_name ~= %then %do;
flag = 1;
%end;
I understand that ~= means "not equal", but there appears to be nothing here for the variable to be compared to. Can someone shed any light on this syntax?
I've ruled out the possibility that this is shorthand for identifying missing observations: the flag is generated regardless of whether var_name contains any missing observations. That being said, it does the exact same thing as the code that you would think would actually do this:
%if var_name ~= . %then %do;
flag = 1;
%end;
The above also generates a flag with value 1 for all observations.
Any help on this greatly appreciated as I am quite new to SAS!
Bestimate: The macro expression is emitting flag=1; as an unconditional DATA step statement .
"Coming across SAS code" can be anywhere on the continuum of a singularly rewarding experience to a journey into a deep dark place.
The snippet
%if var_name ~= . %then %do;
flag = 1;
%end;
is construct consistent with someone who is learning macro and does not yet grok the scopes and environments within a SAS session. Macro variables and statements do not mingle with running data step variables and statements. Macro programming typically controls what is eventually seen as the DATA or PROC step source code that needs to be run.
There can be legitimate reasons for the snippet and therein starts your journey.
%IF expression %THEN statement; involves the resolution of a macro logical expression.
The expression is implicitly resolved and evaluated to be zero (false) or non-zero (true). Expressions that can not be resolved down to a non-missing numeric value at macro evaluation time will log an ERROR:
NOTE: Macro evaluation time is long gone by the time the SAS executor has compiled and is executing the DATA Step. SAS Documentation is pretty awesome, use it!.
Your var_name ~= expression is always true.
%put NOTE: %nrstr(%eval(var_name ~=)) resolves to %eval(var_name ~=);
----
NOTE: %eval(var_name ~=) resolves to 1
Because the %IF expression always resolves to true the %THEN statement is always resolved and emitted as source code to be consumed by the SAS executor.
So in your case the source code flag = 1; is emitted, ostensibly as part of a DATA step in which the flag assignment is unconditional.
Many times the statement is another macro expression that does not emit anything and instead performs an action that affects the macro state at the current macro scope -- For example %IF &variable=&target %THEN %let target_met=1;.
The statements around the one you noticed are really clues to whether the %IF is correct and what it should be. What could it be?
Does the data set be processed actually have a column named var_name ?Maybe you are dealing with metadata output by Proc CONTENTS, SQL DICTIONARY.COLUMNS or working in a framework that uses control data for generating statements.
A goofup wherein the %if - %then should really be a data step if -then and the var_name should have been replaced with an actual variable name found in the data set being processed.
Working in a code generating framework where non-empty symbols representing data step variables are used to generate data step if-then statements
The code is the work of a madman, mad genius, or village idiot.
Happy coding!
I have several programs in one SAS project, i.e program A -> program B ->.... I want to build dependency between programs using macro variables.
Program A will process few data step and procs. If ANY procedure in program A executes with errors, I would like to run program C. Otherwise run program B.
This seems to be tricky since syserr resets at each step boundary. If first data step in program A executes with error and the rest don't, then at the end of program A, syserr is still 0. I need macro variable value to be something other than 0 once error happens and the value can keep till program ends.
If the program dependency is based on other criteria (say values), the user-defined macro variables can handle that. For something related to system errors, I think SAS already has something can do the trick.But I can't find anything else except syserr, which doesn't seem to help.
Note: I find this SAS stop on first error. But basically it's to check the error status after each data step. That sounds crazy if program A contains 50+ data steps.
Easy - just use syscc!
SYSCC is a read/write automatic macro variable that enables you to
reset the job condition code and to recover from conditions that
prevent subsequent steps from running.
See documentation, but I guess you will be looking for something like:
%if &syscc > 4 %then %do;
%inc "/mypath/pgmB.sas";
%end;
%else %do;
%inc "/mypath/pgmA.sas";
%end;
The highest value of syscc is retained across step boundaries, always with an integer to represent the error level. Example values:
The values for SYSCC are:
0 is no errors no warnings
4 is warnings
greater than 4 means an error occurred
Note that there are some things it won't catch, but to improve it's effectiveness you can use:
options errorcheck=strict;
Finally - you mention 'sas project', if by this you mean you are using Enterprise Guide then please be aware of the advice in this usage note.
You could define a macro that kept track of the error status and run this after each step. The macro would look something like this:
%macro track_err;
%global err_status;
%let err_status = %sysfunc(max(&err_status, &syserr ne 0));
%mend;
Usage example below. First initialize the value to track the overall error status. The first datastep will fail, the second will run successfully and the final value of err_status will be 1.
%let err_status = 0;
data oops;
set sashelp.doesnt_exist;
run;
%track_err;
data yay;
set sashelp.class;
run;
%track_err;
%put &=err_status;
Final output:
ERR_STATUS=1
As far as 'sounding crazy to check the status after each step'... well SAS doesn't provide something for the exact requirement you have, so the only way to do it is literally to have something check after each step.
EDIT: Correction - looks like the syscc approach mentioned in RawFocus's answer actually shows there IS something that does this in SAS.
If you want the check to 'blend in' more with the code, then consider replacing your run and quit statements with a macro that performs the run/quit, then checks the status all in one. It will result in slightly cleaner code. Something like this:
%macro run_quit_track;
run;quit;
%global err_status;
%let err_status = %sysfunc(max(&err_status, &syserr ne 0));
%mend;
%let err_status = 0;
data oops;
set sashelp.doesnt_exist;
%run_quit_track;
data yay;
set sashelp.class;
%run_quit_track;
%put &=err_status;
Use one macro, call it runquitA. call this macro at the end of each proc sql or proc data in the place of quit; and run;
Example:
/*Program A*/
%macro runquitA;
; run; quit;
%if &syserr. ne 0 %then %do;
/*Call Program C*/
%end;
%mend runquitA;
proc sql;
create table class1 as
select * from sashelp.class;
%runquitA;
data class2;
set sashelp.class;
%runquitA;
/*Call Program B*/
/*end of Program A*/
I want to create a user written transformation to skip further job execution when certain condition is true.
I have tried the code
%abort;
%abort cancel;
but these statements gives error, something like Stopped processing because of %abort statement.
I don't want an error message to be displayed, just skip the remaining job execution. e.g. If my source table has zero observations then get out of the job without logging error message or warning.
Hmm, not sure if this will work in SAS DI (I don't have it to test), but what we use is the below macro:
%macro stop_sas;
%if "&sysenv" eq "FORE" %then %do;
%abort cancel;
%end;
%else %do;
endsas;
%end;
%mend;
It basically checks to see if SAS is running as a batch job or not, and if it is, quits SAS quietly. If SAS is running in interactive mode, then it will just abort the submitted code, without closing the IDE.
The key statement here is the endsas command - which is probably the part you are looking for.