If statement in SAS macro not always working - if-statement

I'm relatively new to SAS coding, and I'm trying to write this (see the whole code down below) macro that should evaluate performances (to_worse, to_better, recoveded, etc.) of several teams.
The problem is that this rule here:
if team_&y.="&PROVIDER." and team_&i. eq "" and balance_&i. = 0 then do; regolarizzato = "regolarizzato"; end;
else if team_&i. ne team_&y. and team_&y. eq "&PROVIDER." and team_&i. ne "" then do; to_worse="to_worse"; end;
else if team_&i. = team_&y. and team_&y. eq "&PROVIDER." and balance_&y. > balance_&i. then do; to_better = "to_better"; end;
does not seem to be always properly wowking; it works most of the times, but not always, and I don't understand the reason why it's failing sometimes. Could any kindheardted fella please explain why this happens? Thank you so much in advance!
%let oggi = '07oct2022'd;
%let mese = %sysfunc (MONTH(&oggi.));
%IF &mese. < 10 %THEN %do; %let mese = 0&mese.; %end;
%let giorno = %sysfunc (DAY(&oggi.));
%let anno = %sysfunc(Year(&oggi.));
/*%IF &giorno. < 10 %THEN %do; %let giorno = 0&giorno.; %end;*/
%macro COSTI_1;
%let i = 0;
%DO i = 0 %TO &giorno.;
data COSTI_&i.;
set data.initial_db_&mese.;
format balance_&i. commax14.2;
keep contract_number team_&i. balance_&i.;
run;
%end;
%mend;
%COSTI_1;
data COSTI_db;
set COSTI_0;
run;
%macro COSTI_2;
%let i = 1;
%DO i = 1 %TO &giorno.;
PROC SQL;
CREATE TABLE COSTI_db AS
SELECT
A.*,
B.*
FROM COSTI_db AS A LEFT JOIN COSTI_&i. AS B
ON (A.contract_number = B.contract_number);
QUIT;
run;
%end;
%mend;
%COSTI_2;
data COSTI_db;
set COSTI_db;
length team $ 20;
format team $CHAR30.;
team="altro";
run;
%MACRO COSTI_PROVIDER (PROVIDER);
data COSTI_db_&Provider.;
set COSTI_db;
run;
%macro COSTI_A;
%let i = 0;
%DO i = 0 %TO &giorno.;
data COSTI_db_&Provider.;
set COSTI_db_&Provider.;
if team_&i. = "&PROVIDER." then team = "&PROVIDER.";
run;
%end;
%mend;
%COSTI_A;
DATA COSTI_&PROVIDER.;
set COSTI_db_&Provider. (where =(team="&PROVIDER."));
length to_worse $ 20;
format to_worse $CHAR30.;
length to_better $ 20;
format to_better $CHAR30.;
length regolarizzato $ 20;
format regolarizzato $CHAR30.;
to_worse="no";
to_better="no";
regolarizzato="no";
run;
%macro to_worse;
%let i = 1;
%let y = %eval(&i.-1);
%DO i = 1 %TO &giorno.;
data COSTI_&PROVIDER.;
set COSTI_&PROVIDER.;
if team_&y.="&PROVIDER." and team_&i. eq "" and balance_&i. = 0 then do; regolarizzato = "regolarizzato"; end;
else if team_&i. ne team_&y. and team_&y. eq "&PROVIDER." and team_&i. ne "" then do; to_worse="to_worse"; end;
else if team_&i. = team_&y. and team_&y. eq "&PROVIDER." and balance_&y. > balance_&i. then do; to_better = "to_better"; end;
run;
%end;
%mend;
%to_worse;
data COSTI_&PROVIDER.;
set COSTI_&PROVIDER.;
length esito_finale $ 20;
format esito_finale $CHAR30.;
format balance_affido commax12.2;
if to_worse="to_worse" then esito_finale="to_worse";
else if regolarizzato = "regolarizzato" then esito_finale="regolarizzato";
else if to_better = "to_better" then esito_finale = "to_better";
else if team_&giorno. = "&PROVIDER." then esito_finale = "in_gestione_oggi";
if richiamo_o_repo = "&PROVIDER." and inflows < -1 then esito_finale = "richiamo";
if richiamo_o_repo = "&PROVIDER." and to_normal > 1 then esito_finale = "repo";
if team_0 = "&PROVIDER." then balance_affido = balance_0;
else balance_affido = -1;
drop INFLOWS TO_NORMAL RICHIAMO_O_REPO;
run;
%macro COSTI_B;
%let i = 0;
%DO i = 1 %TO &giorno.;
data COSTI_&PROVIDER.;
set COSTI_&PROVIDER.;
if team_&i. = "&PROVIDER." and balance_affido = -1 then balance_affido=balance_&i.;
run;
%end;
%mend;
%COSTI_B;
proc sql;
create table RIEPILOGO_&PROVIDER.
as select esito_finale, sum(balance_affido) as somma_balance_affido
from COSTI_&PROVIDER.
group by esito_finale;
quit;
data RIEPILOGO_&PROVIDER.;
set RIEPILOGO_&PROVIDER.;
format somma_balance_affido commax12.2;
run;
%MEND;
%COSTI_PROVIDER(TEAM_A);
%COSTI_PROVIDER(TEAM_B);
%COSTI_PROVIDER(TEAM_C);
%COSTI_PROVIDER(TEAM_D);

