Get Data from SAS Macro (list of values) to SAS table (column) - sql

I am trying to create a SAS table from Macro variable using PROC SQL:
I have a list of value saved in a macro variable :
%let l=1,2,3;
I want to create a SAS table with a column containing the values of the macro variable :
1
2
3
Thank you very much for your help.
Sincerely,
Abdeljalil

you should so some effort to solving this yourself.
Put the values into a string, parse the string and output the values you would like.
%let l=1,2,3;
data want;
str = "&l";
do i=1 to countw(str,',');
value = input(scan(str,i,","),best.);
output;
end;
/*drop other variables if you want*/
drop str i;
run;

Something like this?
%let age=%str(12,13,15);
proc sql;
select * from sashelp.class where age in (&age);
quit;

You have a data set that contains a list of names and you want to place these names into a macro variable for later use. That will work as long as the macro variable does not go beyond the 64K limit.
If the value hits this limit, then you can use macro processing to retrieve the names from the data set. Since a macro definition does not have the 64K restriction, it can be used to create the list for you.
In the sample code on the Full Code tab, we have a list of names that we want to use on an INPUT statement along with a given informat. This sample demonstrates how to create the list without having to use a macro variable.
data one;
input name $;
datalines;
abc
def
ghi
;
run;
%macro test;
%let dsid=%sysfunc(open(one));
%let cnt=%sysfunc(attrn(&dsid,nobs));
%do i=1 %to &cnt;
%let rc=%sysfunc(fetchobs(&dsid,&i));
%cmpres(%sysfunc(getvarc(&dsid,%sysfunc(varnum(&dsid,name))))) $4.
%end;
%let rc=%sysfunc(close(&dsid));
%mend test;
/** Using %PUT to see outcome **/
/** %test could be used on an INPUT statement **/
%put %test;
source: http://support.sas.com/kb/39/605.html

Related

Can I Create a Macro Name from a Macro Variable

I would like to send a set of values into a macro using the INTO function. I do this all the time. However, I would like to the same thing for three different tables and I want to create the INTO macro name with a macro variable that I pass in. Can this be done? It's not working for me. Perhaps I need a preceding command before the macro name to compile it first? Essentially, I want three macro stores to be created: IPCOLS with values from IP_DENOMINATORS, LTCOLS with values from LT_DENOMINATORS, and OTCOLS with values from OT_DENOMINATORS.
Proc Sql noprint;
%macro SUBST() / parmbuff;
%let i=1;
%let FT=%scan(%bquote(&SYSPBUFF),1);
%do %while (&FT^=);
Select DISTINCT STATE into :&FT.COLS separated by ','
from &FT._DENOMINATORS;
%let i=%eval(&I+1);
%let FT=%scan(%bquote(&SYSPBUFF),&I);
%end;
%mend SUBST;
%SUBST(IP,LT,OT);
Quit;
%Put &&FT.COLS;
The first issue is that your are not trying to reference the macro variable you created. The macro variables will be named IPCOLS, LTCOLS and OPCOLS.
The main issue is that the macro variable(s) might not exist after the macro ends since you created them while the macro was running.
Make sure that the macro variable you are creating is not made local to the macro. When you reference a macro variable while running a macro it first uses the existing macro variable with that name. If none exists then it makes a new one in the local symbol table for the macro. When the macro finishes the local symbol table is gone. You can use the %symexist() function to check if a macro variable already exists or not. If there isn't one you can create a GLOBAL macro variable that will survive past the end of the macro execution.
First thing is don't define the macro in the middle of your other code. Define it first and then use it. That way your code is much easier to read/edit/debug. Depending on how you plan to use it you might even want to have the macro generate the PROC SQL and QUIT statements.
Also there is no need to use PARMBUFF option to pass in a list of values. Just don't use commas between the values. Use space or some other character. (Note that you probably don't want the commas in the values of the macro variables you are creating either, but that depends on what STATE is and how you plan to use those macro variables.)
%macro subst(list);
%local i prefix mvar;
%do i=1 %to %sysfunc(countw(&list,%str( )));
%let prefix=%scan(&list,&i,%str( ));
%let mvar=&prefix.cols;
%if not %symexist(&mvar) %then %global &mvar;
select DISTINCT STATE
into :&mvar separated by ','
from &prefix._DENOMINATORS
;
%end;
%mend subst;
proc sql noprint;
%SUBST(IP LT OT);
quit;
%put &=IPCOLS;
%put &=LTCOLS;
%put &=OTCOLS;
Try the below formulation instead. A few changes were made:
The %subst() macro is moved outside of PROC SQL so that it can compile first.
The do-while loop is converted into a do i = 1 to n. The do-while loop produced a blank value of &FT in the final iteration.
All macros created are explicitly made global.
Code:
%macro SUBST() / parmbuff;
%do i = 1 %to %sysfunc(countw(%bquote(&syspbuff.) ) );
%let ft = %scan(%bquote(&syspbuff.), &i.);
%global &FT.COLS;
Select DISTINCT STATE into :&FT.COLS separated by ','
from &FT._DENOMINATORS;
%end;
%mend SUBST;
Proc Sql noprint;
%SUBST(IP,LT,OT);
Quit;
%put IP: &IPCOLS;
%put LT: &LTCOLS;
%put OT: &OTCOLS;

