SAS - Exporting same dataset to multiple excel files - sas

I would like to export the dataset to multiple excel files based on a certain variable:
proc sql;
create table try as
select distinct make from sashelp.cars;
quit;
proc sql;
create table try2 as
select count(make) as aaa from sashelp.cars;
quit;
data _null_;
set try;
by make;
call symputx ('make',compress(make,' .'),'g');
run;
data _null_;
set try2;
call symputx('n',aaa);
run;
%macro a;
%do i=1 %to &n;
%let var= %scan(&make,&i,"#");
proc export data=testing (where=(make="&make."))
outfile="C:\Users\&make..xlsx"
dbms=xlsx replace;
sheet="&make." ;
run;
%end;
%mend ;
%a;
My goal is to get all the 38 excel files with the maker name as the filename.
However, all I am able to get here is the last maker name's file.
Would you please point out where I am missing out here? Many thanks!!

Your first error is that you count the number of cars that have a make, while you should count the distinct makes of cars. Now let me also take the opportunity to explain you the into clause of sql, so you don't need that data step anymore
proc sql;
select count(distinct make)
into :make_count
from sashelp.cars;
quit;
You remove blanks and point from your make names, but you better remove all non-alphabetic characters at once, with compress(make, '', 'ka'), in which the options k stands for keep and a stands for alphabetic.
Your main error is that you think you append all make names in the macro variable make, but you actually overwrite make time and again: first you write "Cadillac" to it, then "Chevrolet" and by the time you ever use it, it became "Volvo".
I could explain you how to correct your datastep, but instead, I will learn you an option of that into statement:
proc sql;
select distinct compress(make, '', 'ka')`
into :make_list separated by ' '
from sashelp.cars;
quit;
The rest is easy.
%macro export_by_make;
%do make_nr=1 %to &make_count;
%let make= %scan(&make_list, &make_nr);
proc export data=sashelp.cars (where=(compress(make, '', 'ka')`="&make."))
outfile="C:\Users\&make..xlsx"
dbms=xlsx replace;
sheet="&make." ;
run;
%end;
%mend;
%export_by_make;
Note that you don't need to specify a separator for the %scan function, as we separated by blanks, but anyway, if you do, as you use the macro version of scan, you don't need the quotes around it.

Related

How to trigger a macro for every data set containing a keyword

