SAS: do not send email when no records - sas

I'm new to SAS base and I am trying to either add a line saying all ID's transferred when nobs=0 Here's what I added on what Alex gave me which print the ID's when there are any in the data set workgo.recds_not_processed. I added nobs=0 condition but it is not sending the ID's when they are present.
data _null_;
length id_list $ 3000;
retain id_list '';
file mymail;
SET set workgo.recds_not_processed nobs = nobs end = eof;
IF nobs = 0 then PUT "All ID's transferred successfully";
else if _n_ = 1 then do;
put 'Number of records not processed=' nobs;
put 'The IDs are:';
end;
/* Print the IDs in chunks */
if length(strip(id_list)) > 2000 then do;
put id_list;
call missing(id_list);
end;
call catx(', ', id_list, id);
if eof then put id_list;
run;

You are very nearly there - the unmatched end spotted by Joe and the double set that I pointed out were the only obvious errors preventing your code from working.
The reason you got no output when dealing with an empty dataset is that SAS terminates a data step when it tries to read a record from a set statement and there are none left to read. One solution is to move your empty dataset logic before the set statement.
Also, you can achieve the desired result without resorting to retain and call catx by using a double trailing ## in your put statement to write to the same line repeatedly from multiple observations from the input dataset:
data recds_not_processed;
do id=1 to 20;
output;
end;
run;
data _null_;
/*nobs is populated when the data step is compiled, so we can use it before the set statement first executes*/
IF nobs=0 then
do;
PUT "All IDs transferred successfully";
stop;
end;
/*We have to put the set statement after the no-records logic because SAS terminates the data step after trying to read a record when there are none remaining.*/
SET recds_not_processed nobs=nobs end=eof;
if _n_=1 then
do;
put 'Number of records not processed=' nobs;
put 'The IDs are:';
end;
/* Print the IDs in chunks */
if eof or mod(_N_, 5)=0 then
put id;
else
put id +(-1) ', ' ##;
run;

Related

SAS rename table with name with blanks

I have 2 tables:retail with my data and col_dic as a dictionary for column names. In col_dic there are 2 columns - eng_name and eng_name_bl.
So th code is:
data _null_;
set col_dic end = last;
if _n_ eq 1 then call execute('proc datasets nolist lib=work; modify retail; rename');
call execute(catx('=', eng_name,eng_name_bl));
if last then call execute(';quit;');
run;
After executing log gives a mistake, where it wants '=' after blank in new column name.
How can i avoid it?
Example that does work:
data col_dic;
length eng_name eng_name_bl $20;
eng_name = 'AGE';
eng_name_bl = 'AGE_FIX';
output;
eng_name = 'HEIGHT';
eng_name_bl = 'HEIGHT_FIX';
output;
run;
data class;
set sashelp.class;
run;
data _null_;
set col_dic end = last;
if _n_ eq 1 then call execute('proc datasets nolist lib=work; modify class; rename');
call execute(catx('=', eng_name,eng_name_bl));
if last then call execute(';quit;');
run;
First of all don't do this. Variable names with spaces in them are pain in the neck. Why don't you just use the value with spaces in it as the LABEL instead of the NAME?
If you do want to specify a variable name that contains spaces then you need make sure to set
option validvarname=any;
Then in the code generation step use the NLITERAL() function to convert the string with spaces to a valid SAS name literal to avoid the syntax errors.
call execute(catx('=', nliteral(eng_name),nliteral(eng_name_bl)));

SAS Macro using multiple lists for conditions