The macro that is generating that IF statement seems to have few issues.
First the macro variable Y is always going to be 0 since it is set to %eval(1-1). Did you intend Y to always be one less than I? If so move that %LET statement inside the %DO loop.
Second you keep reading and writing the same dataset over and over. You should probably move the %DO loop so that it can all be done with one data step.
%macro to_worse;
%local i y ;
data COSTI_&PROVIDER.;
set COSTI_&PROVIDER.;
%do i = 1 %TO &giorno.;
%let y = %eval(&i.-1);
if team_&i. eq "" and team_&y. eq "&PROVIDER." and balance_&i. = 0 then regolarizzato = "regolarizzato";
else if team_&i. ne team_&y. and team_&y. eq "&PROVIDER." and team_&i. ne "" then to_worse="to_worse";
else if team_&i. eq team_&y. and team_&y. eq "&PROVIDER." and balance_&y. > balance_&i. then to_better = "to_better";
%end;
run;
%mend to_worse;
And finally your set of IF/THEN/ELSE conditions do not cover all of the possible combinations of values of TEAM_&I, TEAM_&Y, BALANCE_&I and BALANCE_&Y. Are you sure that the situations where you think it is not doing what you want are not just the situations that fall into one of those uncovered combinations?

Related

How can I transform this code into macro?

So I'd like to do a macro code mixed with proc sql and data step. I have the following code in SAS:
data work.calendar;
set work.calendar;
if business_day_count^=-1 then do;
num_seq + 1;
drop num_seq;
business_day_count = num_seq;
end;
else
business_day_count = -1;
run;
I'd like to put it into macro code, but it doesn't work.
My macro code:
%macro1();
data work.job_calendar;
set work.job_calendar;
%if business_day_count^=-1 %then %do;
num_seq + 1;
drop num_seq;
business_day_count = num_seq;
%end;
else
business_day_count = -1;
run;
%mend;
The whole code is:
%macro update_day(date);
proc sql;
update work.job_calendar
set business_day_count =
case when datepart(calendar_date) = "&date"d then -1
else business_day_count
end;
quit;
proc sql;
update work.job_calendar
set status_ind =
case when business_day_count = -1 then 'N'
else status_ind
end;
quit;
proc sql;
update work.job_calendar
set rundate_ind =
case when business_day_count = -1 then 'N'
else status_ind
end;
quit;
proc sql;
update work.job_calendar
set daily_rundate_ind =
case when business_day_count = -1 then 'N'
else status_ind
end;
quit;
proc sql;
update work.job_calendar
set weekly_rundate_ind =
case when business_day_count = -1 then 'N'
else status_ind
end;
quit;
proc sql;
update work.job_calendar
set monthly_rundate_ind =
case when business_day_count = -1 then 'N'
else status_ind
end;
quit;
data work.job_calendar;
set work.job_calendar;
if business_day_count^=-1 then do;
num_seq + 1;
drop num_seq;
business_day_count = num_seq;
end;
else
business_day_count = -1;
%mend;
The error code is: ERROR 180 - 322 Statement is not valid or it is used out of proper order. I don't know what I'm doing wrong.
You're mixing data step and macro code. Not sure what you're trying to achieve so no idea on what to propose but removing the %IF/%THEN will allow your code to work.
%macro1();
data work.job_calendar;
set work.job_calendar;
if business_day_count^=-1 then do;
num_seq + 1;
drop num_seq;
business_day_count = num_seq;
end;
else
business_day_count = -1;
run;
%mend;
%macro1;
Here is a tutorial on converting working code to a macro and one on overall macro programming.
To define a macro you need to use the %MACRO statement. Also why did you change lines 3 and 7 from data statements to macro statements?
That code cannot work. First the %IF is always true since the string business_day_count is never going to match the string -1. Second you have an else statement without any previous if statement.
Try something like this instead.
%macro macro1();
data work.job_calendar;
set work.job_calendar;
if business_day_count^=-1 then do;
num_seq + 1;
drop num_seq;
business_day_count = num_seq;
end;
else business_day_count = -1;
run;
%mend macro1;

