SAS ODS Query/Statement print along with it's output - sas

SAS EG
Is there any way I can print the query/statement used to get the output, along with the output, using SAS ODS?
Suppose,
ods pdf file=pdfile;
proc sql;
select a.*
from tab1 a inner join tab2 b
on a.something=b.something
where <>
having <>;
quit;
ods _all_ close;
this would print the OUTPUT generated from the above query. But can I also get the query printed via the ods pdf along with the output?

There's no automatic way to redirect the log that I'm aware of.
There are a few ways to get what you want, however.
First off, if you are able to use Jupytr, SAS has plugins to enable that to work with SAS, and then you can simply write in the notebook and run the code, and the results appear with your code just as you want. See Chris Hemedinger's blog post on the subject for more details.
Second, SAS Studio will support a notebook-style interface probably with the next major revision (I believe version 5.0) which will release late next year. So similarly, you would put your code and get your output in the same windows.
Finally, the third option is to do as Reeza suggested - write to a log file, then print that to the output. It's messy but possible.
Here's an example of the latter. I don't make any effort to clean it up, note, you'd probably want to remove the logging related to PROC PRINTTO and the otehr notes (or turn on NONOTE).
ods pdf file="c:\temp\test.pdf";
filename logfile temp;
proc printto log=logfile;
run;
proc sql;
select * from sashelp.class;
quit;
proc printto;
run;
data _null_;
infile logfile;
input #1 #;
call execute(cats('ods text="',trim(_infile_),'";'));
run;
ods _all_ close;

Related

SAS suppress .lst files but keep ODS output

I was doing a PCA analysis with SAS using the following code:
ods output Eigenvectors=PRINCEEV Eigenvalues=PRINCEEVAL;
proc princomp data=REPLACED PLOTS=SCORE(ELLIPSE NCOMP=5) NOPRINT;
id time;
run;
ods output close;
Because the lst files this analysis produces is too large, I used the NOPRINT option. However, it seems that the NOPRINT option also eliminates all of my ODS outputs. (Now PRINCEEV and PRINCEEVAL are all empty):
ERROR: File WORK.PRINCEEVAL.DATA does not exist.
ERROR: Export unsuccessful. See SAS Log for details.
259 putn
_______
1
259 ! ame=YES; run;
WARNING 1-322: Assuming the symbol PUTNAMES was misspelled as putname.
ERROR: File WORK.PRINCEEV.DATA does not exist.
ERROR: Export unsuccessful. See SAS Log for details.
ERROR: Errors printed on page 1.
Is there a way to suppress the generation of lst file, without affecting the ods output?
UPDATE:
It seems that according to the following sas blog, it is not possible to do that:
Can you combine NOPRINT and ODS OUTPUT?
SAS programmers crave efficiency. Upon reading that the NOPRINT option
can make a procedure run faster, the ambitious programmer might
attempt to run a procedure with the NOPRINT option but use the ODS
OUTPUT statement to capture the results of one table. Sorry, friend,
but you can't do that. The NOPRINT option means that no ODS tables are
created, so there is no way to select a table and save it to a data
set.
But the dilemma is, I have limited space on the cloud computing server. The lst files are doing nothing but wasting my spaces. Deleting the lst files when SAS programs are running with external processes will also produce an io error in SAS (I already tried that).
Is there anyway around?
I would suggest:
ods listing close ;
ods output Eigenvectors=PRINCEEV Eigenvalues=PRINCEEVAL;
proc princomp data=REPLACED PLOTS=SCORE(ELLIPSE NCOMP=5) NOPRINT;
id time;
run;
ods output close;
This will close the listing destination, so should work fine.
I noticed in a related blog post, Rick argued for:
ods exclude _all_ ;
http://blogs.sas.com/content/iml/2015/05/28/five-reasons-ods-exclude.html
Minor change to the previous answer: remove the NOPRINT option and after the ODS OUTPUT is created open up the ods listing if you have further code.
ods listing close ;
ods output Eigenvectors=PRINCEEV Eigenvalues=PRINCEEVAL;
proc princomp data=REPLACED PLOTS=SCORE(ELLIPSE NCOMP=5) /*NOPRINT*/;
id time;
run;
ods output close;
ODS LISTING;
If you execute your sas-script on your cloud computing server via bash, then you can send your .lst files to /dev/null:
sas -print /dev/null script.sas
The -print option only effects your .lst, but not any ODS related outputs.

SAS ODS output formatted weird

I'm trying to export a content to Excel. I use the below code but my output excel formatting is horrible.
ods excel file= "&cur_path/&project_name._Proc_Means.xlsx" style=printer ;
proc means data=&this_lib..&this_data;
run;
ods excel close;
The output looks like
The huge blank gap makes the file unreadable. I also find out that it puts all the outputs in the same row instead of many different rows.
Any suggestions on how to fix it?
Thanks in advance.
Assuming you have SAS 9.3+, which you must to use ODS EXCEL, you can add the stackodsoutput option to the PROC MEANS statement; that will give you a much more nicely formatted sheet.
ods excel file= "c:\temp\Proc_Means.xlsx" style=printer ;
proc means data=sashelp.cars stackodsoutput;
run;
ods excel close;
If you have prior to 9.3, you may want to use the OUT= option in PROC MEANS and then output the dataset yourself using PROC EXPORT or PROC PRINT. The default PROC MEANS ODS output is not very table-friendly.