I have a condition table that I want to use to create my other tables. Now I want my code to go through my condition table taking into consideration both condition. My data looks as follow.
Month Date1 Date2 Line
Jan2010 01Jan2010 31Jan2010 PL
Feb2010 01Feb2010 28Feb2010 CB
Feb2010 01Feb2010 28Feb2010 HB
Mar2010 01Mar2010 31Mar2010 PL
Current Code
%Macro Split_Data(Month,BeginDate,EndDate,Line);
Data Want_&Month._&Line.;
Set Have;
Where Date > &BeginDate and Date <&EndDate and Line = '&Line.';
Quit;
%Mend;
%Split_Data(Jan2010,'01JAN2010'd,'31JAN2010'd,PL);
%Split_Data(Jan2010,'01JAN2010'd,'31JAN2010'd,CB);
I don't want to list the macro like this. I would much rather have a table the macro calls up and condition on. Is this possible? How can I create a less manual method. So I can update my condition table without having to update my SAS code.
I think the dates could complicate things. That being said, you can pass the SAS date values across without quotation marks and your macro would still work. If you have other area's that use the date you would have to confirm that it met your requirements. I would usually use a data _null_ step but I saved it to a data set WANT here so you can see the string, if desired.
data want;
set have;
str = catt('%split_data(', put(month, yymon7.), ",", date1, ",", date2, ",", line, ");");
call execute(str);
run;
Use your metadata to generate the code you need.
First set some macro variables (or make a macro with parameters).
%let inds=HAVE;
%let base=WANT ;
%let metadata=METADATA;
Then use the metadata to generate a single data step to write all of the output datasets.
filename code temp;
data _null_;
set &metadata end=eof;
file code ;
if _n_=1 then put 'DATA';
dsname= catx('_',symget('base'),month,line);
put #2 dsname ;
if eof then put ';' / #2 "set &inds;" ;
run;
data _null_;
set &metadata end=eof;
file code mod;
dsname= catx('_',symget('base'),month,line);
put #2 'IF date > "' date1 date9. '"d and date < "' date2 date9. '"d and line=' line :$quote.
'then output ' dsname ';'
;
if eof then put 'run;' ;
run;
Then include the generated code to run it.
%inc code / source2 ;
So for your example the generated code would look like this:
DATA
WANT_Jan2010_PL
WANT_Feb2010_CB
WANT_Feb2010_HB
WANT_Mar2010_PL
;
set HAVE;
IF date > "01JAN2010"d and date < "31JAN2010"d and line="PL" then output WANT_Jan2010_PL ;
IF date > "01FEB2010"d and date < "28FEB2010"d and line="CB" then output WANT_Feb2010_CB ;
IF date > "01FEB2010"d and date < "28FEB2010"d and line="HB" then output WANT_Feb2010_HB ;
IF date > "01MAR2010"d and date < "31MAR2010"d and line="PL" then output WANT_Mar2010_PL ;
run;

SAS Export data to create standard and comma-delimited raw data files

i m new to sas and studying different ways to do subject line task.
Here is two ways i knew at the moment
Method1: file statement in data step
*DATA _NULL_ / FILE / PUT ;
data _null_;
set engappeal;
file 'C:\Users\1502911\Desktop\exportdata.txt' dlm=',';
put id $ name $ semester scoreEng;
run;
Method2: Proc Export
proc export
data = engappeal
outfile = 'C:\Users\1502911\Desktop\exportdata2.txt'
dbms = dlm;
delimiter = ',';
run;
Question:
1, Is there any alternative way to export raw data files
2, Is it possible to export the header also using the data step method 1
You can also make use of ODS
ods listing file="C:\Users\1502911\Desktop\exportdata3.txt";
proc print data=engappeal noobs;
run;
ods listing close;
You need to use the DSD option on the FILE statement to make sure that delimiters are properly quoted and missing values are not represented by spaces. Make sure you set your record length long enough, including delimiters and inserted quotes. Don't worry about setting it too long as the lines are variable length.
You can use CALL VNEXT to find and output the names. The LINK statement is so the loop is later in the data step to prevent __NAME__ from being included in the (_ALL_) variable list.
data _null_;
set sashelp.class ;
file 'class.csv' dsd dlm=',' lrecl=1000000 ;
if _n_ eq 1 then link names;
put (_all_) (:);
return;
names:
length __name__ $32;
do while(1);
call vnext(__name__);
if upcase(__name__) eq '__NAME__' then leave;
put __name__ #;
end;
put;
return;
run;

SAS: Drop column in a if statement