I'm doing a crash course on SAS macros and I'm stuck at one exercise. I have to create a macro, that will create a proc contents tables for every data set, that contains a keyword. I know how to do that using call execute, but I need this using proc sql and %do loop.
My attempt:
%macro contents(data=&syslast);
proc contents data=&data;
title "&data";
run;
%mend contents;
%macro ContentsAll(keyword);
select libname||'.'||memname
into :dsn1-
from sashelp.vstabvw
where upcase(memname) like %upcase("%quote(%)&&keyword%")
;
quit;
%do i=1 %to &sqlobs;
%contents(data=&&dsn&i);
%end;
%mend ContentsAll;
options mlogic mprint;
%ContentsAll(class);
options nomlogic nomprint;
I know there is some issue with a select statement, but I have no idea how to fix it. And where statement has an unprotected variable (my attempts at fixing it just break the where clause alltogether.
First of all, good job. It's so good that I'm almost sorry you're only missing the Proc SQL Statement :-)
%macro contents(data=&syslast);
proc contents data=&data;
title "&data";
run;
%mend contents;
%macro ContentsAll(keyword);
proc sql noprint;
select libname||'.'||memname
into :dsn1-
from sashelp.vstabvw
where upcase(memname) like %upcase("%quote(%)&&keyword%")
;
quit;
%do i=1 %to &sqlobs;
%contents(data=&&dsn&i);
%end;
%mend ContentsAll;
options mlogic mprint;
%ContentsAll(class);
options nomlogic nomprint;
There is no need to create all of those macro variables. Just keep the list of names in actual data. You can use CALL EXECUTE() to generate the code you want to run for each member in the list.
Note that the variables LIBNAME and MEMNAME will already be in uppercase when pulled from the DICTIONARY.MEMBERS metadata that the view SASHELP.VSTABVW uses. But the user passing in a value for the KEYWORD parameter might not have entered uppercase letters.
%macro ContentsAll(keyword);
data _null_;
set sashelp.vstabvw ;
where memname contains "%qupcase(&keyword)" ;
call execute(cats('%nrstr(%contents)(data=',libname,'.',memname,')'));
run;
%mend ContentsAll;

SAS: create a Macro that add suffix to variables in a dataset

I would like to create a macro that add a suffix to variable names in a dataset. below is my code:
%macro add_suffix(library=,dataset=,suffix=);
proc sql noprint;
select cat(name, ' = ', cats('&suffix.',name )) into :rename_list separated by ' ' from
dictionary.columns where libname = '&library.' and memname= '&dataset.';
quit;
proc datasets library=&library nolist nodetails;
modify &dataset;
rename &rename_list;
run;
quit;
%mend;
%add_suffix(library=OUTPUT,dataset=CA_SPREADS,suffix=CA);
It gives error messages:
NOTE: No rows were selected.
NOTE: PROCEDURE SQL used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
WARNING: Apparent symbolic reference RENAME_LIST not resolved.
NOTE: Line generated by the invoked macro "ADD_SUFFIX".
2 rename &rename_list; run;
-
22
76
NOTE: Enter RUN; to continue or QUIT; to end the procedure.
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
If I put the library and dataset names in quotation mark, it works for the first block i.e. add values to string rename_list but not for the proc dataset step
Macro triggers like % and & are not honored inside of single quotes. That is why you are not getting any hits on your SQL query. There is no library name that has an & as the first character.
The reason it looked like it was sort of working is that when you use this in your SQL statement
catx('=',name,cats('&prefix.',name))
then you end up with a string like
age=&prefix.age
And that will actually work because the reference to the macro variable PREFIX will resolve when you run the RENAME statement.
You should just use double quotes instead.
%macro change_names(library=,dataset=,prefix=,suffix=);
%local rename_list;
proc sql noprint;
select catx('=',name,cats("&prefix",name,"&suffix"))
into :rename_list separated by ' '
from dictionary.columns
where libname = %upcase("&library")
and memname = %upcase("&dataset")
;
quit;
%if (&sqlobs) %then %do;
proc datasets library=&library nolist nodetails;
modify &dataset;
rename &rename_list;
run;
quit;
%end;
%else %put WARNING: Did not find any variables for &library..&dataset..;
%mend change_names;
%change_names(library=OUTPUT,dataset=CA_SPREADS,prefix=CA);
Your macro variables are not being resolved because you're wrapping them in single quotes ' rather than double quotes ".
You should uppercase the libname and memname parameters of your macro as these are always in uppercase in dictionary.columns.
Tested and works. Longer but maybe more beginner friendly approach. Input the dataset name and what suffix you want to add.
example: %add_suffix(orders, _old); /* will add _old suffix to all variables.*/
%macro Add_Suffix(Dataset, suffix);
proc contents noprint
data=work.&dataset out=sjm_tmp(keep=NAME);
run;
data sjm_tmp2;
set sjm_tmp;
foobar=cats(name, '=',NAME,'&suffix.');
run;
proc sql noprint;
select foobar into :sjm_list separated by ' ' from sjm_tmp2;
quit;
proc datasets library = work nolist;
modify &dataset;
rename &sjm_list;
quit;
proc datasets library=work noprint;
delete sjm_tmp sjm_tmp2 ;
run;
%mend Add_Suffix;

SAS loop through datasets

I have multiple tables in a library call snap1:
cust1, cust2, cust3, etc
I want to generate a loop that gets the records' count of the same column in each of these tables and then insert the results into a different table.
My desired output is:
Table Count
cust1 5,000
cust2 5,555
cust3 6,000
I'm trying this but its not working:
%macro sqlloop(data, byvar);
proc sql noprint;
select &byvar.into:_values SEPARATED by '_'
from %data.;
quit;
data_&values.;
set &data;
select (%byvar);
%do i=1 %to %sysfunc(count(_&_values.,_));
%let var = %sysfunc(scan(_&_values.,&i.));
output &var.;
%end;
end;
run;
%mend;
%sqlloop(data=libsnap, byvar=membername);
First off, if you just want the number of observations, you can get that trivially from dictionary.tables or sashelp.vtable without any loops.
proc sql;
select memname, nlobs
from dictionary.tables
where libname='SNAP1';
quit;
This is fine to retrieve number of rows if you haven't done anything that would cause the number of logical observations to differ - usually a delete in proc sql.
Second, if you're interested in the number of valid responses, there are easier non-loopy ways too.
For example, given whatever query that you can write determining your table names, we can just put them all in a set statement and count in a simple data step.
%let varname=mycol; *the column you are counting;
%let libname=snap1;
proc sql;
select cats("&libname..",memname)
into :tables separated by ' '
from dictionary.tables
where libname=upcase("&libname.");
quit;
data counts;
set &tables. indsname=ds_name end=eof; *9.3 or later;
retain count dataset_name;
if _n_=1 then count=0;
if ds_name ne lag(ds_name) and _n_ ne 1 then do;
output;
count=0;
end;
dataset_name=ds_name;
count = count + ifn(&varname.,1,1,0); *true, false, missing; *false is 0 only;
if eof then output;
keep count dataset_name;
run;
Macros are rarely needed for this sort of thing, and macro loops like you're writing even less so.
If you did want to write a macro, the easier way to do it is:
Write code to do it once, for one dataset
Wrap that in a macro that takes a parameter (dataset name)
Create macro calls for that macro as needed
That way you don't have to deal with %scan and troubleshooting macro code that's hard to debug. You write something that works once, then just call it several times.
proc sql;
select cats('%mymacro(name=',"&libname..",memname,')')
into :macrocalls separated by ' '
from dictionary.tables
where libname=upcase("&libname.");
quit;
&macrocalls.;
Assuming you have a macro, %mymacro, which does whatever counting you want for one dataset.
* Updated *
In the future, please post the log so we can see what is specifically not working. I can see some issues in your code, particularly where your macro variables are being declared, and a select statement that is not doing anything. Here is an alternative process to achieve your goal:
Step 1: Read all of the customer datasets in the snap1 library into a macro variable:
proc sql noprint;
select memname
into :total_cust separated by ' '
from sashelp.vmember
where upcase(memname) LIKE 'CUST%'
AND upcase(libname) = 'SNAP1';
quit;
Step 2: Count the total number of obs in each data set, output to permanent table:
%macro count_obs;
%do i = 1 %to %sysfunc(countw(&total_cust) );
%let dsname = %scan(&total_cust, &i);
%let dsid=%sysfunc(open(&dsname) );
%let nobs=%sysfunc(attrn(&dsid,nobs) );
%let rc=%sysfunc(close(&dsid) );
data _total_obs;
length Member_Name $15.;
Member_Name = "&dsname";
Total_Obs = &nobs;
format Total_Obs comma8.;
run;
proc append base=Total_Obs
data=_total_obs;
run;
%end;
proc datasets lib=work nolist;
delete _total_obs;
quit;
%mend;
%count_obs;
You will need to delete the permanent table Total_Obs if it already exists, but you can add code to handle that if you wish.
If you want to get the total number of non-missing observations for a particular column, do the same code as above, but delete the 3 %let statements below %let dsname = and replace the data step with:
data _total_obs;
length Member_Name $7.;
set snap1.&dsname end=eof;
retain Member_Name "&dsname";
if(NOT missing(var) ) then Total_Obs+1;
if(eof);
format Total_Obs comma8.;
run;
(Update: Fixed %do loop in step 2)

Set the labels of a SAS Dataset equal to their variable name

I'm working with a rather large several dataset that are provided to me as a CSV files. When I attempt to import one of the files the data will come in fine but, the number of variables in the file is too large for SAS, so it stops reading the variable names and starts assigning them sequential numbers. In order to maintain the variable names off of the data set I read in the file with the data row starting on 1 so it did not read the first row as variable names -
proc import file="X:\xxx\xxx\xxx\Extract\Live\Live.xlsx" out=raw_names dbms=xlsx replace;
SHEET="live";
GETNAMES=no;
DATAROW=1;
run;
I then run a macro to start breaking down the dataset and rename the variables based on the first observations in each variable -
%macro raw_sas_datasets(lib,output,start,end);
data raw_names2;
raw_names;
if _n_ ne 1 then delete;
keep A -- E &start. -- &end.;
run;
proc transpose data=raw_names2 out=raw_names2;
var A -- &end.;
run;
data raw_names2;
set raw_names2;
col1=compress(col1);
run;
data raw_values;
set raw;
keep A -- E &start. -- &end.;
run;
%macro rename(old,new);
data raw_values;
set raw_values;
rename &old.=&new.;
run;
%mend rename;
data _null_;
set raw_names2;
call execute('%rename('||_name_||","||col1||")");
run;
%macro freq(var);
proc freq data=raw_values noprint;
tables &var. / out=&var.;
run;
%mend freq;
data raw_names3;
set raw_names2;
if _n_ < 6 then delete;
run;
data _null_;
set raw_names3;
call execute('%freq('||col1||")");
run;
proc sort data=raw_values;
by StudySubjectID;
run;
data &lib..&output.;
set raw_values;
run;
%mend raw_sas_datasets;
The problem I'm running into is that the variable names are now all set properly and the data is lined up correctly, but the labels are still the original SAS assigned sequential numbers. Is there any way to set all of the labels equal to the variable names?
If you just want to remove the variable labels (at which point they default to the variable name), that's easy. From the SAS Documentation:
proc datasets lib=&lib.;
modify &output.;
attrib _all_ label=' ';
run;
I suspect you have a simpler solution than the above, though.
The actual renaming step needs to be done differently. Right now it's rewriting the entire dataset over and over again - for a lot of variables that is a terrible idea. Get your rename statements all into one datastep, or into a PROC DATASETS, or something else. Look up 'list processing SAS' for details on how to do that; on this site or on google you will find lots of solutions.
You likely can get SAS to read in the whole first line. The number of variables isn't the problem; it is probably the length of the line. There's another question that I'll find if I can on this site from a few months ago that deals with this exact problem.
My preferred option is not to use PROC IMPORT for CSVs anyway; I would suggest writing a metadata table that stores the variable names and the length/types for the variables, then using that to write import code. A little more work at first, but only has to be done once per study and you guarantee PROC IMPORT isn't making silly decisions for you.
In the library sashelp is a table vcolumn. vcolumn contains all the names of your variables for each library by table. You could write a macro that puts all your variable names into macro variables and then from there set the label.
Here's some code that I put together (not very pretty) but it does what you're looking for:
data test.label_var;
x=1;
y=1;
label x = 'xx';
label y = 'yy';
run;
proc sql noprint;
select count(*) into: cnt
from sashelp.vcolumn
where memname = 'LABEL_VAR';quit;
%let cnt = &cnt;
proc sql noprint;
select name into: name1 - :name&cnt
from sashelp.vcolumn
where memname = 'LABEL_VAR';quit;
%macro test;
%do i = 1 %to &cnt;
proc datasets library=test nolist;
modify label_var;
label &&name&i=&&name&i;
quit;
%end;
%mend test;
%test;

sas add prefix to column names

I need to add a prefix to certain column names in a table. The names at the moment range from _15 to _49 and I would simply like to add the prefix N to give N_15,...,N_49 etc.
I tried the following:
proc sql noprint;
select cats(name,'=','N',name)
into :prefixlist
separated by ' '
from dictionary.columns
where libname = 'WORK' and memname = 'Freq_nais_2006_2010';
quit;
However this does nothing as I just get the message no rows were selected in the log output. What must I change?
Your particular issue is that the WHERE clause is not being fulfilled by any rows, likely because of this: and memname = 'Freq_nais_2006_2010'. Member names are typically capitalized internally in SAS, even if they're not capitalized in your code.
Otherwise your code looks fine, and you should be able to use that &prefixlist. in a PROC DATASETS or data step rename statement. I generally suggest the PROC SQL method as it's easier to customize to specify the variables you want to rename, but of course if you're renaming all of the variables in the dataset the macro works as well.
You're very close:
proc sql noprint;
select cats(name,'=','N',name)
into :prefixlist
separated by ' '
from dictionary.columns
where libname = 'WORK' and memname = 'FREQ_NAIS_2006_2010'
/* and substr(NAME,1,1) = '_' - can add condition on column name pattern */;
quit;
proc datasets lib=WORK nolist nodetails;
modify FREQ_NAIS_2006_2010;
rename
&prefixlist
;
quit;
Changed separator to space for use in PROC DATASETS; MODIFY ... RENAME ... statement.
Side note: the datastep variant answers rewrite the dataset completly, which is ineffective and dangerous for real world usage (big tables), also much less clear on what you're doing.
I managed to find the following code from the sas website (http://support.sas.com/kb/37/433.html):
%macro vars(dsn,chr,out);
%let dsid=%sysfunc(open(&dsn));
%let n=%sysfunc(attrn(&dsid,nvars));
data &out;
set &dsn(rename=(
%do i = 2 %to &n;
%let var=%sysfunc(varname(&dsid,&i));
&var=&chr&var
%end;));
%let rc=%sysfunc(close(&dsid));
run;
%mend vars;
%vars(Freq_nais_2006_2010,N,Freq_nais_2006_2010);
You can just list them in the rename statement in your code, no need for macros or anything else. Though the best idea is avoid it in the first place if you can. See the rename statement below.
data test;
array test(20) _1-_20;
do i=1 to 20;
test(i)=rand('normal', 20);
end;
run;
data test2;
set test;
rename _1-_20 = n_1-n_20;
run;