Proc Sql Do Loop in SAS - sas

For an assignment I am asked to create a do loop in Proc Sql statement. My program is not recognizing the m1sales and m2sales. Here are the data sets and the macros that I had to create. The First macro is to set allow people to set the qtr to a number. The second macro set Months One, Two, and Three depending on the months. The first proc sql is doing what I want. The second is not when I add in the do statement. I was given the additional statement, "The %do loop isn't 'within' a create table command. The %do loop is replaced with a sequence of text that follows the create table command; that text may be though of as 'within' the create table statement." Can anyone tell me how to create that do loop correctly?
data Month1;
input Name $ sales;
cards;
Joyce 235
Marsha 352
Bill 491
Vernon 210
Sally 418
;
data Month2;
input Name $ sales;
cards;
Joyce 169
Marsha 281
Bill 315
Vernon 397
Sally 305
;
data Month3;
input Name $ sales;
cards;
Joyce 471
Marsha 314
Bill 394
Vernon 291
Sally 337
;
data Month4;
input Name $ sales;
cards;
Joyce 338
Marsha 259
Bill 310
Vernon 432
Sally 362
;
data Month5;
input Name $ sales;
cards;
Joyce 209
Marsha 355
Bill 302
Vernon 416
Sally 475
;
data Month6;
input Name $ sales;
cards;
Joyce 306
Marsha 472
Bill 351
Vernon 405
Sally 358
;
options symbolgen;
%Macro quarter(quarter);
%Global qtr;
%Let qtr = &quarter;
%Mend quarter;
%quarter (1);
options mprint symbolgen;
%Macro Month(day);
%Global One;
%Global Two;
%Global Three;
%if &qtr = %eval(1) %then %do;
%Let One = 1;
%Let Two = 2;
%Let Three = 3;
%end;
%Else %if &qtr = %eval(2) %then %do;
%Let One = 4;
%Let Two = 5;
%Let Three = 6;
%end;
%Else %if &qtr = %eval(3) %then %do;
%Let One = 7;
%Let Two = 8;
%Let Three = 9;
%end;
%Else %if &qtr = %eval(4) %then %do;
%Let One = 10;
%Let Two = 11;
%Let Three = 12;
%end;
%Mend Month;
%Month(&qtr);
Correct code:
%Macro qtrearn(x);
proc sql;
create table qtr&x as
select Month&One..name, month&One..sales as m&One.sales, month&Two..sales as m&Two.sales,
month&Three..sales as m&Three.sales, sum(month&One..sales, month&Two..sales, month&Three..sales) as qtr&x.sales
from month&One, month&Two, month&Three
where month&One..name=month&Two..name=month&Three..name;
select sum(m&One.sales) as m&One.total, sum(m&Two.sales) as m&Two.total, sum(m&Three.sales) as m&Three.total,
sum(qtr&x.sales) as qtr&x.total
from qtr&x;
%Mend qtrearn;
%qtrearn(&qtr);
Code that is not working with the do loop. I need to insert the do loop for an assignment.
options mprint symbolgen;
%Macro qtrearn(x);
proc sql;
%do i = &One %to &Three;
create table qtr&x as
select Month&i..name, month&&i..sales as m&&i.sales,
sum(month&One..sales, month&Two..sales, Month&Three..sales) as qtr&x.sales
from month&One, month&Two, month&Three
where month&One..name=month&Two..name=month&Three..name;
%end;
select sum(m&One.sales) as m&One.total, sum(m&Two.sales) as m&Two.total, sum(m&Three.sales) as m&Three.total,
sum(qtr&x.sales) as qtr&x.total
from qtr&x;
%Mend qtrearn;
%qtrearn(&qtr);

I was able to solve it. In case someone else wants it. Here is the answer:
%Macro qtrearn(x);
proc sql;
create table qtr&x as
select Month&One..name,
%do i = &One %to &Three;
month&&i..sales as m&&i.sales,
%end;
sum(month&One..sales, month&Two..sales, month&Three..sales) as qtr&x.sales
from month&One, month&Two, month&Three
where month&One..name=month&Two..name=month&Three..name;
create table totals_qtr&x as
select %do i = &One %to &Three;
sum(m&&i.sales) as m&&i.total,
%end;
sum(qtr&x.sales) as qtr&x.total
from qtr&x;
proc print data=work.qtr&x;
run;
proc print data=work.totals_qtr&x;
run;
%Mend qtrearn;
%qtrearn(&qtr);