How to randomly select variables in SAS?

I can find all sorts of information on how to randomly select observations in SAS which is a fairly easy task. This is not what I need though. I need to randomly select variables. What I want to do specifically is randomly choose 20 variables from my list of 159 variables and do this 50 times. I want to ensure diversity too. I have been spending about two days on this and am having no luck.
I'm glad that you asked this question, because I just developed a solution for that! Let's break down exactly what needs to be done, step-by-step.
Step 0: What do we need to do?
We need a way to take all of our variables and randomly select 20 of them while keeping them within the bounds of the SAS language rules.
We'll require:
All variables in the dataset
A way to re-sort them randomly
A limit of 20 variables
A way to loop this 50 times
Let's start with 1.
Step 1: Getting all the variables
sashelp.vcolumn provides a list of all variables within a dataset. Let's select them all.
proc sql noprint;
create table all_vars as
select name
where libname = 'LIBRARYHERE' AND memname = 'HAVE'
;
quit;
This gets us a list of all variables within our dataset. Now, we need to sort them randomly.
Step 2: Making them random
SAS provides the rand function that allows you to pull from any distribution that you'd like. You can use call streaminit(seedhere) prior to the rand function to set a specific seed, creating reproducable results.
We'll simply modify our original SQL statement and order the dataset with the rand() function.
data _null_;
call streaminit(1234);
run;
proc sql noprint;
create table all_vars as
select name
from sashelp.vcolumn
where libname = 'LIBRARYHERE' AND memname = 'HAVE'
order by rand('uniform');
quit;
Now we've got all of our variables in a random order, distributed evenly by the uniform distribution.
Step 3: Limit to 20 variables
You can do this a few ways. One way is the obs= dataset option in separate procedures, another is the outobs= proc sql option. Personally, I like the obs= dataset option since it doesn't generate a warning in the log, and can be used in other procedures.
data _null_;
call streaminit(1234);
run;
proc sql noprint outobs=20;
create table all_vars as
select name
from sashelp.vcolumn
where libname = 'LIBRARYHERE' AND memname = 'HAVE'
order by rand('uniform');
quit;
Step 4: Loop it 50 times
We'll use SAS Macro Language to do this part. We can create 50 individual datasets this way, or switch the code up slightly and read them into macro variables.
%macro selectVars(loop=50, seed=1234);
data _null_;
call streaminit(&seed);
run;
%do i = 1 %to &loop;
proc sql noprint outobs=20;
create table all_vars&i as
select name
from sashelp.vcolumn
where libname = 'LIBRARYHERE' AND memname = 'HAVE'
order by rand('uniform')
;
quit;
%end;
%mend;
%selectVars;
Or, option 2:
%macro selectVars(loop=50, seed=1234);
data _null_;
call streaminit(&seed);
run;
%do i = 1 %to &loop;
proc sql noprint outobs=20;
select name
into :varlist separated by ' '
from sashelp.vcolumn
where libname = 'LIBRARYHERE' AND memname = 'HAVE'
order by rand('uniform')
;
quit;
%end;
%mend;
%selectVars;
The 2nd option will create a local macro variable called &varlist that will have the random 20 variables separated by spaces. This can be convenient for various modeling procs, and is preferable since it does not create a separate dataset each time.
Hope this helps!
You will need to treat your meta data as data and use SURVEYSELECT to select observations. Then perhaps put these names into macro variables but you did not mention the exact output you want.
data v;
array rvars[159];
run;
proc transpose data=v(obs=0) out=vars name=name;
var rvars:;
run;
proc surveyselect reps=4 sampsize=20 data=vars out=selection;
run;
proc transpose data=selection out=lists(drop=_:);
by replicate;
var name;
run;
proc print;
run;
data _null_;
set lists;
by replicate;
call symputx(cats('VLIST',_n_),catx(' ',of col:));
run;
%put _global_;