2 proc print output in the same page (listing or rtf)

I am running two proc print and would like to compare them visually on the SAS listing output. Both proc print prints only 3 observations.
The issue is I can't have the 2 output in one and same page...I have to scroll down for one page to another page to look at the other output. I have tried option pagesize=MAX but it doesn't work (MIN neither)...Is there a way to achieve what I want ?
I was wondering if an ODS statement redirecting to RTF or (PDF) would do that ?
Thanks in advance
sas_kappel
Both ODS destinations can give you this, using the startpage=never option, which tells SAS not to start a new page when a new procedure is run.
These output to a results window, rather than the listing output.
option obs=3;
ods pdf startpage=never;
proc print data=sashelp.class;
run;
proc print data=sashelp.class;
run;
ods pdf close;
option obs=max;
For the listing destination, you can replace the pagebreak with another character (e.g. a space) by using the option: option formdlim=' ';.
Thanks Keith.
And would it be possible to have it directly in my output sas window ? I was thinking the ods statement ( as I only need the listing output) It doesn't seems to work:
option startpage=never obs=3;
proc print data=sashelp.class;
run;
proc print data=sashelp.class;
run;
option obs=max;
So I guess there is no other option to achieving this other than ODS statement ?

Grouping plots and tables using a BY statement in SAS

I am using a BY statement with both proc boxplot and proc report to create a plot and a table for each level of the BY variable. As is, the code prints all the plots and then prints all of the tables. I would like it to print the plot and then the table for each level of the By variable (so the ouput would alternate between a plot and a table). Is there a way to do this?
This is the code I currently have for the plots and tables-
proc boxplot data=study;
plot Lead_Time*Study_ID/ horizontal;
by Project_Name;
format Lead_Time dum.;
run;
proc report data=study nowd;
column ID Title Contact Status Message Audience Priority;
by Project_Name;
run;
Thank You!!
Unfortunately, I don't think the ODS (Output Delivery System) can interleave outputs from procedures. You will need to use a macro to loop over all the by variables and call BOXPLOT and REPORT for each one.
Something like this:
%macro myreport();
%let byvars = A B C D;
%let n=4;
%do i=1 %to &n;
%let var = %scan(&byvars,&i);
proc something data=have(where=(byvar="&var"));
...;
run;
proc report data=have(where=(byvar="&var"));
....
run;
%end;
%mend;
%myreport();
Obviously you need to change this to fit your needs. There are plenty of examples on Stackoverflow of it. Here is one: looping over character values in SAS
This is in principle possible using PROC DOCUMENT and the ODS DOCUMENT output type. It's not exactly easy, per se, but it's possible, and has some advantages over the macro option, although I'm not sure sufficient to recommend its use. However, it's worth exploring nonetheless.
First off, this is largely guided (including, coincidentally, using the same dataset!) by Cynthia Zender's excellent tutorial, Have It Your Way: Rearrange and Replay Your Output with ODS DOCUMENT, presented during the 2009 SAS Global Forum. She initially describes a GUI method of doing this, but then later explains it in code, which would clearly be superior for this sort of thing. Kevin Smith covers similar ground in ODS DOCUMENT From Scratch, from 2012's SGF, though Cynthia's paper is a bit more applicable here (as she covers the exact topic).
First, you need to generate all of your results. Order here doesn't matter too much.
I generate a sample of SASHELP.PRDSALE that is sorted appropriately by country.
proc sort data=sashelp.prdsale out=prdsale;
by country;
run;
Then, we generate some tables; a proc means and a sgplot. Note the title uses #BYVAL1 to make sure the title is included - otherwise we lose the useful labels on the procs!
title "#BYVAL1 Report";
ods _all_ close;
ods document name=work.mydoc(write);
proc means data=prdsale sum;
by country;
class quarter year;
var predict;
run;
proc sgplot data=prdsale;
by country;
vbar quarter/response=predict group=year groupdisplay=cluster;
run;
ods document close;
ods preferences;
Now, we have something that is wrong, but is usable for what you actually want. You can use the techniques in Cynthia or Kevin's papers to look into this in detail; for now I'll just go into what you need for this purpose.
It's now organized like this, imagining a folder tree:
\REPORT\MEANS\COUNTRY\
What we need is:
\REPORT\COUNTRY\MEANS
That's easy enough to do. The code to do so is below. Obviously, for a production process this would be better automated; given the input dataset it should be trivial to generate this code. Note that the BYVALs increment for each by value, so CANADA is 1 and 4, GERMANY is 2 and 5, and USA is 3 and 6.
proc document name=work.mydoc_new(write);
make CANADA, GERMANY, USA; *make the lower level folders;
run;
dir ^^; *Go to the bottom level, think "cd .." in unix/windows;
dir CANADA; *go to Canada folder;
dir; *Notes to the Listing destination where we are, not that important;
copy \work.mydoc\Means#1\ByGroup1#1\Summary#1 to ^; *copy that folder from orig doc to here;
copy \work.mydoc\SGPlot#1\ByGroup4#1\SGPlot#1 to ^; *^ being current directory, like '.' in unix/windows;
*You could also copy \ByGroup1#1 and \Bygroup4#1 without the last level of the tree. That would give a slightly different result (a bit more of the text around the table would be included), so do whichever matches your expectations.;
**Same for Germany and USA here. Note that this is the part that would be easy to automate!;
dir ^^;
dir GERMANY;
dir;
copy \work.mydoc\Means#1\ByGroup2#1\Summary#1 to ^;
copy \work.mydoc\SGPlot#1\ByGroup5#1\SGPlot#1 to ^;
dir ^^;
dir USA;
dir;
copy \work.mydoc\Means#1\ByGroup3#1\Summary#1 to ^;
copy \work.mydoc\SGPlot#1\ByGroup6#1\SGPlot#1 to ^;
run;
quit; *this is one of those run group procedures, need a quit;
Now, you only have to replay the document to get it out the right way.
proc document name=mydoc_new;
replay;
run;
quit;
Tada, you have what you want.
If you're going to run the procs once per by value, that's pretty easy. Create a macro to run just one instance, then use proc sql to create a call for each instance. That is entirely dynamic, and could be easily adjusted to allow for other options such as multiple by variables, levels, etc.
Given a single by value:
*Macro that runs it once;
%macro run_reports(project_name=);
title "Report for &project_name.";
proc boxplot data=study;
plot Lead_Time*Study_ID/ horizontal;
where Project_Name="&project_name.";
format Lead_Time dum.;
run;
proc report data=study nowd;
column ID Title Contact Status Message Audience Priority;
where Project_Name="&project_name.";
run;
%mend run_Reports;
*SQL pull to create a list of macro calls;
proc sql;
select distinct cats('%run_Reports(project_name=',project_name,')')
into :runlist separated by ' '
from study;
quit;
&runlist.;
Turn options symbolgen; on to see what the runlist looks like, or look at your output window (or results window in 9.3+). When you're running this in production, add noprint to proc sql to avoid generating that table.