Write out variables in a loop to a dataset

I want to assign a string to a variable in a loop and write out the variable to a dataset on each iteration.
Here is the code that prints out each variable
%macro t_size(inlib=,inds=);
%let one_gig = 5000;
proc sql noprint;
select ceil((nobs*obslen)/&one_gig) into :tsize
from sashelp.vtable where libname=upcase("&inlib") and memname=upcase("&inds");
quit;
%let no_of_tables=%eval(%sysfunc(int(&tsize)));
%if (&tsize gt 1) %then
%do i = 1 %to &no_of_tables;
%put &inds._&i.;
%end;
%else
%do;
%put &inds.;
%end;
%mend;
%t_size(inlib=SASHELP,inds=SHOES);
run;
This produces the required output:
SHOES_1
SHOES_2
SHOES_3
SHOES_4
SHOES_5
SHOES_6
SHOES_7
Instead of printing the variables out to the log I want to write them to a new, empty dataset.
It appears you are attempting to split a data set FOO into N one_gig pieces FOO_1 to FOO_N. Your first step also appears to be creating the FOO target table names. Computing the split names within a DATA step will save the computed names.
Example:
%macro make_split_names(data=, out=split_names, splitsize=5000);
%local lib mem;
%let syslast = &data;
%let lib = %scan(&data,1,.);
%let mem = %scan(&data,2,.);
data parts;
ds = open ('sashelp.cars');
nobs = attrn(ds, 'NOBS');
lrecl = attrn(ds, 'LRECL');
ds = close(ds);
do n = 1 to ceil ( nobs * lrecl / &splitsize );
name = catx("_", "&mem", n);
OUTPUT;
end;
keep name;
run;
%mend;
%make_split_names (data=sashelp.cars)
If you want a dataset then replace your last block of macro logic with a data step.
data member_list ;
length memname $32 ;
if &no_of_tables > 1 then do i=1 to &no_of_tables;
memname=catx('_',"&inds",i);
output;
end;
else do;
memname="&inds";
output;
end;
keep memname;
run;
Solution:
%macro t_size(inlib=,inds=);
%let one_gig = 5000;
proc sql noprint;
select ceil((nobs*obslen)/&one_gig) into :tsize
from sashelp.vtable where libname=upcase("&inlib") and memname=upcase("&inds");
quit;
%let no_of_tables=%eval(%sysfunc(int(&tsize)));
data temp;
length temp $100;
%if (&tsize gt 1) %then
%do i = 1 %to &no_of_tables;
temp= "&inds._&i.";
output;
%end;
%else
%do;
temp= "&inds.";
output;
%end;
run;
%mend;
%t_size(inlib=SASHELP,inds=SHOES);
run;
Just add data step named temp, where input in variable temp.
Output:
+---------+
| temp |
+---------+
| SHOES_1 |
| SHOES_2 |
| SHOES_3 |
| SHOES_4 |
| SHOES_5 |
| SHOES_6 |
| SHOES_7 |
+---------+

SAS remove the specified word when it starts or ends the expression