SAS sql select variable as change name to a date in MonYY7. format

I am not sure if it is possible at all, but in case someone knows the answer. I need to select variables and rename them to dates in MonYY7. format. My understanding is that SAS stores dates as numbers, and it is the formats which represent them in the former way. However, would it be possible to somehow rename the variable's name itself according to the format?
Here is the code I have written:
%macro try;
%let month_count_back = 12;
%let today = %sysfunc(today());
%let sysmonth = %sysfunc(month("&sysdate"d));
proc sql;
create table try as
select *,
%do i = -&sysmonth. %to -&month_count_back.-&sysmonth.+1 %by -1;
max(month(FP_NDT) = month(intnx('month',&today.,&i.))) as mn%eval(&month_count_back.+&sysmonth.+&i.)
%if &i. = -&month_count_back.-&sysmonth.+1 %then %goto leave_month;
,
%leave_month:
%end;
from work.test
group by var;
quit;
run;
%mend try;
%try;
run;
It returns dummy indicators for each month value of the 'var' variable for the previous year (the intention here is to know which values are null and which are not). However, I would like each dummy variable created be named according to the month and the year it refers to. For example, m12 should be DEC2015, m11 - NOV2015 etc... As a corollary if month_count_back is equal to, say, 36 then m36 should be DEC2015, but M12 should be DEC2013 and M1 should be JAN2013 etc...
Maybe there is way to rename it later in a data step? I have tried to loop through it, but could not control for the changing month_count_back value...
Would appreciate any suggestions, thanks!

SAS - renaming variables