What is the easiest way to ODS a variable's value to an rtf file in SAS

What is the easiest way to ODS a variable's value to an rtf file
without tables or columns displayed.
I would prefer not to have to use macros to do this also if it is possible.
Thanks in advance my friends
Two options:
ods proctitle=no;
options nodate nonumber;
title;
ods rtf file="d:\temp\test.rtf" style=minimal;
proc print data=sashelp.class(where=(name='Alfred')) noobs label style={borderstyle=none};
var age;
label age='00'x;
run;
data _null_;
set sashelp.class;
where name='Alfred';
declare odsout ODS();
ods.format_text(data:age);
run;
ods rtf close;
The latter does exactly what you want, but isn't exactly supported for RTF (see the log). The former does more or less what you want; there is still a header row, but it's blank.
As pointed out in comments, this is not exactly the same question if there is more context to this.
If ODS is not a requirement, and you are just after the simplest and fastest method, you could always write the values directly to a file with a .rtf extension. This would result in the cleanest RTF output as well if that is desired.
Simple version:
data _null_;
file "c:\test.rtf";
put "Hello";
run;
Depends on who the recipient of the file and how they are going to consume it among other things. The file produced will open perfectly fine in say Word and probably most .RTF readers.
More complex versions:
If you need valid .RTF syntax, you could also add the minimum syntax for a valid RTF, which from what I can gather would look something like this:
{\rtf1\ansi\f0
Hello
}
Code to produce the above:
data _null_;
file "c:\test.rtf";
put "{\rtf1\ansi\f0";
put "Hello";
put "}";
run;
If you had a variable in a table you could print them all to an .rtf file like this:
data _null_;
file "c:\test.rtf";
set mytable end=eof;
if _n_ eq 1 then do;
put "{\rtf1\ansi\f0";
end;
put my_variable;
if eof then do;
put "}";
end;
run;
References used: http://www.biblioscape.com/rtf15_spec.htm and wikipedia.
If you already have some other text that's going into your rtf and you need your variable's value to be placed inline, then I think the most sensible approach is to store the value in a macro variable and put that in the middle of the text in your rtf. If you are keen to keep macro logic to a minimum, you can generate the macro variable via call symput in a data step or select into via proc sql depending on your preference. E.g.
proc sql noprint;
select Age
into :Alfred_age
from sashelp.class
where name = 'Alfred';
quit;
ods listing close;
ods rtf file = "C:\temp\test1.rtf";
ods rtf text = "Alfred's age is %sysfunc(strip(&Alfred_age)).";
ods rtf close;
ods listing;