Related

Iterate date in loop in SAS

need help on one query , I have to iterate date in do loop that is in format of yymmd6.(202112) so that once the month reach to 12 then its automatically change to next year first month.
///// code////////
%let startmo=202010 ;
%let endmo= 202102;
%macro test;
%do month= &startmo %to &endmo;
Data ABC_&month;
Set test&month;
X=&month ;
%end;
Run;
%mend;
%test;
//////////
Output should be 5 dataset as
ABC_202010
ABC_202011
ABC_202012
ABC_202101
ABC_20210
I need macro variable month to be resolved 202101 once it reached to 202012
Those are not actual DATE values. Just strings that you have imposed your own interpretation on so that they LOOK like dates to you.
Use date values instead and then it is easy to generate strings in the style you need by using a FORMAt.
%macro test(startmo,endmo);
%local offset month month_string;
%do offset = 0 to %sysfunc(intck(month,&startmo,&endmo));
%let month=%sysfunc(intnx(month,&startmo,&offset));
%let month_string=%sysfunc(putn(&month,yymmn6.));
data ABC_&month_string;
set test&month_string;
X=&month ;
format X monyy7.;
run;
%end;
%mend;
%test(startmo='01OCT2020'd , endmo='01FEB2021'd)
And if you need to convert one of those strings into a date value use an INFORMAT.
%let date=%sysfunc(inputn(202010,yymmn6.));
I would prefer to use a do while loop.
check whether the last 2 characters are 12, if so, change the month part to 01.
code
%let startmo=202010 ;
%let endmo= 202102;
%macro test;
%do %while(&startmo <= &endmo);
Data ABC_&startmo;
Set test&startmo;
X=&startmo ;
Run;
%end;
%let mon = %substr(&startmo, 5, 2);
%let yr = %substr(&startmo, 1, 4);
%if &mon = 12 %then %do;
%let m = 01;
%let startmo = %sysfunc(cat(%eval(&yr + 1), &m));
%end;
%else %do;
%let startmo = %eval(&startmo + 1);
%end;
%mend;
%test;

how can I build a loop for macro in SAS?

I want to do a simulation based on macro in SAS. I can build a function named 'fine()', the code is as follows
DATA CLASS;
INPUT NAME $ SEX $ AGE HEIGHT WEIGHT;
CARDS;
ALFRED M 14 69.0 112.5
ALICE F 13 56.5 84.0
BARBARA F 13 65.3 98.0
CAROL F 14 62.8 102.5
HENRY M 14 63.5 102.5
RUN;
PROC PRINT;
TITLE 'DATA';
RUN;
proc print data=CLASS;run;
PROC FCMP OUTLIB = work.functions.func;
function populationCalc(HEIGHT,WEIGHT,thres);
pop=HEIGHT-WEIGHT-thres;
return (pop);
ENDSUB;
options cmplib=(work.functions);
%macro fine(i);
data ex;
set CLASS;
thres=&i;
pop = populationCalc(HEIGHT,WEIGHT,thres);
if (pop>50) then score=1;
else score=0;
run;
proc iml;
USE ex;
READ all var _ALL_ into ma[colname=varNames];
CLOSE ex;
nn=nrow(ma);
total_score=sum(ma[,'thres']);
avg_score=sum(ma[,'thres'])/nn;
print total_score avg_score;
%mend fine;
%fine(10);
%fine(100);
%fine(150);
I want to build a loop for function 'fine()' ans also use macro, but the result is not as I expect. How can I fix this?
%macro ct(n);
data data_want;
%do i=1 %to &n;
x=%fine(&i);
output x;
%end;
run;
%macro ct;
%ct(10);
%fine does not generate any text that can be used in the context of a right hand side (RHS) of a DATA Step variable assignment statement.
You seem to perhaps want this data set as a result of invoking %ct
i total_score average_score
- ----------- -------------
1 5 1
2 10 2
3 15 3
etc...
Step 1. Save IML result
Add this to the bottom of IML code in %fine, replacing the print
create fine_out var {total_score avg_score};
append;
close fine_out;
quit;
Step 2. Rewrite ct macro
Invoke %fine outside a DATA step context so the DATA and IML steps can run. Append the IML output to a results data set.
%macro ct(n,out=result);
%local i;
%do i=1 %to &n;
%fine(&i)
%if &i = 1 %then %do;
data &out; set fine_out; run;
%end;
%else %do;
proc append base=&out data=fine_out; run;
%end;
%end;
%mend;
options mprint;
%ct(10)
This should be the output WORK.RESULT based on your data