I am trying to change the names of variables in my table/dataset. I went through several websites and this discussion forum, but I didnĀ“t manage to find any code that would work properly in my case (i am a newcomer to SAS).
My dataset contains 103 columns and I would like to rename the first 100 columns. The name of the first column is CFT(1), CFT(2) of the second column,..., CFT(100) of the 100th column. New variables can be called for example CFT_n(1),...,CFT_n(100).
The code I was using is following:
data vystup_m200_b;
set vystup_m200_a;
rename 'cft(1)'n - 'cft(100)'n='cft(1)_n'n - 'cft(100)_n'n;
run;
But I obtain an error stating:
Aplhabetic prefixes for enumerated variables (cft(1)-cft(100)) are different.
Thank you for any suggestion what I am doing wrong.
Even with validvarname=any the numeric suffix on a numbered variable list have to have the number as the last part of the name. You "could" use the features of PROC TRANSPOSE to flip-flop the data to rename the variables. This is only advisable if the data are rather small.
data ren;
array _a[*] 'cft(1)'n 'cft(2)'n 'cft(3)'n ( 1 2 3);
do i = 1 to 10;
output;
end;
drop i;
run;
proc transpose data=ren out=ren2;
run;
proc transpose data=ren2 out=renamed(drop=_name_) suffix=_N;
id _name_;
run;
If your variables are sequentially named, a simple macro will suffice:
option validvarname = any;
data ren;
array _a[*] 'cft(1)'n 'cft(2)'n 'cft(3)'n ( 1 2 3);
do i = 1 to 10;
output;
end;
drop i;
run;
%macro rename_loop;
%local i;
%do i = 1 %to 3;
"cft(&i)"n = "cft(&i)_n"n
%end;
%mend rename_loop;
proc datasets lib = work nolist nowarn nodetails;
modify ren;
rename %rename_loop;
run;
quit;
This should work more or less instantaneously, regardless of the size of the dataset, as it only needs to update the metadata.
Renaming is fastest. I would look to a more general solution that doesn't require knowing anything like the name or how many or if you need name literals.
data ren;
array _a[*] 'cft(1)'n 'cft(2)'n 'cft(3)'n (1 2 3);
do i = 1 to 10;
output;
end;
drop i;
run;
proc print;
run;
proc transpose data=ren(obs=0) out=ren2;
run;
proc sql noprint;
select catx('=',nliteral(_name_),nliteral(cats(_name_,'_n')))
into :renamelist separated by ' '
from ren2;
quit;
run;
%put NOTE: &=renamelist;
proc datasets nolist;
modify ren;
rename &renamelist;
run;
contents data=ren varnum short;
quit;
Another solution, which is renaming variables after upload:
proc import datafile="\\folder\RUN_00.xlsx"
dbms=xlsx out=run_00 replace;
run;
data rename;
length ren $32767;
set run_00(obs= 1);
keep ren delka;
array cfte{*} CFT:;
do i=1 to dim(cfte);
ren=strip(ren)||" 'cft("||strip(i)||")'n='cft_"||strip(i)||"_00'n";
delka=length(ren);
end;
call symputx("renam",ren);
run;
proc datasets library=work;
modify run_00;
rename &renam;
run;

Select character variables that have all missing values

