Change sheet names in SAS ODS dynamically - excel

Is there a way to dynamically change the sheet names in SAS ODS using macros? Inside a macro, I have the following code:
%DO N = 1 %TO &SQLOBS;
ODS TAGSETS.ExcelXP
OPTIONS(SHEET_NAME = &&TABLEVAR&N
EMBEDDED_TITLES = 'NO'
EMBEDDED_FOOTNOTES = 'NO'
PRINT_HEADER = 'CURRENT &&TABLEVAR&N AS OF &D';
PROC PRINT
DATA = WORK.&&TABLEVAR&N
NOOBS;
RUN;
%END;
Which basically loops through an array of table names and for each table outputs a separate Excel Sheet. And by calling a macro variable &TABLEVAR, it is supposed to dynamically change the sheet name for each table. However, I get the following error:
ERROR 22-322: Expecting a quoted string.
So SAS does resolve the macro variable, I can see the table names are right in the log, but the problem is they are not a quoted string. So I tried it like this with quotes around it:
OPTIONS(SHEET_NAME = '&&TABLEVAR&N'
But then the macro variable is not being resolved by SAS and all the sheets are named &&TABLEVAR&1, 2, etc.
Is there a way around this? (And each sheet is a separate table with a different name so I can't use a BY group.) What good macros are if they can't interact with ODS?

I just found out placing %sysfunc and quote in front of &&TABLEVAR&N does the trick.
So the final code looks like this:
%DO N = 1 %TO &SQLOBS;
ODS TAGSETS.ExcelXP
OPTIONS(SHEET_NAME = %sysfunc(quote(&&TABLEVAR&N))
EMBEDDED_TITLES = 'NO'
EMBEDDED_FOOTNOTES = 'NO'
PRINT_HEADER = 'CURRENT &&TABLEVAR&N AS OF &D';
PROC PRINT
DATA = WORK.&&TABLEVAR&N
NOOBS;
RUN;
%END;

I don't think you need all that. If you use double quotes around the macro variable expression, you should get the same result as the %SYSFUNC(QUOTE()) function:
"&&TABLEVAR&N"
That is, unless you somehow plan to have double quotes in the sheet names....

Related

SAS export subset of column to worksheet with a column name

Given a SAS dataset with columns named n1,n2,..nN.
Is there a simple way to export common set of columns and unique subset of columns to a workbook, where each column is exported to the worksheet with the same name as the last column name?
Example:
For the SAS dataset above, the columns:
n1, n2, n5 -> Worksheet n5
n1, n2, n9 -> Worksheet n9
n1, n2, n13 -> Worksheets n13
are exported to a Excel workbook, with worksheets named as above.
Appreciate any suggestions.
Use the SHEET= statement in a Proc EXPORT step.
For example:
filename myxl 'c:\temp\sandbox.xlsx';
proc export replace file=myxl dbms=excel
data=sashelp.class (keep=name)
;
sheet='Name';
run;
proc export replace file=myxl dbms=excel
data=sashelp.class (keep=name age weight)
;
sheet='Weight';
run;
A macro can be coded to generate repetitive parts
%macro excel_push (file=, data=, always=, each=);
%local i n var;
%let n = %sysfunc(countw(&each));
%do i = 1 %to &n;
%let var = %scan(&each,&i);
proc export replace file=&file dbms=excel
data=&data(keep=&always &var)
;
sheet="&var";
run;
%end;
%mend;
options mprint;
filename myxl2 'c:\temp\sandbox2.xlsx';
%excel_push (
file=myxl2,
data=sashelp.class,
always=name age sex,
each=height weight
)
If you open the Excel output, leave it open and rerun the code, you will get an error, albeit slightly obscure:
ERROR: Error attempting to CREATE a DBMS table. ERROR: Execute: The Microsoft Access database
engine could not find the object ********. Make sure the object exists and that you spell
its name and the path name correctly. If ******** is not a local object, check your
network connection or contact the server administrator..
I guess, What I understood from your question is how to have sheet name with last variable of SAS dataset. One way to do this is to use dictionary.columns and find which column position (varnum in dictionary.columns)is max in a dataset which will give the last variable and you can make a macro variable out of this and use this for sheet in proc export.
/* pick up the last variable*/
proc sql ;
select name into :mysheet TRIMMED from dictionary.columns
where libname = "SASHELP"
and memname = "CLASS"
and varnum = (select max(varnum) from dictionary.columns
where libname = "SASHELP"
and memname = "CLASS");
/* use the macrovariable in your sheet statement*/
PROC EXPORT DATA= Sashelp.Class /*Sheet 1*/
outfile= "/folders/myfolders/class.xlsx "
dbms=xlsx replace;
sheet="&mysheet";
run;

SAS PROC IMPORT with Dynamic Named Ranges

I have a workbook containing database information split into various tabs that I want to import into SAS for further manipulation.
The dimensions of these database tables vary from tab to tab and in addition, there might be further changes to the dimensions of the table throughout the tenure of this project.
As such, I've set up dynamic named ranges in every tab such that it picks up the exact dimensions I require i.e. My named range TBL_SHEET1
=OFFSET(SHEET1!$A$1,0,0,COUNTA(SHEET1!$A:$A),COUNTA(SHEET1!$1:$1)-2)
Now the SAS issue is that when I use the proc import code, it does not seem to think these named ranges "exist" and returns a failure message in the log.
Is there a way around this? Or just a limitation in the way SAS communicates with Excel?
(As a side note, the reason why I am not just importing the entire tab is because there are some extra columns to the right which I want to exclude from my import)
My SAS import code:
proc import
out= rwork.SHEET1
datafile = "C:\My User\Sample.xlsx"
dbms= excel replace;
range=TBL_SHEET1.;
getnames=yes;
run;
Adding your code will really help. However, if I understood your question correct, the problem is with importing named ranges. Here's an example on how to import user defined named range into SAS.
In the example below, I am selecting Range A1:C7 and naming the range as Test
Now, to import the names range Test into SAS,
PROC IMPORT OUT= WANT
DATAFILE= "C:\Desktop\Test.xls"
DBMS=XLS REPLACE;
RANGE="Test";
GETNAMES=YES;
RUN;
In the above code, Range tells SAS to import data from excel under named range Test. You can also automate the code and import multiple excel sheets with dynamic name range into SAS.
For example, I have multiple sheets in an Excel which I want to import into SAS, I'll use the following code to bring the spreadsheets to SAS.
# Assigning Library #
Libname Test "C:\Desktop\Test.xls";
# Creating a table to select sheet names in above library #
Proc sql;
create table test_excel as
select *
from dictionary.tables
where libname="Test" ;
Quit;
# Assigning count and Sheet names to macros#
## Sheet names ##
Proc sql;
select memname into: list seperated by '*'
from test_excel
;
quit;
## Number of sheets ##
Proc sql;
select count(memname) into:count
from test_excel
;
quit;
# Macro to import #
options mprint;
%macro import;
%do i=1 %to &count;
%let var=%scan(&list,&i,*);
PROC IMPORT OUT= %substr(&var,1,%length(&var)-1)
DATAFILE= "C:\Desktop\Test.xls"
DBMS=EXCEL REPLACE;
RANGE="&var";
GETNAMES=YES;
RUN;
%end;
%mend import;
%import ;
I hope this is clear.
I replicated the problem and also can't import a dynamic (not fixed) range from Excel. It doesn't work on either EXCEL or XLSX engines.
If you need a quick solution and don't necessarily have to use Excel ranges, here is a workaround for the problem:
Allocate one sheet's cell, e.g A1 for storing what you currently store in the dynamic range TBL_sheet1, i.e. the range of cells to import.
Execute proc import only to import this range of cells:
PROC IMPORT DATAFILE="C:\test.xlsx" DBMS=xlsx out=work.x
replace;
getnames=no;
RANGE="Sheet1$A1:A1";
RUN;
Next, put the imported range to a macro variable and execute proc import again with proper RANGE= definition.
%let range=;
proc sql noprint;
select A into :range
from work.x;
quit;
%let range = %sysfunc(compress(&range.,$,));
%put &range.;
/* resolves to: B2:B4 */
%macro import(x);
PROC IMPORT DATAFILE="C:\test.xlsx" DBMS=xlsx out=work.y
replace;
getnames=no;
RANGE="Sheet1$&x.";
RUN;
%mend;
%import(&range.);

Export one SAS table into multiple Excel worksheets based on field value

I'm trying to export one SAS table into multiple Excel worksheets based on the value of a field (parent_account). I want each worksheet to be named the same as the parent_account. I'm using the following code that I found at http://www.tek-tips.com/viewthread.cfm?qid=1335588, but I'm getting these error messages:
A character operand was found in the %EVAL function or %IF condition where a numeric operand is required.
Argument 2 to macro function %SCAN is not a number.
%macro export_to_excel();
%local varlist idx var;
proc sql noprint;
select distinct parent_account into: varlist separated by '||'
from todays_activity;
quit;
%let idx = 1;
%do %while ( %scan(&varlist, &idx, %str(||)) ne %str() );
%let var=%scan(&varlist, &idx, %str(||));
proc export data=sashelp.class (where=(parent_account="&var"))
outfile='My file location\Report.xls'
dbms=excel;
sheet="&var";
quit;
%let idx = %eval(&idx + 1);
%end;
%mend export_to_excel;
%export_to_excel;
You could try using ODS EXCEL. Here is an example use SASHELP.CLASS dataset.
First make sure the data is sorted by your grouping variables.
proc sort data=sashelp.class out=class ;
by sex ;
run;
Set up ODS to point to your target file. Tell it to make a new sheet for each BY group.
ods excel file="&path/class.xlsx" ;
ods excel options
(sheet_interval="bygroup"
suppress_bylines="yes"
sheet_name='GENDER'
);
You also might want to turn off other output destinations.
Then print the file using PAGEBY option. And close the ODS EXCEL destination
proc print data=class noobs;
by sex ;
pageby sex ;
var _all_;
run;
ods excel close;
To test it you could try reading it back in as data. But watch out that it will create member names with embedded spaces.
options validmemname=extend;
libname xx xlsx "&path/class.xlsx";
proc copy inlib=xx outlib=work; run;
libname xx clear ;
From SAS log
NOTE: The data set WORK.GENDER has 9 observations and 5 variables.
NOTE: The data set WORK.'GENDER 2'n has 10 observations and 5 variables.
This might be helpful
%macro export_to_excel;
proc sql noprint;
select distinct parent_account into: varlist separated by '#' from todays_activity;
select count(distinct parent_account) into:n from todays_activity;
quit;
%do i=1 %to &n;
%let var= %scan(&varlist,&i,"#");
proc export data=sashelp.class (where=(parent_account="&var"))
outfile='Your file location\Report.xls'
dbms=excel;
sheet="&var";
run;
%end;
%mend export_to_excel;

Updating a variable in naming a file in SAS

I'm somewhat new to SAS. I'm trying to update a file name to write an excel file through a loop, but am having trouble assigning the file name. Here's my code:
%MACRO loop;
%DO year1 = 1995 %TO 2008;
DATA _NULL_;
dailyret = catx(STRIP(&year1),
'''/h1/usr11/angeli/finland/haz/phreg_dailyret_', '.csv''');
*to save output to excel;
ODS TAGSETS.EXCELXP
file= %QUOTE(dailyret)
STYLE=minimal
OPTIONS ( Orientation = 'landscape'
FitToPage = 'yes'
Pages_FitWidth = '1'
Pages_FitHeight = '100' );
*a block of code that runs the program, irrelevant to my question;
ods tagsets.excelxp close;
RUN;
%END;
%MEND loop;
%loop;
I have tried many variations of this, but every time, I always get an error message along the lines of "ERROR: No logical assign for filename DAILYRET".
Is there any way I can do this so that I don't have to put physical quotes in the line with "file=" and be able to update the year?
Thank you so much!
-Angel
To reference a macro variable prefix it with an &. Make sure to use double quote characters " to quote the filename since macro triggers are not resolved inside of single quotes.
You can also optionally append a period to the macro name so that the parser knows where the macro variable name ends and regular text starts again. This means that when you want to append an extension that starts with a period you need to have two periods since the first will be used to mark the end of the macro variable reference.
%MACRO loop;
%DO year1 = 1995 %TO 2008;
ODS TAGSETS.EXCELXP
file= "/h1/usr11/angeli/finland/haz/phreg_dailyret_&year1..xml"
STYLE=minimal
OPTIONS ( Orientation = 'landscape'
FitToPage = 'yes'
Pages_FitWidth = '1'
Pages_FitHeight = '100'
)
;
*-------------------;
*a block of code that runs the program, irrelevant to my question;
*-------------------;
ods tagsets.excelxp close;
%END;
%MEND loop;

SAS- Defining Page Breaks inside Macro

I am wondering if there is a way of defining when and where page breaks occur when using a macro to output data. I know within the various ODS tagests the "Startpage=NOW" can be used but that does not seem to work if a macro is used inside that tagset. So basically I want the two tables, and graph for each personal ID code to be on a single and the next page contains the same summary graphs, charts for that individual, and etc. Currently I can only get every table and chart to its own individual page which makes for a lengthy report! Any help/suggestions would be appreciated!
/*************************************************************************/
/* Create a macro variable of all the ID codes */
/* */
/*************************************************************************/
proc sql noprint;
select personal_id
into :varlist separated by ' ' /*Each identifier code in the list is sep. by a single space*/
from provider;
quit;
%let cntlist = &sqlobs; /*Store a count of the number of id codes*/
%put &varlist; /*Print the codes to the log to be sure our list is accurate*/
ods tagsets.rtf file="C:\USER\test.doc" style=sasdocprinter;
/* macro for generating the output table*/
%macro output(x);
proc print data=prov_&x;
run;
proc print data=prov_revCD_&x;
run;
/*Print graph to template defined earlier*/
ods graphics on / height=500px width=500px;
proc sgrender data=summary_&x template=corf_graphs;
run;
ods graphics on / reset=all;
%mend;
%macro loopit(mylist);
%let else=;
%let n = %sysfunc(countw(&mylist)); /*let n=number of codes in the list*/
data
%do I=0 %to &n;
%let val = %scan(&mylist,&I); /*Let val= the ith code in the list*/
%end;
/*Run a loop for each oscar code. Each code will enter the
%do j=0 %to &n;
%let val = %scan(&mylist,&j); /*Let val= the jth code in the list*/
/*Run the macro loop to generate the required tables*/
%runtab(&val);
%output(&val);
%end;
run;
%mend;
%loopit(&varlist)
/*Run the macro loop over the list of significant procedure code values*/
ods tagsets.rtf close;
Macros are nothing but source code generation devices. They have no impact on the effectiveness of any particular technique, except insomuch as their (mis)use may make it more difficult to see how to do it properly.
In this case, if you want the entirety of each single iteration of %output() to be on one page, then you simply need to add a startpage now before the %output(&val) call.
%runtab(&val);
ods tagsets.rtf startpage=now;
%output(&val);
%end;
That will produce a startpage instruction immediately before the output. You could just as easily include it in the %output macro, if you will always want a new page before a call of it.
I inserted the startpage=NOW where suggested and it inserted a blank page, after playing around with the placement and looking at what the code is doing this following snip it ended up working:
ods tagsets.rtf startpage=NOW;
%runtab(&val);
%output(&val);
ods tagsets.rtf startpage=no;
%end;
Beyond the use of macro, you can also consider the use of pagebywith proc print to split page for you.
See this link for PageBY

Resources