Include macro condition in report

Below is the small piece of code to get reports in excel.
%if &linear %then %do;
ods excel options(sheet_name="vol");
proc print data=perf;
id direction segment;
var accts;
run;
%end;
%else %do;
ods excel options(sheet_name="vol");
proc print data=perf;
id direction segment;
var accts;
run;
%end;
Direction segment accts
A model 17177
A booked 567
A unbooked 5676
B model 17177
B booked 567
B unbooked 5676
If segments are not available i will get report as below
Direction segment accts
A model 17177
A 1 17177
B model 17177
B 1 17177
Iam planing to introduce two macro variables
%let dir =A;
%let Non_segment=y;
Based on value for direction it should give only those direction and if there no segment(Non_segment=y;), it should have only first observation. So the output will looks like below for Non_segment=y
Direction segment accts
A model 17177
If you do not have any variable to test then you cannot subset to the first observation per by group with just a WHERE statement. You will need to generate a data step.
data to_print ;
set perf ;
by direction;
%if %length(&dir) %then %do;
where direction="&dir";
%end;
%if %upcase(&non_segment)=Y %then %do;
if first.direction;
%end;
run;
I tried below code.
%macro isblank(var);
%if %symexist(&var) %then 1; %*not exist*;
%else %if %sysevalf(%superq(&var)=,boolean) %then 1; %*blank*;
%else 0;
%mend isblank;
%let dir =A;
%let Non_segment=y;
proc print data=new;
%if %isblank(Non_segment) %then (obs=1);*firstobs*;
id direction segment;
var acct;
%else;*all obsevation*;
proc print data =new;
id direction segment;
var acct;
run;
Getting below error
proc print data=new;
78 %if %isblank(Non_segment) %then (obs=1);*firstobs*;
ERROR: Nesting of %IF statements in open code is not supported. %IF ignored.
ERROR: Skipping to next %END statement.
79 id direction segment;
80 var acct;
81 %else;*all obsevation*;
ERROR: The %ELSE statement is not valid in open code.
NOTE: The SAS System stopped processing this step because of errors.
NOTE: There were 6 observations read from the data set WORK.NEW.
NOTE: PROCEDURE PRINT used (Total process time):
real time 10.57 seconds
cpu time 6.31 seconds
82 proc print data =new;
83 id direction segment;
84 var acct;
85 run;

nested sysfunc + cat + leading 0 removed

I'm trying with the below loop to manage a dynamic append between three tables.
However, the nested cat with %sysfuncs removed the zeros from the month and the day
Would be someone so gentle to explain what Is not working fine here?
I'm expecting to dynamically declares the table like, ex.
"FTP.TOTAL_4B_20170603" (yyyymmdd) and not like the code is retriving: "FTP.TOTAL_4B_201763"
data dd; %MACRO H;
%DO I=1 %TO 2;
proc append
base=prod0
data=FTP.TOTAL_4B_%sysfunc(CATS(%sysfunc(year(%sysfunc(intnx(day,%sysfunc(today()),-&i,s)))),
%sysfunc(putn(%sysfunc(month(%sysfunc(intnx(day,%sysfunc(today()),-&i,s)))),z2.)),
%sysfunc(putn(%sysfunc(day(%sysfunc(intnx(day,%sysfunc(today()),-&i,s)))),z2.))));
run;
%end;
proc sort data=prod0;
by pan fecha;
run;
%MEND H;
%H;
run;
Thanks for your help
Bests
D
Not sure why you have proc append and proc sort within a data step, but I think the macro expression below could help:
%MACRO J;
%DO I=1 %TO 2;
%put FTP.TOTAL_4B_%sysfunc(intnx(DAY,%sysfunc(today()),-&I.,S),yymmddn8.);
%END;
%MEND J;
%J;
You can use formats, to get the date YYYYMMDD. Also intnx is not necessary if you are going to iterate on days only. date() returns integer, so you can easily use addition/subtraction.
%MACRO test;
%DO I=1 %TO 2;
%let test_date=%sysfunc(putn(%sysfunc(date()) - &i, yymmddn8.));
%put DS name FTP.TOTAL_4B_&test_date;
%end;
%MEND test;
%test;
Gives the result
1 OPTIONS NONOTES NOSTIMER NOSOURCE NOSYNTAXCHECK;
59
60
61
62
63 %MACRO test;
64 %DO I=1 %TO 2;
65 %let test_date=%sysfunc(putn(%sysfunc(date())-&i, yymmddn8.));
66 %put DS name FTP.TOTAL_4B_&test_date;
67 %end;
68 %MEND test;
69
70 %test;
DS name FTP.TOTAL_4B_20170612
DS name FTP.TOTAL_4B_20170611
71
72 OPTIONS NONOTES NOSTIMER NOSOURCE NOSYNTAXCHECK;
84