I am writing a macro variable that aims at producing a valid SQL WHERE clause as specified by user in prompts. Assume we have 3 variables X, Y, Z, and so on. User may specify the filter for every variable and macro variable looks like:
a = x eq 1 and y eq 1 and z eq 1;
which is then proceeded to the WHERE clause.
But if user specifies only, let's say' filter for Y it looks like:
a = and y eq 1 and
And I would like it to look like:
a = y eq 1
That is why I would like to somehow remove the operand 'AND' when it starts or ends the expression (it may start or end it multiple times, e.g. if we fitler only Z variable it looks like):
a = and and and z eq 1
I suppose it could be easily done with regular expressions but since I'm new to it, is there anyone willing to help me ? ;)
Slight rework of #DirkHorsten's technique. This simply organizes the code in a slightly different manner so that the SQL statement can be more easily read. In my opinion, the SQL statement is the important piece of code that you would like readers to understand (so let's keep it simple!), while the building of the where clause is just a side-note. This can be a valuable approach, especially as your SQL statements become more complex.
Approach 1, a single variable for all filters:
%macro getMyData(xValue=, yValue=, zValue=);
%local and_filters;
* THE BORING IMPLEMENTATION DETAILS ARE KEPT SEPARATE;
%let and_filters = ;
%if "&xValue" ne "" %then %do;
%let and_filters = &and_filters and sex eq "&xValue";
%end;
%if "&yValue" ne "" %then %do;
%let and_filters = &and_filters and age eq &yValue;
%end;
%if "&zValue" ne "" %then %do;
%let and_filters = &and_filters and height eq &zValue;
%end;
* THE IMPORTANT PIECE OF CODE IS EASY TO READ;
proc sql;
select *
from sashelp.class
where 1 &and_filters
;
quit;
%mend;
Approach 2, individual variables for each filter:
%macro getMyData(xValue=, yValue=, zValue=);
%local and_sex_filter and_age_filter and_height_filter;
* THE BORING IMPLEMENTATION DETAILS ARE KEPT SEPARATE;
%let and_sex_filter = ;
%let and_age_filter = ;
%let and_height_filter = ;
%if "&xValue" ne "" %then %do;
%let and_sex_filter = and sex eq "&xValue";
%end;
%if "&yValue" ne "" %then %do;
%let and_age_filter = and age eq &yValue;
%end;
%if "&zValue" ne "" %then %do;
%let and_height_filter = and height eq &zValue;
%end;
* THE IMPORTANT PIECE OF CODE IS EASY TO READ;
proc sql;
select *
from sashelp.class
where 1
&and_sex_filter
&and_age_filter
&and_height_filter
;
quit;
%mend;
Assuming you're doing this via macro parameters, this is easier to do by supplying a default.
%macro filter(x=1,y=1,z=1);
where &x. and &y. and &z.;
%mend filter;
1 is "true", so it acts (along with "AND") as a left-out argument.
If you only want to pass values (not the full equality), then you can also do this:
%macro filter(x=x, y=y, z=z);
where x=&x. and y=&y. and z=&z.;
%mend filter;
x=x is always true in SAS, but if you're passing through to SQL Server or Oracle and might have nulls this will not work (as null=null is false in SQL Server or Oracle).
%macro getMyData(xValue=, yValue=, zValue=);
proc sql;
select *
from sashelp.class
where
%if %length(&xValue) %then %do;
sex = "&xValue." and
%end;
%if %length(&yValue) %then %do;
age = &yValue. and
%end;
%if %length(&zValue) %then %do;
height >= &zValue. and
%end;
1;
quit;
%mend;
Title 'Females';
%getMyData(xValue=F);
Title '12 year';
%getMyData(yValue=12);
Title 'Large males';
%getMyData(xValue=M, zValue=60);
You can use a little known "where also" expression. Appended "where also" expressions logically equal to AND operator for each WHERE clause and you can user "where also" as your first WHERE clause without any issues to your code.
If you have a macro like that:
%MACRO get_data;
data want;
set have;
where a = x eq 1 and y eq 1 and z eq 1;
RUN;
%MEND;
You can rewrite to someting like:
%MACRO get_data;
data want;
set have;
%IF &X ne %THEN
%DO;
where also &x eq 1;
%END;
%IF &Y ne %THEN
%DO;
where also &y eq 1;
%END;
%IF &Z ne %THEN
%DO;
where also &z eq 1;
%END;
RUN;
%MEND;
Before you test the code, you need to at least initialise the macro variables.
You could do it with something like that:
%IF %symexist(&Z)=0 %THEN %LET Z = ;
Thanks all,
I have already figured out a similar structure as #Robert Penridge, but I appriciate all of the answers :) Thanks!
PS. I also was not familiar with WHERE ALSO - may prove useful in the future:)