I have a dataset called have with one entry with multiple variables that look like this:
message reference time qty price
x 101 35000 100 .
the above dataset changes every time in a loop where message can be ="A". If the message="X" then this means to remove 100 qty from the MASTER set where the reference number equals the reference number in the MASTER database. The price=. is because it is already in the MASTER database under reference=101. The MASTER database aggregates all the available orders at some price with quantity available. If in the next loop message="A" then the have dataset would look like this:
message reference time qty price
A 102 35010 150 500
then this mean to add a new reference number to the MASTER database. In other words, to append the line to the MASTER.
I have the following code in my loop to update the quantity in my MASTER database when there is a message X:
data b.master;
modify b.master have(where=(message="X")) updatemode=nomissingcheck;
by order_reference_number;
if _iorc_ = %sysrc(_SOK) then do;
replace;
end;
else if _iorc_ = %sysrc(_DSENMR) then do;
output;
_error_ = 0;
end;
else if _iorc_ = %sysrc(_DSEMTR) then do;
_error_ = 0;
end;
else if _iorc_ = %sysrc(_DSENOM) then do;
_error_ = 0;
end;
run;
I use the replace to update the quantity. But since my entry for price=. when message is X, the above code sets the price='.' where reference=101 in the MASTER via the replace statement...which I don't want. Hence, I prefer to delete the price column is message=X in the have dataset. But I don't want to delete column price when message=A since I use this code
proc append base=MASTER data=have(where=(msg_type="A")) force;
run;
Hence, I have this code price to my Modify statement:
data have(drop=price_alt);
set have; if message="X" then do;
output;end;
else do; /*I WANT TO MAKE NO CHANGE*/
end;run;
but it doesn't do what I want. If the message is not equal X then I don't want to drop the column. If it is equal X, I want to drop the column. How can I adapt the code above to make it work?
Its a bit of a strange request to be honest, such that it raises questions about whether what you're doing is the best way of doing it. However, in the spirit of answering the question...
The answer by DomPazz gives the option of splitting the data into two possible sets, but if you want code down the line to always refer to a specific data set, this creates its own complications.
You also can't, in the one data step, tell SAS to output to the "same" data set where one instance has a column and one instance doesn't. So what you'd like, therefor, is for the code itself to be dynamic, so that the data step that exists is either one that does drop the column, or one that does not drop the column, depending on whether message=x. The answer to this, dynamic code, like many things in SAS, resolves to the creative use of macros. And it looks something like this:
/* Just making your input data set */
data have;
message='x';
time=35000;
qty=1000;
price=10.05;
price_alt=10.6;
run;
/* Writing the macro */
%macro solution;
%local id rc1 rc2;
%let id=%sysfunc(open(work.have));
%syscall set(id);
%let rc1=%sysfunc(fetchobs(&id, 1));
%let rc2=%sysfunc(close(&id));
%IF &message=x %THEN %DO;
data have(drop=price_alt);
set have;
run;
%END;
%ELSE %DO;
data have;
set have;
run;
%END;
%mend solution;
/* Running the macro */
%solution;
Try this:
data outX(drop=price_alt) outNoX;
set have;
if message = "X" then
output outX;
else
output outNoX;
run;
As #sasfrog says in the comments, a table either has a column or it does not. If you want to subset things where MESSAGE="X" then you can use something like this to create 2 data sets.

Is there a way to detect when you've reached the last observation in a SAS DATA step?

Is there a way to check how many observations are in a SAS data set at runtime OR to detect when you've reached the last observation in a DATA step?
I can't seem to find anything on the web for this seemingly simple problem. Thanks!
The nobs= option to a set statement can give you the number of observations. When the data step is compiled, the header portion of the input datasets are scanned, so you don't even have to execute the set statement in order to get the number of observations. For instance, the following reports 2 as expected:
/* a test data set with two observations and no vars */
data two;
output;
output;
run;
data _null_;
if 0 then set two nobs=nobs;
put nobs=;
run;
/* on log
nobs=2
*/
The end= option sets a flag when the last observation (for the set statement) is read in.
A SAS data set, however, can be a SAS data file or a SAS view. In the case of the latter, the number of observations may not be known either at compile time or at execution time.
data subclass/view=subclass;
set sashelp.class;
where sex = symget("sex");
run;
%let sex=F;
data girls;
set subclass end=end nobs=nobs;
put name= nobs= end=;
run;
/* on log
Name=Alice nobs=9.0071993E15 end=0
Name=Barbara nobs=9.0071993E15 end=0
Name=Carol nobs=9.0071993E15 end=0
Name=Jane nobs=9.0071993E15 end=0
Name=Janet nobs=9.0071993E15 end=0
Name=Joyce nobs=9.0071993E15 end=0
Name=Judy nobs=9.0071993E15 end=0
Name=Louise nobs=9.0071993E15 end=0
Name=Mary nobs=9.0071993E15 end=1
*/
You can also use %sysfunc(attrn( dataset, nlobs)) though it is limited to SAS data sets (i.e. not data views). Credit for the macro to this SUGI paper, which also give great information regarding good macro design.
You can get all sorts of other character and numeric information on a SAS data set.
See the documentation on attrn and attrc.
%macro numobs (data=&syslast ) ;
/* --------------------------------------------
Return number of obs as a function
--------------------------------------------
*/
%local dsid nobs rc;
%let data = &data ; /* force evaluation of &SYSLAST */
%let dsid=%sysfunc(open(&data));
%if &dsid > 0 %then
%do ;
%let nobs=%sysfunc(attrn(&dsid,nlobs));
%let rc=%sysfunc(close(&dsid));
%end ;
%else
%let nobs = -1 ;
&nobs
%mend numobs;
Find the number of observations in a SAS data set:
proc sql noprint;
select count(*) into: nobs
from sashelp.class
;
quit;
data _null_;
put "&nobs";
run;
The SQL portion counts the number of observaions, and stores the number in a macro variable called "nobs".
The data step puts the number for display, but you can use the macro variable like any other.
Performing a certain action when the last observation is processed:
data _null_;
set sashelp.class end=eof;
if eof then do;
put name= _n_=;
end;
run;
The "end" option to the "set" statement defines a variable (here "eof" for end-of-file) that is set to 1 when the last observation is processed. You can then test the value of the variable, and perform actions when its value is 1. For more info, see the documentation for the "set" statement.
data hold;
set input_data end=last;
.
.
.
if last then do;
.
.
.
end;
run;