How to create this macro with conditions?

Sample input data:
FirstName LastName Group Age LastVenue Position
Jack Smith ULDA 25 TheaterA 1
Jesse James GODL 37 TheaterB 12
Jane Doe ULDA 29 TheaterA 3
Izzy Gord IIPA 41 TheaterC 8
Ann Roswell GODL 30 TheaterB 16
Chelsea Jenk ULDA 19 TheaterA 11
I am trying to create:
%macro group_members(group=);
proc print data=sample;
var Position Age Group FirstName LastName;
where group=&group;
%mend group_members;
However I want to add conditions to it so if nothing is entered %group_members() then it will display all groups with the order of the variables shown above. If an invalid group is entered in this case: %group_members(LOL) then I would like a note to be sent to the log %put 'An invalid group was entered'. and therefor nothing should be printed. I am trying to create a program very similar on a much larger dataset.
I appreciate any help in advanced! Thank you :)
So far I have tried:
%macro group_members(group=);
proc sql;
select count(*) into :ct
from sample
where group="&group"
quit;
proc print data=sample;
%if &group ^= %then %do;
where group="&group."; %end;
%if &ct = 0 %then %put An Invalid group was entered;
%else %do;
where group="&group.";
%end; run;
%mend group_members;
I get errors from every test.. for example %group_members() returns an error of:
ERROR: More positional parameters found than defined
Entering a blank resulting in all groups being shown could be achieved by surrounding the where statement with this macro code:
%if &group ^= %then %do;
where group="&group.";
%end;
This only submits the where statement, in the event that the &group variable is populated. Note also that I've added double quotes so that the where statement doesn't generate syntax errors.
The macro would need to know which groups were or were not valid. This would require an extra processing step before the proc print:
proc sql;
select count(*) into :ct
from sample
where group="&group";
quit;
%if &ct = 0 %then %put An invalid group was entered;
%else %do;
...
&ct will contain the number of records that match the where clause. If zero, then I'm assuming that means it's an invalid group.
/Creating Sample dataset/
data test;
infile datalines dlm="," missover;
input FirstName : $10.
LastName : $10.
Group : $8.
Age : 8.
LastVenue : $10.
Position : 8.
;
datalines;
Jack,Smith,ULDA,25,TheaterA,1
Jesse,James,GODL,37,TheaterB,12
Jane,Doe,ULDA,29,TheaterA,3
Izzy,Gord,IIPA,41,TheaterC,8
Ann,Roswell,GODL,30,TheaterB,16
Chelsea,Jenk,ULDA,19,TheaterA,11
;
run;
Have added comments in the code itself
%macro group_members(group=);
%put &group.;
/*Checking if the group is valid or invalid*/
proc sql noprint;
select count(*) into :num from test where group="&group.";
quit;
%put &num.;
data final;
set test;
/*checking if the group entered is NULL, if it is ,then it will output all the records*/
%if "&group."="" %then %do; %end;
/*If the group is Valid or not, if it is invalid then nothing will be in output and a msg in the LOG will be displayed, you can put ERROR statement if you want*/
%else %if &num. = 0 %then %do;
where group="&group.";
%put "An Invalid group was entered";
/*If above two are not the case then it will filter on that group*/
%end;
%else %do;
where group="&group.";
%end;
run;
%mend group_members;
%group_members(group=);
Credit to #mjsqu
Step1: Test if &group is a valid group. count(*) do this for you.
Step2: If count(*) return 0, then output user-defined messages.
Step3: Otherwise, continue proc print. If &group = then list all records.
%macro group_members(group);
proc sql noprint;
select count(*) into :ct
from sample
where group="&group.";
%if &ct = 0 and &group ne %then %put An Invalid group was entered;
%else %do;
proc print data=sample;
var Position Age Group FirstName LastName;
%if &group ne %then
%do;
where group="&group.";
%end;
%end;
%mend group_members;
%group_members();
%group_members(GODL);
%group_members(G);