How to create multiple files in SAS?

I am trying to write a macro that should create multiple external html files . Here is my code
%macro createFiles;
%let name = Jupiter*Mercury*Venus;
%let htmlTxt1 = <html><h1>Hello To ;
%let htmlTxt2 = </h1></html> ;
%let i = 1 ;
%let thisName = %scan(&name., &i.,"*") ;
%do %while (&thisName. ne ) ;
filename thisFile "C:\Users\owner\Desktop\&thisName.html";
call execute ('data _null_; file &thisFile; put &htmlTxt1 || &thisName || &htmlTxt2; run; ') ;
%let i = %eval(&i + 1 ) ;
%let thisName = %scan(&name.,&i.,"*");
%end ;
%mend ;
%createFiles
However, it does not work . Please help me
Thanks
Mostly a combination of typo's and syntax errors. SAS also has the ODS HTML destination which would be easier to use to create HTML files in my opinion.
%macro createFiles;
%let name = Jupiter*Mercury*Venus;
%let htmlTxt1 = <html><h1>Hello To ;
%let htmlTxt2 = </h1></html> ;
%let i = 1 ;
%let thisName = %scan(&name., &i.,"*") ;
%do %while (&thisName. ne ) ;
filename thisFile "C:\temp\&thisName..html";
data _null_;
file thisFile;
put "&htmlTxt1 || &thisName || &htmlTxt2";
run;
%let i = %eval(&i + 1 ) ;
%let thisName = %scan(&name.,&i.,"*");
%end ;
%mend ;
%createFiles

What's the fastest way to partition a sas dataset for batch processing?

I have a large sas dataset (1.5m obs, ~250 variables) that I need to split into several smaller sas datasets of equal size for batch processing. Each dataset needs to contain all the variables but only a fraction of the obs. What is the fastest way of doing this?
You could do something like the following:
%macro splitds(inlib=,inds=,splitnum=,outid=);
proc sql noprint;
select nobs into :nobs
from sashelp.vtable
where libname=upcase("&inlib") and memname=upcase("&inds");
quit;
%put Number of observations in &inlib..&inds.: &nobs;
data %do i=1 %to &splitnum.;
&outid.&i
%end;;
set &inds.;
%do j=1 %to (&splitnum.-1);
%if &j.=1 %then %do;
if
%end;
%else %do;
else if
%end;
_n_<=((&nobs./&splitnum.)*&j.) then output &outid.&j.;
%end;
else output &outid.&splitnum.;
run;
%mend;
An example call to split MYLIB.MYDATA into 10 data sets named NEWDATA1 - NEWDATA10 would be:
%splitds(inlib=mylib,inds=mydata,splitnum=10,outid=newdata);
Try this. I haven't tested yet, so expect a bug somewhere. You will need to edit the macro call to BATCH_PROCESS to include the names of the datasets, number of new data sets, etc.
%macro nobs (dsn);
%local nobs dsid rc;
%let nobs=0;
%let dsid = %sysfunc(open(&dsn));
%if &dsid %then %do;
%let nobs = %sysfunc(attrn(&dsid,NOBS));
%end;
%else %put Open for dataset &dsn failed - %sysfunc(sysmsg());
%let rc = %sysfunc(close(&dsid));
&nobs
%mend nobs;
%macro batch_process(dsn_in,dsn_out_prefix,number_of_dsns);
%let dsn_obs = &nobs(&dsn_in);
%let obs_per_dsn = %sysevalf(&dsn_obs / &number_of_dsns);
data
%do i = 1 %to &number_of_dsns;
&dsn_out_prefix.&i
%end;
;
set &dsn_in;
drop _count;
retain _count 0;
_count = _count + 1;
%do i = 1 %to &number_of_dsns;
if (1 + ((&i - 1) * &obs_per_dsn)) <= _count <= (&i * &obs_per_dsn) then do;
output &dsn_out_prefix.&i;
end;
%end;
run;
%mend batch_process;
%batch_process( dsn_in=DSN_NAME , dsn_out_prefix = PREFIX_ , number_of_dsns = 5 );