Write out variables in a loop to a dataset - sas

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 |
+---------+

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;

Scan values from a macro variable and store it in a column in data set using SAS

%let abc = ("234.34", "C56.67", "2345.67", "C67.56") ;
Output:
Table1
Col1
234.34
C56.67
2345.67
C67.56
This is throwing an error, can somebody please guide me:
%macro generate ;
%local i ;
data table1;
length Col1 $100.;
%do i=1 %to %sysfunc(countw(&abc.));
Col1 = %scan(&abc., &i.,,"sq");
output;
%end;
run;
%mend;
%generate;
you do not need a macro and one way to do this.
%let abc = ("234.34", "C56.67", "2345.67", "C67.56" );
%let abc = %qsysfunc(compress(&abc,%str(%"%)%()));
data table1;
length Col1 $100.;
do i = 1 to countw("&abc", ",");
Col1 = scan("&abc", i,',');
output;
end;
run;
You can do this with a DO loop, but you will need to remove the () from the value of the macro variable.
%let abc = ("234.34", "C56.67", "2345.67", "C67.56") ;
data table1;
length Col1 $100.;
do Col1 = %substr(&abc,2,%length(&abc)-2);
output;
end;
run;
If the values do not contain () then you could also just use %scan(&abc,1,()), which also has the advantage of working whether or not the original value has the enclosing ().
Or just remove the () from the value and add them back when you use the macro variable in place where they are needed.
%let abc = "234.34", "C56.67", "2345.67", "C67.56" ;
...
do Col1 = &abc ;
...
where dxcode in (&abc)
....
A proc sql way
%let abc = ("234.34", "C56.67", "2345.67", "C67.56") ;
proc sql;
create table tab1 (col1 varchar(100));
quit;
options macrogen mlogic;
%macro gen;
%let i = 1;
%do %while (%scan(%superq(abc),&i,%str(,)) ne %str( ));
proc sql;
insert into tab1
values(%scan(%superq(abc),&i,%str(,)));
quit;
%let i = %sysevalf(&i + 1);
%end;
%mend gen; %gen;
It would scan through your macro that is delimited by ",", and progress by 1 with each loop.

%DROPMISS - SAS

I've been reading up on how to DROP variables from my dataset that have null values in every observation - it seems the best way to do this is using the %DROPMISS macro function - however I'm getting an error msg - below is the code I'm trying and the error msg
Code
%DROPMISS (DSIN=dataset1, DSOUT=dataset2);
Log
4665 %DROPMISS (DSIN=dataset1, DSOUT=dataset2);
-
180
WARNING: Apparent invocation of macro DROPMISS not resolved.
ERROR 180-322: Statement is not valid or it is used out of proper order.
You need to define the dropmiss macro before you use it.
You can find it here in the appendix (page3)
http://support.sas.com/resources/papers/proceedings10/048-2010.pdf
Or better formatted here:
/******************/
options nomprint noSYMBOLGEN MLOGIC;
/****************************/
%macro DROPMISS( DSNIN /* name of input SAS dataset
*/
, DSNOUT /* name of output SAS dataset
*/
, NODROP= /* [optional] variables to be omitted from dropping even if
they have only missing values */
) ;
/* PURPOSE: To find both Character and Numeric the variables that have only
missing values and drop them if
* they are not in &NONDROP
*
* NOTE: if there are no variables in the dataset, produce no variables
processing code
*
*
* EXAMPLE OF USE:
* %DROPMISS( DSNIN, DSNOUT )
* %DROPMISS( DSNIN, DSNOUT, NODROP=A B C D--H X1-X100 )
* %DROPMISS( DSNIN, DSNOUT, NODROP=_numeric_ )
* %DROPMISS( DSNIN, DSNOUT, NOdrop=_character_ )
*/
%local I ;
%if "&DSNIN" = "&DSNOUT"
%then %do ;
%put /------------------------------------------------\ ;
%put | ERROR from DROPMISS: | ;
%put | Input Dataset has same name as Output Dataset. | ;
%put | Execution terminating forthwith. | ;
%put \------------------------------------------------/ ;
%goto L9999 ;
%end ;
/*###################################################################*/
/* begin executable code
/*####################################################################/
/*===================================================================*/
/* Create dataset of variable names that have only missing values
/* exclude from the computation all names in &NODROP
/*===================================================================*/
proc contents data=&DSNIN( drop=&NODROP ) memtype=data noprint out=_cntnts_( keep=
name type ) ; run ;
%let N_CHAR = 0 ;
%let N_NUM = 0 ;
data _null_ ;
set _cntnts_ end=lastobs nobs=nobs ;
if nobs = 0 then stop ;
n_char + ( type = 2 ) ;
n_num + ( type = 1 ) ;
/* create macro vars containing final # of char, numeric variables */
if lastobs
then do ;
call symput( 'N_CHAR', left( put( n_char, 5. ))) ;
call symput( 'N_NUM' , left( put( n_num , 5. ))) ;
end ;
run ;
/*===================================================================*/
/* if there are no variables in dataset, stop further processing
/*===================================================================*/
%if %eval( &N_NUM + &N_CHAR ) = 0
%then %do ;
%put /----------------------------------\ ;
%put | ERROR from DROPMISS: | ;
%put | No variables in dataset. | ;
%put | Execution terminating forthwith. | ;
%put \----------------------------------/ ;
%goto L9999 ;
%end ;
/*===================================================================*/
/* put global macro names into global symbol table for later retrieval
/*===================================================================*/
%LET NUM0 =0;
%LET CHAR0 = 0;
%IF &N_NUM >0 %THEN %DO;
%do I = 1 %to &N_NUM ;
%global NUM&I ;
%end ;
%END;
%if &N_CHAR > 0 %THEN %DO;
%do I = 1 %to &N_CHAR ;
%global CHAR&I ;
%end ;
%END;
/*===================================================================*/
/* create macro vars containing variable names
/* efficiency note: could compute n_char, n_num here, but must declare macro names
to be
global b4 stuffing them
/*
/*===================================================================*/
proc sql noprint ;
%if &N_CHAR > 0 %then %str( select name into :CHAR1 - :CHAR&N_CHAR from
_cntnts_ where type = 2 ; ) ;
%if &N_NUM > 0 %then %str( select name into :NUM1 - :NUM&N_NUM from
_cntnts_ where type = 1 ; ) ;
quit ;
/*===================================================================*/
/* Determine the variables that are missing
/*
/*===================================================================*/
%IF &N_CHAR > 1 %THEN %DO;
%let N_CHAR_1 = %EVAL(&N_CHAR - 1);
%END;
Proc sql ;
select %do I= 1 %to &N_NUM; max (&&NUM&I) , %end; %IF &N_CHAR > 1 %THEN %DO;
%do I= 1 %to &N_CHAR_1; max(&&CHAR&I), %END; %end; MAX(&&CHAR&N_CHAR)
into
%do I= 1 %to &N_NUM; :NUMMAX&I , %END; %IF &N_CHAR > 1 %THEN %DO;
%do I= 1 %to &N_CHAR_1; :CHARMAX&I,%END; %END; :CHARMAX&N_CHAR
from &DSNIN;
quit;
/*===================================================================*/
/* initialize DROP_NUM, DROP_CHAR global macro vars
/*===================================================================*/
%let DROP_NUM = ;
%let DROP_CHAR = ;
%if &N_NUM > 0 %THEN %DO;
DATA _NULL_;
%do I = 1 %to &N_NUM ;
%IF &&NUMMAX&I =. %THEN %DO;
%let DROP_NUM = &DROP_NUM %qtrim( &&NUM&I ) ;
%END;
%end ;
RUN;
%END;
%IF &N_CHAR > 0 %THEN %DO;
DATA _NULL_;
%do I = 1 %to &N_CHAR ;
%IF "%qtrim(&&CHARMAX&I)" eq "" %THEN %DO;
%let DROP_CHAR = &DROP_CHAR %qtrim( &&CHAR&I ) ;
%END;
%end ;
RUN;
%END;
/*===================================================================*/
/* Create output dataset
/*===================================================================*/
data &DSNOUT ;
%if &DROP_CHAR ^= %then %str(DROP &DROP_CHAR ; ) ; /* drop char variables
that
have only missing values */
%if &DROP_NUM ^= %then %str(DROP &DROP_NUM ; ) ; /* drop num variables
that
have only missing values */
set &DSNIN ;
%if &DROP_CHAR ^= or &DROP_NUM ^= %then %do;
%put /----------------------------------\ ;
%put | Variables dropped are &DROP_CHAR &DROP_NUM | ;
%put \----------------------------------/ ;
%end;
%if &DROP_CHAR = and &DROP_NUM = %then %do;
%put /----------------------------------\ ;
%put | No variables are dropped |;
%put \----------------------------------/ ;
%end;
run ;
%L9999:
%mend DROPMISS ;

How to loop through a macro variable in SAS

I have an example like this:
proc sql;
select dealno into :deal_no
from deal_table;
Now I want to traverse the variable deal_no now containing all dealno in table deal_table but I don't know how to do it.
Another option is add 'separated by' to the sql code, which will add a delimiter to the values. You can then use the SCAN function in a data step or %SCAN in a macro to loop through the values and perform whatever task you want. Example below.
proc sql noprint;
select age into :age separated by ','
from sashelp.class;
quit;
%put &age.;
data test;
do i=1 by 1 while(scan("&age.",i) ne '');
age=scan("&age.",i);
output;
end;
drop i;
run;
If you do
%put &deal_no;
you can see that it only contains the first value of dealno, not all of them.
To avoid that you can do something like this:
proc sql;
create table counter as select dealno from deal_table;
select dealno into :deal_no_1 - :deal_no_&sqlobs
from deal_table;
quit;
%let N = &sqlobs;
%macro loop;
%do i = 1 %to &N;
%put &&deal_no_&i;
%end;
%mend;
%loop; run;
Here's another solution.
proc sql noprint;
select age into :ageVals separated by ' '
from ageData;
quit;
%put &ageVals;
%macro loopAgeVals; %let i = 1; %let ageVal = %scan(&ageVals, &i);
%do %while("&ageVal" ~= "");
%put &ageVal;
%let i = %eval(&i + 1);
%let ageVal = %scan(&ageVals, &i);
%end;
%mend;
%loopAgeVals;

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 );