I'm trying to call a MACRO on SAS CI (using a Node Process) but I'm receiving a error message (1012 or 3000) when I tried to execute. This code is working perfect on SAS Enterprise Guide.
I tried remove the "call execute" syntax, but I did not success.
DATA _NULL_ ;
SET SMS_TEMP END = EOF ;
IF _N_ = 1 THEN DO UNTIL (EOF);
%build_JSON;
END ;
PUT 'Complete';
STOP ;
RUN ;
Code full:
DATA _NULL_ ;
SET SMS_TEMP END = EOF ;
IF _N_ = 1 THEN DO UNTIL (EOF);
CALL EXECUTE('%build_JSON');
END ;
PUT 'FINAL';
STOP ;
RUN ;
%macro build_JSON;
FILENAME CODE TEMP;
DATA _NULL_;
SET SMS_TEMP;
FILE CODE ;
PUT
'WRITE VALUES "TP_SMS" ' CODSMS :$QUOTE. ';'
/ 'WRITE VALUES "NM_REMETENTESMS" ' REMETENTE :$QUOTE. ';'
/ 'WRITE VALUES "NR_TELEFONECELULARSMS" ' MOBILE :$QUOTE. ';'
/ 'WRITE VALUES "TX_MENSAGEMSMS" ' MSGTEXT :$QUOTE. ';'
/ 'WRITE VALUES "DT_PARAENVIOSMS" ' DATAPARA :$QUOTE. ';'
/ 'WRITE VALUES "DT_LIMITEENVIOSMS" ' DATALIMI :$QUOTE. ';'
/ 'WRITE VALUES "DS_CHAVEORIGEMSMS" ' RESP :$QUOTE. ';'
;
RUN;
PROC JSON OUT="%SYSFUNC(GETOPTION(WORK))/TEST.JSON" PRETTY KEYS NOSASTAGS;
WRITE OPEN OBJECT;
%INCLUDE CODE;
WRITE CLOSE;
RUN;
FILENAME CODE CLEAR;
%MEND build_JSON;
I expect to working with macros on SAS CI (using SAS Code on Node Process).
How can I do it?
I'll try to explain exactly what I needed: I have a dataset that I need create a JSON file for each line from my dataset. After I'll call another macro with a API process. My loop should be: Read (1), Create out JSON File (2), execute the API Macro (3). Exclude the file out (json), and find for the next register;
When calling a macro from CALL EXECUTE it will immediately execute up to a point where code gen will occur. This can be confusing to novice users of the feature and has side effects discussed in documentation and conference papers.
To explicitly stack macro invocation statements so they only occur after the step completes, the EXECUTE should use an argument that %NRSTR wraps the macro invocation.
call execute ('%NRSTR(the source code that invokes the macro)');
First make sure you define the macro before you call it.
Second your macro is just doing the same thing every time so there is no sense it calling multiple times.
So why have a macro at all? Why not just call the code that the macro is generating?
Or perhaps you want to define your macro to take an input parameter? Such as the name of the dataset to convert to JSON? If so where is the list of dataset going to come from?
Note that your first step cannot work since your macro generates multiple steps. The first PROC or DATA statement generated will end your data step in the middle of definition of the DO/END block so it will fail since SAS will never see a closing END for the opening DO.
I did some adjusts in my code:
DATA _NULL_ ;
IF _N_ = 1 THEN DO UNTIL (EOF);
SET SMS_TEMP END = EOF ;
PUT 'Number line:' NumbLINE;
CALL SYMPUTX('NRL',NumbLINE);
CALL SYMPUTX('RESPTRACKING',RESP);
CALL EXECUTE('%BUILD_JSON');
/*CALL EXECUTE('%CALLAPI'); --- In dev*/
/*CALL EXECUTE('%DELETEFILE'); --- In dev*/
END ;
ELSE DO;
PUT 'FINISH';
END;
RUN ;
This code above is responsible to call all my MACROS one by one until end of file.
%MACRO BUILD_JSON;
FILENAME CODE TEMP;
DATA _NULL_;
SET SMS_TEMP;
WHERE NROLINHA EQ &NRL.;
FILE CODE ;
PUT
'WRITE VALUES "PITP_SMS" ' CODSMS :$QUOTE. ';'
/ 'WRITE VALUES "PINM_REMETENTESMS" ' REMETENTE :$QUOTE. ';'
/ 'WRITE VALUES "PINR_TELEFONECELULARSMS" ' MOBILE :$QUOTE. ';'
/ 'WRITE VALUES "PITX_MENSAGEMSMS" ' MSGTEXT :$QUOTE. ';'
/ 'WRITE VALUES "PIDT_PARAENVIOSMS" ' DATAPARA :$QUOTE. ';'
/ 'WRITE VALUES "PIDT_LIMITEENVIOSMS" ' DATALIMI :$QUOTE. ';'
/ 'WRITE VALUES "PIDS_CHAVEORIGEMSMS" ' RESP :$QUOTE. ';'
;
RUN;
PROC JSON OUT="%SYSFUNC(GETOPTION(WORK))/TEST.JSON" PRETTY KEYS NOSASTAGS;
WRITE OPEN OBJECT;
%INCLUDE CODE;
WRITE CLOSE;
RUN;
FILENAME CODE CLEAR;
%MEND BUILD_JSON;
I expect create a routine to Create a JSON file ->then-> Call API Macro to send this JSON ->then-> Exclude this File ->then-> Repeat. For each line of my dataset I'll call this looping process and it seems working as well at least on SAS Enterprise Guide.
This problem occurs just when I tried execute this code on SAS Customer Intelligence (CI) trought a process node.
Related
I want to conditionally execute macro function dependent on existence of table
Data
data have;
do i = 1 to 5;
output;
end;
run;
Table I want to condition on
data counttbl;
infile datalines delimiter='::';
format variable $char400. condition $char400.;
input variable $ condition $;
datalines;
runcount::i>1
;
run;
Some tests to show that I can condition on counttbl existence (works as expected)
data _null_;
call execute("data want;");
call execute("set have;");
if exist("work.counttbl") then
call execute("tmp = 1;");
call execute('run;');
stop;
run;
The above creates column tmp = 1
proc delete data=work.counttbl;
run;
data _null_;
call execute("data want;");
call execute("set have;");
if exist("work.counttbl") then
call execute("tmp = 1;");
call execute('run;');
stop;
run;
After deleting the table, the above does not create the column tmp
Macro function to execute
%macro apply_change();
call execute('if _n_ eq 1 then do;');
do until (eof);
set counttbl (keep=variable) end=eof;
call execute(strip(variable) || ' = 0;');
end;
call execute('end;');
call missing(eof);
do until (eof);
set counttbl end=eof;
call execute(strip(variable) || ' + (' || strip(condition) || ');');
end;
call missing(eof);
%mend apply_change;
Works fine when counttbl exists
data _null_;
call execute("data want;");
call execute("set have;");
if exist("work.counttbl") then
%apply_change()
call execute('run;');
stop;
run;
Throws error when counttbl is deleted - I want it to simply skip executing the macro function
proc delete data=work.counttbl;
run;
data _null_;
call execute("data want;");
call execute("set have;");
if exist("work.counttbl") then
%apply_change()
call execute('run;');
stop;
run;
ERROR: File WORK.COUNTTBL.DATA does not exist.
Thanks in advance for your help
Your problem area is
if exist("work.counttbl") then
%apply_change()
Macro is processed, and generates source code, prior to the SAS system implicitly compiling the data step and running it.
I would not recommend pursuing this avenue because it mixes scopes (macro/data step)
If you persist, there are a couple of tips
place all the code generation in a macro
place the existential check in the macro, and do NOT codegen source that has a SET counttbl when not present
For example:
%macro apply_change();
%if not %sysfunc(EXISTS(WORK.COUNTTBL)) %then %return;
%* This code gen only done when COUNTTBL present;
call execute('if _n_ eq 1 then do;');
do until (eof);
set counttbl (keep=variable) end=eof;
call execute(strip(variable) || ' = 0;');
end;
call execute('end;');
call missing(eof);
do until (eof);
set counttbl end=eof;
call execute(strip(variable) || ' + (' || strip(condition) || ');');
end;
call missing(eof);
%mend apply_change;
Replace
if exist("work.counttbl") then %apply_change()
With
%apply_change()
First
if exist("work.counttbl") then
will only apply to the first line of your macro
call execute('if _n_ eq 1 then do;');
This is because the macro is evaluated before the datastep is executed. So sas will simple paste the macro content to the location of the macro invocation.
But even when it would apply to the whole macro it will not work.
Take for example the following code:
data x;
if 0 then do;
set y;
end;
set z;
run;
Her y and z has to exist. However no observation will be read from y only structure is taken.
You cannot use a test at run time to prevent SAS from compiling some lines of code in the same data step. Instead you need to use macro logic to not generate the lines of code.
It looks like you want to use the dataset to generate a series of variables that count how many times a condition is met. I find that it is much easier to debug if that type of data driven code generation is done by writing the code to a file. Then you can stop after generating the file and look at the generated code and make sure your code generation step is working properly.
It looks like you want the new dataset generated whether or not the file with the list of VARIABLE/CONDITION pairs exists or not. So just hard code that part of the data step and only conditionally generate the part that calculates the new variables. Since you are generating sum statements there is not need for the IF _N_=1 block to set the initial values to zero. SAS will automatically set them to zero and retain them. (Assuming that HAVE doesn't already have variables with those names, in which the sum statement won't work right either.)
filename code temp;
data _null_;
file code ;
%if %sysfunc(exist(&dsname)) %then %do;
set &dsname end=eof;
put ' ' variable '+ ( ' condition ');' ;
%end ;
run;
So either the temp file CODE is empty or it has code like:
VAR1 + ( dx='123' );
VAR2 + ( sex='M' );
Then to make your dataset just run this step with a %INCLUDE to add in the conditionally generated code.
data want;
set have;
%include code /source2;
run;
If you are using an old version of SAS you will need to wrap that %IF statement into a macro. But the newest releases of SAS allow that type of simple %IF/%THEN/%DO/%END construct in open code.
I have some problems when I'm trying to generate a JSON file from dataset on SAS GUIDE. I generated a TEST.JSON:
{"TP_SMS":"1" "NM_REMETENTESMS":"00000159"},
{"TP_SMS":"2" "NM_REMETENTESMS":"00000159"},
{"TP_SMS":"3" "NM_REMETENTESMS":"00000159"},
{"TP_SMS":"4" "NM_REMETENTESMS":"00000159"},
{"TP_SMS":"5" "NM_REMETENTESMS":"00000159"},
.
.
.
{"TP_SMS":"9" "NM_REMETENTESMS":"00000159"},
The field TP_SMS is filled correct, but the second field is wrong - they are considering just the last position from my table.
Below there is my code white macro:
data teste30;
set MATABLES.EXIT_DATA;
RESP=cat(CD_CLIENTE,"|",ANWER_DATA);
ID=_N_;
call symputx('ID',ID);
call symputx('CD_CLIENTE',CD_CLIENTE);
call symputx('NM_PRIMNOMECLIENTE',NM_PRIMNOMECLIENTE);
call symputx('RESP',RESP);
call symputx('msgtext',msgtext);
run;
%macro MontaJSON(ID);
WRITE OPEN OBJECT;
WRITE VALUES "TP_SMS" "&ID";
WRITE VALUES "NM_REMETENTESMS" "&CD_CLIENTE";
WRITE CLOSE;
%mend MontaJSON(ID);
%macro SMSRecords;
%do i = 1 %to &dim_IDs;
%MontaJSON(&&&ID_&i);
%end;
%mend SMSRecords;
proc sql;
select id, CD_CLIENTE into :ID_1 - :ID_&SysMaxLong from work.teste30;
%let dim_IDs = &sqlObs;
quit;
proc json out="C:\TEMP\TEST.json" pretty nokeys nosastags;
write open array; /* container for all the data */
%SMSRecords;
write close; /* container for all the data */
run;
I expect this macro get all datas on sequence, as TP_SMS code:
{"TP_SMS":"1" "NM_REMETENTESMS":"00014578"},
{"TP_SMS":"2" "NM_REMETENTESMS":"21323445"},
{"TP_SMS":"3" "NM_REMETENTESMS":"23456753"},
{"TP_SMS":"4" "NM_REMETENTESMS":"00457663"},
{"TP_SMS":"5" "NM_REMETENTESMS":"00014795"},
{"TP_SMS":"6" "NM_REMETENTESMS":"00014566"},
{"TP_SMS":"7" "NM_REMETENTESMS":"00014578"},
{"TP_SMS":"8" "NM_REMETENTESMS":"00000122"},
{"TP_SMS":"9" "NM_REMETENTESMS":"00000159"}
Does anyone has some idea to solve it?
Tks
I would avoid generating all of those macro variables that are confusing your code.
Instead for that simple format you can just write the file directly instead of using PROC JSON.
data _null_;
set MATABLES.EXIT_DATA end=eof;
file "C:\TEMP\TEST.json" ;
if _n_=1 then put '[';
if not eof then delim=',';
put '{"TP_SMS":' id :$quote. ' "NM_REMETENTESMS":' CD_CLIENTE :$quote. '}' delim ;
if eof then put ']';
run;
Or if you really find that PROC JSON helps then use a similar data step to write the lines of code and use %INCLUDE to run the generated code.
filename code temp;
data _null_;
set MATABLES.EXIT_DATA ;
file code ;
put 'WRITE OPEN OBJECT;'
/ 'WRITE VALUES "TP_SMS" ' ID :$quote. ';'
/ 'WRITE VALUES "NM_REMETENTESMS" ' CD_CLIENTE :$quote. ';'
/ 'WRITE CLOSE;'
;
run;
proc json out="C:\TEMP\TEST.json" pretty nokeys nosastags;
write open array; /* container for all the data */
%include code;
write close; /* container for all the data */
run;
This line is your problem, it will only hold the last data point.
call symputx('CD_CLIENTE',CD_CLIENTE);
Instead, create a value for each ID, similar to how you created the IDs.
call symputx(catx('_', 'CD_CLIENTE', put(i, 8.-l)), CD_CLIENTE);
Then use it later on as &&&CD_CLIENTE&i
So I've done some searching around online but haven't managed to find anything that can solve this problem. Essentially, I have been given a dataset that I've then split into individual dataset's based on name.
However, if the person is a female, the age needs to be omitted from the dataset. Example output:
Males
Name Age Weight Height
Females
Name Weight Height
I have tried the following IF statement, but it just seems to drop the age variable from both the male and female tables:
if sex="F" then do;
drop age;
end;
I'm fairly new to SAS so any help would be greatly appreciated!
When you run a data step in SAS, some statements are processed during compilation, and others subsequently during execution. In this case, the drop statement is processed before your if-then logic, so you can't use it to conditionally drop a column.
Alternatively, you could output a missing value for age for each affected row, e.g.
if sex = 'F' then call missing(age);
Or you could use a drop clause on one output dataset but not the other:
data boys girls(drop=age);
set sashelp.class;
if sex = 'F' then output girls;
else if sex = 'M' then output boys;
run;
The DROP statement cannot be run conditionally. You need to conditionally generate the DROP statement (or DROP= dataset option).
To use a trivial example dataset let's start with SASHELP.CLASS and split it into individual datasets. Note that this dataset only has one observation per NAME, but I will add BY group processing to the code generation step so you can see how you could use it in the case where there are multiple observations per name.
First let's generate code for single DATA statement that creates multiple output datasets. Based on the value of the SEX variable it will conditionally add a DROP= dataset option.
filename code temp;
data _null_;
set sashelp.class end=eof ;
by name ;
file code ;
if _n_=1 then put 'data' ;
if first.name then do;
put ' ' name # ;
if sex='F' then put '(drop=age)' # ;
put ;
end;
if eof then put ';' ;
run;
Now let's append the code for the rest of the data step that will read the source dataset and output the records to the appropriate dataset.
data _null_;
set sashelp.class end=eof ;
by name ;
file code mod ;
if _n_=1 then put ' set sashelp.class; ' ;
if first.name then put ' if name =' name $quote. 'then output ' name ';' ;
if eof then put 'run;' ;
run;
Finally run the generated code.
%include code / source2 ;
Although I have the error handling:
filename myfile email
to=&e_mail.
subject= "Error: &number."
type="text/plain";
%macro errmail;
%if &syserr ne 0 %then %do;
options obs= max replace nosyntaxcheck;
data _null_;
file myfile;
put;
put 'ERROR';
put "&syserrortext";
put 'check a log'
run;
%abort cancel;
%end;
%mend errmail;
when I have the error in the proc export:
(&number. is the table in the work)
proc export data=&number.
outfile="/usr/local/backup/&number./&number._%SYSFUNC(TODAY(),DATE9.).txt"
replace
dbms=dlm;
delimiter=';';
run;
%errmail;
ERROR: Physical file does not exist, /usr/local/backup/2116/2116_13MAY2016.txt.
NOTE: The SAS System stopped processing this step because of errors.
NOTE: There were 1 observations read from the data set WORK.2116.
NOTE: DATA statement used (Total process time):
real time 0.01 seconds
cpu time 0.01 seconds
SAS dosen't check a macro and doesn't send an e-mail. I have an error in the path, so I understand the error, but I need an e-mail about that and I want that SAS stops processing because of macro.
Adding some options to the macro can fix it?
this is how I send out my email from SAS
not required to create a file on the system
filename outbox email "email#address.com";
data _null_;
file outbox
to=("email#address.com")
cc=("email#address.com")
subject="SUBJECT OF EMAIL "
/* attach=("C:\sas\results.out" "C:\sas\code.sas") */ ; /* incase you need to attach a file */
put 'Hello! ';
put ' ';
put 'Thank you & let me know if there is anything,';
put "¯ovar."; /*in case you need to put some detail in the email from macro variables make sure to use double quote for that */
put 'SIGNATURE ME';
run;
Here is the macro code.....
libname myfmt "&FBRMrootPath./Formats";
%macro CreateFormat(DSN,Label,Start,fmtname,type);
options mprint mlogic symbolgen;
%If &type='n' %then %do;
proc sort data=&DSN out=Out; by &Label;
Run;
Data ctrl;
set Out(rename=(&Label=label &Start=start )) end=last;
retain fmtname &fmtname type &type;
output;
If last then do;
hlo='O';
label='*ERROR';
output;
End;
Run;
%End;
%Else %do;
proc sort data=&DSN out=Out; by &Start;
Run;
Data ctrl;
set Out(rename=(&Start=label &Label=start )) end=last;
retain fmtname &fmtname type &type;
output;
If last then do;
hlo='O';
label='*ERROR';
output;
End;
Run;
%End;
proc format library=myfmt cntlin=ctrl;
Run;
%Mend CreateFormat;
Here is the code for control data set through which above macro should run for each observation of the data set and the values of the observations are inputs for varibales in the macro....
Data OPER.format_control;
Input DSN :$12. Label :$15. Start :$15. fmtName :$8. type :$1. fmt_Startdt :mmddyy. fmt_Enddt :mmddyy.;
format fmt_Startdt fmt_Enddt date9.;
Datalines;
ssin.prd prd_nm prd_id mealnm n . 12/31/9999
ssin.prd prd_id prd_nm mealid c . 12/31/9999
ssin.fac fac_nm onesrc_fac_id fac1SRnm n . 12/31/9999
ssin.fac fac_nm D3_fac_id facD3nm n . 12/31/9999
ssin.fac onesrc_fac_id D3_fac_id facD31SR n . 12/31/9999
oper.wrkgrp wrkgrp_nm wrkgrp_id grpnm n . 12/31/9999
;
Something like this.
proc sql;
select catx(',',cats('%CreateFormat(',DSN),Label,Start,fmtname,cats(type,')');
into :formcreatelist separated by ' '
from oper.format_control;
quit;
You may need to PUT some of your variables to get the format you want into the macro variable. I use the slightly cludgy cats/catx combo here, you could cats once with ',' added in a bunch of times also.
You do have a limit here - around 20,000 characters total in a macro variable. If it's over that, you either have to use CALL EXECUTE (which has some quirky features) or you can put the macro call into a text file and %INCLUDE it.
There is a better way to do this rather than select ... into a macro variable. Use a temp file like this:
filename dyncode temp;
data _null_;
file dyncode;
set OPER.format_control;
put '%createformat ....';
run;
%include dyncode;
filename dyncode clear;
This technique is not limited by the 32k length limitation on macro variables.
Note that you should definitely use single quotes around the %createformat to prevent SAS from invoking the macro just prior to data step compilation. You want the macro to run when the %include runs.
The above approach is analogous to call execute, but call execute is evil because it does not execute the macro and embedded data/proc code within the macro in the expected order. Avoid call execute.
Finally, if you are running interactive SAS and using the technique there is a neat trick you can use to debug. Comment out the last two lines of code -- the include and the filename clear. After you run the remaining code, enter the SAS command "fslist dyncode" in the command window. This will pop up a notepad view on the dynamic code you just generated. You can review it and make sure you got what you intended.
Here's a call execute solution, just for completeness:
data _null_;
set OPER.format_control;
call execute('%CreateFormat(' || DSN || ',' || Label || ',' || Start || ',' || fmtname || ',' || type || ');');
run;