I have a SAS dataset with around 3,000 variables, and I would like to get rid of the character variables for which all values are missing. I know how to do this for numeric variables-- I'm wondering specifically about the character variables. I need to do the work using base SAS, but that could include proc SQL, which is why I've tagged this one 'SQL' also.
Thank you!
Edit:
Background info: This is a tall dataset, with survey data from 7 waves of interviews. Some, but not all, of the survey items (variables) were repeated across waves. I'm trying to create a list of items that were actually used in each wave by pulling all the records for that wave, getting rid of all the columns that have nothing but SAS's default missing values, and then running proc contents.
I created a macro that will check for empty character columns and either remove them from the original or create a new data set with the empty columns removed. It takes two optional arguments: The name of the data set (default is the most recently created data set), and a suffix to name the new copy (set suffix to nothing to edit the original).
It uses proc freq with the levels option and a custom format to determine the empty character columns. proc sql is then used to create a list of the columns to be removed and store them in a macro variable.
Here is the macro:
%macro delemptycol(ds=_last_, suffix=_noempty);
option nonotes;
proc format;
value $charmiss
' '= ' '
other='1';
run;
%if "&ds"="_last_" %then %let ds=&syslast.;
ods select nlevels;
ods output nlevels=nlev;
proc freq data=&ds.(keep=_character_) levels ;
format _character_ $charmiss.;
run;
ods output close;
/* create macro var with list of cols to remove */
%local emptycols;
proc sql noprint;
select tablevar into: emptycols separated by ' '
from nlev
where NNonMissLevels=0;
quit;
%if &emptycols.= %then %do;
%put DELEMPTYCOL: No empty character columns were found in data set &ds.;
%end;
%else %do;
%put DELEMPTYCOL: The following empty character columns were found in data set &ds. : &emptycols.;
%put DELEMPTYCOL: Data set &ds.&suffix created with empty columns removed;
data &ds.&suffix. ;
set &ds(drop=&emptycols);
run;
%end;
options notes;
%mend;
Examples usage:
/* create some fake data: Here char5 will be empty */
data chardata(drop= j randnum);
length char1-char5 $8.;
array chars(5) char1-char5;
do i=1 to 100;
call missing(of char:);
randnum=floor(10*ranuni(i));
do j=2 to 5;
if (j-1)<randnum<=(j+1) then chars(j-1)="FOO";
end;
output;
end;
run;
%delemptycol(); /* uses default _last_ for the data and "_noempty" as the suffix */
%delemptycol(ds=chardata, suffix=); /* removes the empty columns from the original */
There's probably a simpler way but this is what I came up with.
Cheers
Rob
EDIT: Note that this works for both character and numeric variables.
**
** TEST DATASET
*;
data x;
col1 = "a"; col2 = ""; col3 = "c"; output;
col1 = "" ; col2 = ""; col3 = "c"; output;
col1 = "a"; col2 = ""; col3 = "" ; output;
run;
**
** GET A LIST OF VARIABLE NAMES
*;
proc sql noprint;
select name into :varlist separated by " "
from sashelp.vcolumn
where upcase(libname) eq "WORK"
and upcase(memname) eq "X";
quit;
%put &varlist;
**
** USE A MACRO TO CREATE A DATASTEP. FOR EACH COLUMN THE
** THE DATASTEP WILL CREATE A NEW COLUMN WITH THE SAME NAME
** BUT PREFIXED WITH "DELETE_". IF THERE IS AT LEAST 1
** NON-MISSING VALUE FOR THE COLUMN THEN THE "DELETE" COLUMN
** WILL FINISH WITH A VALUE OF 0, ELSE 1. WE WILL ONLY
** KEEP THE COLUMNS CALLED "DELETE_" AND OUTPUT ONLY A SINGLE
** OBSERVATION TO THE FINAL DATASET.
*;
%macro find_unused_cols(iDs=);
%local cnt;
data vars_to_delete;
set &iDs end=eof;
%let cnt = 1;
%let varname = %scan(&varlist, &cnt);
%do %while ("&varname" ne "");
retain delete_&varname;
delete_&varname = min(delete_&varname, missing(&varname));
drop &varname;
%let cnt = %eval(&cnt + 1);
%let varname = %scan(&varlist, &cnt);
%end;
if eof then do;
output;
end;
run;
%mend;
%find_unused_cols(iDs=x);
**
** GET A LIST OF VARIABLE NAMES FROM THE NEW DATASET
** THAT WE WANT TO DELETE AND STORE TO A MACRO VAR.
*;
proc transpose data=vars_to_delete out=vars_to_delete;
run;
proc sql noprint;
select substr(_name_,8) into :vars_to_delete separated by " "
from vars_to_delete
where col1;
quit;
%put &vars_to_delete;
**
** CREATE A NEW DATASET CONTAINING JUST THOSE VARS
** THAT WE WANT TO KEEP
*;
data new_x;
set x;
drop &vars_to_delete;
run;
Rob and cmjohns, thank you SO MUCH for your help. Based on your solutions and an idea I had over the weekend, here is what I came up with:
%macro removeEmptyCols(origDset, outDset);
* get the number of obs in the original dset;
%let dsid = %sysfunc(open(&origDset));
%let origN = %sysfunc(attrn(&dsid, nlobs));
%let rc = %sysfunc(close(&dsid));
proc transpose data= &origDset out= transpDset;
var _all_;
run;
data transpDset;
set transpDset;
* proc transpose converted all old vars to character,
so the . from old numeric vars no longer means 'missing';
array oldVar_ _character_;
do over oldVar_;
if strip(oldVar_) = "." then oldVar_ = "";
end;
* each row from the old dset is now a column with varname starting with 'col';
numMiss = cmiss(of col:);
numCols = &origN;
run;
proc sql noprint;
select _NAME_ into: varsToKeep separated by ' '
from transpDset
where numMiss < numCols;
quit;
data &outDset;
set &origDset (keep = &varsToKeep);
run;
%mend removeEmptyCols;
I will try all 3 ways and report back on which one is fastest...
P.S. added 23 Dec 2010 for future reference: SGF Paper 048-2010: Dropping Automatically Variables with Only Missing Values
This is very simple method useful for all variables
proc freq data=class nlevels ;
ods output nlevels=levels(where=(nmisslevels>0 and nnonmisslevels=0));
run;
proc sql noprint;
select TABLEVAR into :_MISSINGVARS separated by ' ' from levels;
quit;
data want;
set class (keep=&_MISSINGVARS);
run;