<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?>

<feed xmlns="http://purl.org/atom/ns#" version="0.3" xml:lang="en-US">
<link href="https://www.blogger.com/atom/9861540" rel="service.post" title="Jack Hamilton's SAS Code Log" type="application/atom+xml"/>
<link href="https://www.blogger.com/atom/9861540" rel="service.feed" title="Jack Hamilton's SAS Code Log" type="application/atom+xml"/>
<title mode="escaped" type="text/html">Jack Hamilton's SAS Code Log</title>
<tagline mode="escaped" type="text/html">&lt;p&gt;SAS code and pointers.&lt;/p&gt;

&lt;p&gt;To see more entries, select an earlier month from the archive list.&lt;/p&gt;

&lt;p&gt;Note: depending on its mood, Blogger might display  all the text for an index entry instead of just the first 250 characters.  If it's in the mood to display everything, reading will be difficult.  It also moves items around from time to time for no apparent reason.  &lt;/p&gt;</tagline>
<link href="http://www.excursive.com/sas/weblog/" rel="alternate" title="Jack Hamilton's SAS Code Log" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540</id>
<modified>2006-11-04T21:10:14Z</modified>
<generator url="http://www.blogger.com/" version="6.72">Blogger</generator>
<info mode="xml" type="text/html">
<div xmlns="http://www.w3.org/1999/xhtml">This is an Atom formatted XML site feed. It is intended to be viewed in a Newsreader or syndicated to another site. Please visit the <a href="http://help.blogger.com/bin/answer.py?answer=697">Blogger Help</a> for more info.</div>
</info>
<convertLineBreaks xmlns="http://www.blogger.com/atom/ns#">false</convertLineBreaks>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/113096904628825931" rel="service.edit" title="Repeating group values in PROC REPORT" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-11-02T13:36:00-08:00</issued>
<modified>2006-01-28T07:34:45Z</modified>
<created>2005-11-02T22:04:06Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/11/repeating-group-values-in-proc-report.html" rel="alternate" title="Repeating group values in PROC REPORT" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-113096904628825931</id>
<title mode="escaped" type="text/html">Repeating group values in PROC REPORT</title>
<content mode="escaped" type="text/html" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">&lt;p&gt;I was working on a summary report with several group variables.  As you know, when you display group (or order) variables in PROC REPORT, only the first row containing a particular value with display that value; following rows will show a blank.  This sample program and PROC REPORT show the problem (the sample data are somewhat complex; ignore that and take my word for it that the test data set will contain repetitions of the group variables HasMemberships, ProdLineMatch, ProdLine, and Mbsh_MCProd):&lt;/p&gt;

&lt;pre&gt;
data test;
   drop i;
   do HasMemberships = 'y', 'n', 'y';
      do ProdLineMatch = 'y', 'n', 'y';
         if ranuni(92345) &gt; .4 then 
            do ProdLine = 'M', 'C', 'R', ' ';
               do Mbsh_MCProd = 'C', 'R', 'M', ' ';
                  if ranuni(87890) &gt; .6 then 
                     do i = 1 to round(ranuni(95605)*5-1);
                        _freq_ = round(ranuni(94612)*100);
                        if hasmemberships = 'n' then 
                           do;
                           mbsh_mcprod = ' ';
                           prodlinematch = 'n';
                           end;
                        output;
                     end;
               end;
            end;
      end;
   end;
run;

title 'Default Display of Repeated Group Values';

proc report data=test missing nofs;
   column HasMemberships ProdLineMatch ProdLine Mbsh_MCProd
          _freq_;

   define HasMemberships / group width=14;
   define ProdLineMatch  / group width=13;
   define ProdLine       / group width=8;
   define Mbsh_MCProd    / group width=11;

   define _freq_ / sum format=comma12.0 width=12 'Freq';

run;
&lt;/pre&gt;

&lt;p&gt;displays:&lt;/p&gt;

&lt;style type="text/css"&gt;
.Header
{
  font-family: Arial, Helvetica, sans-serif;
  font-size: x-small;
  font-weight: bold;
  font-style: normal;
  color: #FFFFFF;
  background-color: #6495ED;
}
.Data
{
  font-family: Arial, Helvetica, sans-serif;
  font-size: x-small;
  font-weight: normal;
  font-style: normal;
  color: #000000;
  background-color: #FFFFFF;
}
.Table
{
  font-family: Arial, Helvetica, sans-serif;
  font-size: x-small;
  font-weight: normal;
  font-style: normal;
  color: #003399;
  background-color: #CCCCCC;
}
.l {text-align: left }
.c {text-align: center }
.r {text-align: right }
&lt;/style&gt;

&lt;table class="Table" border="1" summary="Proc Report: Detailed and/or summarized report"&gt;
&lt;colgroup&gt;
&lt;col&gt;
&lt;col&gt;
&lt;col&gt;
&lt;col&gt;
&lt;col&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class="c Header" scope="col"&gt;HasMemberships&lt;/th&gt;
&lt;th class="c Header" scope="col"&gt;ProdLineMatch&lt;/th&gt;
&lt;th class="c Header" scope="col"&gt;ProdLine&lt;/th&gt;
&lt;th class="c Header" scope="col"&gt;Mbsh_MCProd&lt;/th&gt;
&lt;th class="c Header" scope="col"&gt;Freq&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;n&lt;/td&gt;
&lt;td class="l Data"&gt;n&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="r Data"&gt;         281&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;C&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="r Data"&gt;         479&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;M&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="r Data"&gt;         252&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;R&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="r Data"&gt;         689&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;y&lt;/td&gt;
&lt;td class="l Data"&gt;n&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;M&lt;/td&gt;
&lt;td class="r Data"&gt;         175&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;R&lt;/td&gt;
&lt;td class="r Data"&gt;         151&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;C&lt;/td&gt;
&lt;td class="l Data"&gt;R&lt;/td&gt;
&lt;td class="r Data"&gt;         123&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;M&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="r Data"&gt;         143&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;M&lt;/td&gt;
&lt;td class="r Data"&gt;           7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;R&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="r Data"&gt;         243&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;y&lt;/td&gt;
&lt;td class="l Data"&gt;C&lt;/td&gt;
&lt;td class="l Data"&gt;C&lt;/td&gt;
&lt;td class="r Data"&gt;          61&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;M&lt;/td&gt;
&lt;td class="r Data"&gt;         191&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;M&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="r Data"&gt;         175&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;R&lt;/td&gt;
&lt;td class="l Data"&gt;C&lt;/td&gt;
&lt;td class="r Data"&gt;         235&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;As you can see, it can be difficult to tell which real values are behind each cell.  Having the group values repeat in each cell would make the table much easier to read.  Unfortunately, in SAS 9.1 there's no way to make that happen automatically.&lt;/p&gt;

&lt;p&gt;You can, however, do it by using  compute blocks.  I'm not going to explain the code, but here's an example:&lt;/p&gt;

&lt;pre&gt;
proc report data=test missing nofs;
   column HasMemberships ProdLineMatch ProdLine Mbsh_MCProd
          _freq_;

   define HasMemberships / group width=14;

   define ProdLineMatch  / group width=13;
   compute before ProdLinematch;
      hold_ProdLinematch = ProdLinematch;
   endcomp;
   compute ProdlineMatch;
      if _break_ = ' ' then
         ProdLinematch = hold_ProdLinematch;
   endcomp;

   define ProdLine       / group width=8;
   compute before ProdLine;
      hold_ProdLine = ProdLine;
   endcomp;
   compute Prodline;
      if _break_ = ' ' then
         do;
         ProdLine = hold_ProdLine;
         end;
   endcomp;

   define Mbsh_MCProd    / group width=11;

   define _freq_ / sum format=comma12.0 width=12 'Freq';

run;
&lt;/pre&gt;

&lt;p&gt;displays:&lt;/p&gt;

&lt;table class="Table" border="1" summary="Proc Report: Detailed and/or summarized report"&gt;
&lt;colgroup&gt;
&lt;col&gt;
&lt;col&gt;
&lt;col&gt;
&lt;col&gt;
&lt;col&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class="c Header" scope="col"&gt;HasMemberships&lt;/th&gt;
&lt;th class="c Header" scope="col"&gt;ProdLineMatch&lt;/th&gt;
&lt;th class="c Header" scope="col"&gt;ProdLine&lt;/th&gt;
&lt;th class="c Header" scope="col"&gt;Mbsh_MCProd&lt;/th&gt;
&lt;th class="c Header" scope="col"&gt;Freq&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;n&lt;/td&gt;
&lt;td class="l Data"&gt;n&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="r Data"&gt;         281&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;n&lt;/td&gt;
&lt;td class="l Data"&gt;C&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="r Data"&gt;         479&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;n&lt;/td&gt;
&lt;td class="l Data"&gt;M&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="r Data"&gt;         252&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;n&lt;/td&gt;
&lt;td class="l Data"&gt;R&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="r Data"&gt;         689&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;y&lt;/td&gt;
&lt;td class="l Data"&gt;n&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;M&lt;/td&gt;
&lt;td class="r Data"&gt;         175&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;n&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;R&lt;/td&gt;
&lt;td class="r Data"&gt;         151&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;n&lt;/td&gt;
&lt;td class="l Data"&gt;C&lt;/td&gt;
&lt;td class="l Data"&gt;R&lt;/td&gt;
&lt;td class="r Data"&gt;         123&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;n&lt;/td&gt;
&lt;td class="l Data"&gt;M&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="r Data"&gt;         143&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;n&lt;/td&gt;
&lt;td class="l Data"&gt;M&lt;/td&gt;
&lt;td class="l Data"&gt;M&lt;/td&gt;
&lt;td class="r Data"&gt;           7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;n&lt;/td&gt;
&lt;td class="l Data"&gt;R&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="r Data"&gt;         243&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;y&lt;/td&gt;
&lt;td class="l Data"&gt;C&lt;/td&gt;
&lt;td class="l Data"&gt;C&lt;/td&gt;
&lt;td class="r Data"&gt;          61&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;y&lt;/td&gt;
&lt;td class="l Data"&gt;C&lt;/td&gt;
&lt;td class="l Data"&gt;M&lt;/td&gt;
&lt;td class="r Data"&gt;         191&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;y&lt;/td&gt;
&lt;td class="l Data"&gt;M&lt;/td&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="r Data"&gt;         175&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="l Data"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td class="l Data"&gt;y&lt;/td&gt;
&lt;td class="l Data"&gt;R&lt;/td&gt;
&lt;td class="l Data"&gt;C&lt;/td&gt;
&lt;td class="r Data"&gt;         235&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/111532718904986882" rel="service.edit" title="Mixing Means and PctNs in a column in PROC REPORT" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-05-05T14:01:00-07:00</issued>
<modified>2006-01-28T07:36:48Z</modified>
<created>2005-05-05T21:06:29Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/05/mixing-means-and-pctns-in-column-in.html" rel="alternate" title="Mixing Means and PctNs in a column in PROC REPORT" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-111532718904986882</id>
<title mode="escaped" type="text/html">Mixing Means and PctNs in a column in PROC REPORT</title>
<content mode="escaped" type="text/html" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">&lt;p&gt;In a posting to SAS-L (&lt;http://www.listserv.uga.edu/cgi-bin/wa?A2=ind0505A&amp;L=sas-l&amp;P=R22768&gt;), Janina Nowles
asked how to create a report where a column contains PctN at the detail level but the Mean at the summary level.  The code below comes pretty close; the data required some preprocessing to make it suitable.&lt;/p&gt;

&lt;pre&gt;
dm 'clear log; clear out;';

proc format; 
   value ldfmt 
      9 ='Like Extremely' 8 ='Like very much' 
      7 ='Like moderately' 6 ='Like slightly' 
      5 ='Neither like nor dislike' 4 ='Dislike slightly' 
      3 ='Dislike moderately' 2 ='Dislike very much' 
      1 ='Dislike extremely'
      ;
run;

data raw; 
   infile cards; 
   input id sample q1 q2; 
   label q1='constr' q2='extrus'; 
cards; 
1 558 6 8 
1 911 5 6 
2 911 8 8 
2 558 7 7 
3 558 7 8 
3 911 8 8 
4 911 7 7 
4 558 7 7 
5 558 7 6 
5 911 8 9 
6 911 6 8 
6 558 6 8
;;;; 

data normal;
   set raw;
   array qnames{2} $ _temporary_ ('q1' 'q2');
   array qvals{2} q1 q2;
   do i = 1 to 2;
      q = qnames{i};
      value = qvals{i};
      select (sample);
         when (558) sample_558 = value;
         when (911) sample_911 = value;
         otherwise  error 'Unknown sample type';
      end;
      output;
   end;
   drop q1 q2 i;
run;

proc report data=normal missing nowindows nocenter split='!';

   column q value ('- Sample -' 
                   sample_558 sample_558=sample_558_n show_558 
                   sample_911 sample_911=sample_911_n show_911);

   define q / group;
   define value        / group format=ldfmt. order=internal descending;
   define sample_558   / analysis mean format=10.0 '558 Mean' noprint;
   define sample_911   / analysis mean format=10.0 '911 Mean' noprint;
   define sample_558_n / analysis n format=10.1 '558 N' noprint;
   define sample_911_n / analysis n format=10.1 '911 N' noprint;
   define show_558     / computed format=10.0 '558!%';
   define show_911     / computed format=10.0 '911!%';
   
   compute before q;
      n_558 = sample_558_n;
      n_911 = sample_911_n;
   endcomp;

   break after q / summarize skip dol;

   compute show_558;
      if _break_ = ' ' then 
         do;
         if min(sample_558_n, n_558) = 0 then 
            show_558 = .;
         else 
            show_558 = 100 * sample_558_n / n_558;         
         end;
      else 
         do;
         show_558 = sample_558.mean;
         call define(_col_, 'format', '10.1');
         end;
   endcomp;

   compute show_911;
      if _break_ = ' ' then 
         do;
         if min(sample_911_n, n_911) = 0 then 
            show_911 = .;
         else 
            show_911 = 100 * sample_911_n / n_911;         
         end;
      else 
         do;
         show_911 = sample_911.mean;
         call define(_col_, 'format', '10.1');
         end;
   endcomp;

   compute after q;
      q = 'Mean';
   endcomp;

run;
&lt;/pre&gt;

&lt;p&gt;Here is the output:&lt;/p&gt;

&lt;pre&gt;
                                      ------- Sample -------
                                             558         911
  q                            value           %           %
  q1        Like very much                     .          50
            Like moderately                   67          17
            Like slightly                     33          17
            Neither like nor dislike           .          17
  ========                            ==========  ==========
  Mean                                       6.7         7.0

  q2        Like Extremely                     .          17
            Like very much                    50          50
            Like moderately                   33          17
            Like slightly                     17          17
  ========                            ==========  ==========
  Mean                                       7.3         7.7
&lt;/pre&gt;</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/111471882215849539" rel="service.edit" title="Dynamically generated PIPEs" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-04-28T13:00:00-07:00</issued>
<modified>2006-01-28T07:37:10Z</modified>
<created>2005-04-28T20:07:02Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/04/dynamically-generated-pipes.html" rel="alternate" title="Dynamically generated PIPEs" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-111471882215849539</id>
<title mode="escaped" type="text/html">Dynamically generated PIPEs</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<pre>
data _null_;
   command = 'nslookup 207.255.105.92';
   infile ns1 pipe filevar=command end=eod1;
   do until (eod1);
      input;
      put '(1) ' _infile_;
   end;
   command = 'nslookup 207.255.105.93';
   infile ns2 pipe filevar=command end=eod2;
   do until (eod2);
      input;
      put '(2) ' _infile_;
   end;
   stop;
run;

data _null_;
   do command = 'nslookup 207.255.105.92', 'nslookup 207.255.105.93';
      eod = 0;
      infile nslookup pipe filevar=command end=eod;
      do until (eod);
         input;
         put command ':' _infile_;
      end;
   end;
   stop;
run;
</pre>

<p>prints</p>

<pre>
332  data _null_;
333     command = 'nslookup 207.255.105.92';
334     infile ns1 pipe filevar=command end=eod1;
335     do until (eod1);
336        input;
337        put '(1) ' _infile_;
338     end;
339     command = 'nslookup 207.255.105.93';
340     infile ns2 pipe filevar=command end=eod2;
341     do until (eod2);
342        input;
343        put '(2) ' _infile_;
344     end;
345     stop;
346  run;

NOTE: The infile NS1 is:
      Unnamed Pipe Access Device,
      PROCESS=nslookup 207.255.105.92,RECFM=V,
      LRECL=256

(1) Server:  spoke.dcn.davis.ca.us
(1) Address:  168.150.253.2
(1)
(1) Name:    207-255-105-092-dhcp.unt.pa.atlanticbb.net
(1) Address:  207.255.105.92
(1)
NOTE: The infile NS2 is:
      Unnamed Pipe Access Device,
      PROCESS=nslookup 207.255.105.93,RECFM=V,
      LRECL=256

(2) Server:  spoke.dcn.davis.ca.us
(2) Address:  168.150.253.2
(2)
(2) Name:    207-255-105-093-dhcp.unt.pa.atlanticbb.net
(2) Address:  207.255.105.93
(2)
NOTE: 6 records were read from the infile NS1.
      The minimum record length was 0.
      The maximum record length was 51.
NOTE: 6 records were read from the infile NS2.
      The minimum record length was 0.
      The maximum record length was 51.
NOTE: DATA statement used (Total process time):
      real time           0.75 seconds
      cpu time            0.01 seconds


347
348  data _null_;
349     do command = 'nslookup 207.255.105.92', 'nslookup 207.255.105.93';
350        eod = 0;
351        infile nslookup pipe filevar=command end=eod;
352        do until (eod);
353           input;
354           put command ':' _infile_;
355        end;
356     end;
357     stop;
358  run;

NOTE: The infile NSLOOKUP is:
      Unnamed Pipe Access Device,
      PROCESS=nslookup 207.255.105.92,RECFM=V,
      LRECL=256

nslookup 207.255.105.92 :Server:  spoke.dcn.davis.ca.us
nslookup 207.255.105.92 :Address:  168.150.253.2
nslookup 207.255.105.92 :
nslookup 207.255.105.92 :Name:    207-255-105-092-dhcp.unt.pa.atlanticbb.net
nslookup 207.255.105.92 :Address:  207.255.105.92
nslookup 207.255.105.92 :
NOTE: The infile NSLOOKUP is:
      Unnamed Pipe Access Device,
      PROCESS=nslookup 207.255.105.93,RECFM=V,
      LRECL=256

nslookup 207.255.105.93 :Server:  spoke.dcn.davis.ca.us
nslookup 207.255.105.93 :Address:  168.150.253.2
nslookup 207.255.105.93 :
nslookup 207.255.105.93 :Name:    207-255-105-093-dhcp.unt.pa.atlanticbb.net
nslookup 207.255.105.93 :Address:  207.255.105.93
nslookup 207.255.105.93 :
NOTE: 6 records were read from the infile NSLOOKUP.
      The minimum record length was 0.
      The maximum record length was 51.
NOTE: 6 records were read from the infile NSLOOKUP.
      The minimum record length was 0.
      The maximum record length was 51.
NOTE: DATA statement used (Total process time):
      real time           0.75 seconds
      cpu time            0.03 seconds
</pre>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/111462728175912658" rel="service.edit" title="Dynamic formats in PROC REPORT" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-04-27T11:38:00-07:00</issued>
<modified>2006-01-28T07:37:34Z</modified>
<created>2005-04-27T18:41:21Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/04/dynamic-formats-in-proc-report.html" rel="alternate" title="Dynamic formats in PROC REPORT" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-111462728175912658</id>
<title mode="escaped" type="text/html">Dynamic formats in PROC REPORT</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This code shows how to apply a dynamically defined format in PROC REPORT.  In this simplified example, the format will be 10.0 if the variable DECIMALS has a value of 0, 10.1 if DECIMALS has a value of 1, and so forth.</p>

<pre>
data test (keep=decimals value displayed);
   do i = 1 to 5;
      do decimals = 0 to 3;
         value = ranuni(95605) * 10;
         displayed = value;
         output;
      end;
   end;
   stop;
run;
 
proc report data=test nowindows missing nocenter;
   columns value decimals displayed;
   define _all_ / display;
   compute displayed;
      call define(_col_, 'format', '10.' || put(decimals, z1.0));
   endcomp;
run;
</pre>

<p>prints:</p>

<pre>
      value   decimals  displayed
  3.4648784          0          3
  3.3165982          1        3.3
  2.0278367          2       2.03
  8.0749122          3      8.075
  8.1445687          0          8
  7.6102304          1        7.6
  5.2384895          2       5.24
  3.8716015          3      3.872
  5.5787562          0          6
   0.951658          1        1.0
  9.5194012          2       9.52
  6.4120504          3      6.412
  7.6308912          0          8
  1.8146458          1        1.8
  2.6590652          2       2.66
   2.289426          3      2.289
  0.5009372          0          1
  7.2236493          1        7.2
  6.0467117          2       6.05
  2.5131059          3      2.513
</pre>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/111152318303097988" rel="service.edit" title="Simple example using &quot;hash&quot; object (associative array)" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-03-22T12:16:00-08:00</issued>
<modified>2006-01-28T07:37:55Z</modified>
<created>2005-03-22T20:26:23Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/03/simple-example-using-hash-object.html" rel="alternate" title="Simple example using &quot;hash&quot; object (associative array)" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-111152318303097988</id>
<title mode="escaped" type="text/html">Simple example using "hash" object (associative array)</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is a simple example of the use of the "hash" or associative array object in the SAS version 9 data step.  This example doesn't accomplish anything except demonstrate the syntax.</p>

<pre>
data test;
   length outname $5.;
   do outname = 'one', 'two', 'three';
      do i = 1 to 5;
         random = ranuni(95605);
         output;
      end;
   end;
run;

data _null_;

   if _n_ = 1 then 
      do;
      length outname $5.;
      length i random 8.;
      /* Read TEST data set */
      declare hash test(dataset: 'test');
      /* Define OUTNAME as key and I, RANDOM as data for hash object */
      test.defineKey('outname', 'i', 'random');
      test.defineData('outname', 'i', 'random');
      test.defineDone();
      /* avoid uninitialized variable notes */
      call missing(outname, i, random);
      end;

   /* Create output data set from hash object */
   rc = test.output(dataset: 'work.out');
   stop;

*****; run;

proc print data=work.out;
run;
</pre>

<p>prints</p>

<pre>
Obs    outname    i     random

  1     three     2    0.64121
  2     one       4    0.80749
  3     three     1    0.95194
  4     one       2    0.33166
  5     three     3    0.76309
  6     one       5    0.81446
  7     two       5    0.09517
  8     one       3    0.20278
  9     three     4    0.18146
 10     two       1    0.76102
 11     two       3    0.38716
 12     three     5    0.26591
 13     two       4    0.55788
 14     one       1    0.34649
 15     two       2    0.52385
</pre>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/111111506519460307" rel="service.edit" title="Writing to an in-memory array with PUT" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-03-17T18:56:00-08:00</issued>
<modified>2006-01-28T07:38:20Z</modified>
<created>2005-03-18T03:04:25Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/03/writing-to-in-memory-array-with-put.html" rel="alternate" title="Writing to an in-memory array with PUT" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-111111506519460307</id>
<title mode="escaped" type="text/html">Writing to an in-memory array with PUT</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This code writes to an in-memory array using the PUT statement.  Although there ought to be a builtin way to do this (there was in the data step language's model, PL/I), there's not.  This provides a workaround.</p>

<p>The general idea is create a memory buffer using FILE and INFILE with SHAREBUFFERS; when the file is written to, the value of the automatic variable _INFILE_ is set.  You can then copy _INFILE_ to a member of your array.  Only one actual I/O operation takes place, because everything is buffered.</p>

<p>The same technique works for a single variable; this example uses an array because that's what I needed when I created the original code.</p>

<pre>
filename buffer catalog 'work.io.buffer.source' lrecl=100 recfm=f;

data _null_;
   file buffer lrecl=100 recfm=f;
   put ' ';
run;

options stimer fullstimer;

data _null_;

   array claims (1000) $100. _temporary_ (1000 * ' ');

   infile buffer sharebuffers lrecl=100 recfm=f;
   file buffer;

   input @@;  /* Make sure buffer exists */

   do i = 1 to 1000;
      put @1 'a b c ' i comma6.0 @@;
      claims(i) = _infile_;
   end;

   file log; 

   do i = 1 to 5, 996 to 1000;
      put i= claims{i}=;      
   end;

   stop;

run;
</pre>

<p>See also the documentation for the _INFILE_ and _FILE_ variables.  On some cases, _FILE_ will be all you need.</p>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/111111161167981508" rel="service.edit" title="Case-insensitive Sorting" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-03-17T17:57:00-08:00</issued>
<modified>2006-01-28T07:41:42Z</modified>
<created>2005-03-18T02:06:51Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/03/case-insensitive-sorting.html" rel="alternate" title="Case-insensitive Sorting" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-111111161167981508</id>
<title mode="escaped" type="text/html">Case-insensitive Sorting</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>If you want to sort a field in a case-insensitive way (for use by humans, not computers), there are several ways you can do it.</p>

<pre>
options nocenter nodate nonumber;
data test;
infile cards;
input sometext $8.;
cards;
abcd
ABCD
aBcD
efgh
EFGH
111
a
Z
A
z
;;;; run;

proc sort data=test out=test_default;
   by sometext;
run;

title 'Default Sort Order';
proc print data=test_default;
run;

proc sort data=test out=test_lat1_lcs
          sortseq=lat1_lcs; 
   by sometext;
run;

title 'lat1_lcs Sort Order';
proc print data=test_lat1_lcs;
run;

proc sql;
   create table test_sql as
      select   *
      from     test
      order    by upcase(sometext);
quit;

title 'SQL Sort Order';
proc print data=test_sql;
run;
</pre>


<p>The result:</p>

<pre>
Default Sort Order

Obs    sometext

  1      111
  2      A
  3      ABCD
  4      EFGH
  5      Z
  6      a
  7      aBcD
  8      abcd
  9      efgh
 10      z
<hr/>
lat1_lcs Sort Order

Obs    sometext

  1      111
  2      a
  3      A
  4      abcd
  5      ABCD
  6      aBcD
  7      efgh
  8      EFGH
  9      Z
 10      z
<hr/>
SQL Sort Order

Obs    sometext

  1      111
  2      A
  3      a
  4      aBcD
  5      abcd
  6      ABCD
  7      EFGH
  8      efgh
  9      z
 10      Z
</pre>

<p>In this case, the last two sorts produce the same result.  On an EBCDIC system, however, they would be different because of differences between ASCII encoding and EBCDIC encoding.  Also, the first sort would produce different results on the two systems.</p>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/111110952561009769" rel="service.edit" title="Writing a file with PROC SQL" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-03-17T17:26:00-08:00</issued>
<modified>2006-01-28T07:42:08Z</modified>
<created>2005-03-18T01:32:05Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/03/writing-file-with-proc-sql.html" rel="alternate" title="Writing a file with PROC SQL" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-111110952561009769</id>
<title mode="escaped" type="text/html">Writing a file with PROC SQL</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>It's hard to think of a good practical application for this code, but maybe one will come up.  It shows the use of the file information functions in PROC SQL to write a flat file.</p>

<pre>
data test;
   input @1 value $8.;
cards;
abc
defg
hijklmno
;;;;

proc sql;

   select   F.filename_rc, 
            F.file_id, 
            D.value,
            fput  (f.file_id, 'value=' || value) as fput_rc, 
            fwrite(f.file_id) as fwrite_rc
   from
           (select   filename('myfile', 'c:\temp\myfile.txt') as filename_rc,
                     fopen('myfile', 'O', 256, 'v') as file_id
            from     test (obs=1)
            ) as F
   right join
            test as D
   on       1=1
   ;

quit;      

data _null_;
   infile 'c:\temp\myfile.txt';
   input;
   put 'INFO: myfile.txt: ' _infile_;
run;
</pre>

<p>prints in the log:</p>

<pre>
NOTE: The infile 'c:\temp\myfile.txt' is:
      File Name=c:\temp\myfile.txt,
      RECFM=V,LRECL=256

INFO: myfile.txt: value=abc
INFO: myfile.txt: value=defg
INFO: myfile.txt: value=hijklmno
NOTE: 3 records were read from the infile 'c:\temp\myfile.txt'.
      The minimum record length was 14.
      The maximum record length was 14.
</pre>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/111065819758763824" rel="service.edit" title="Four methods of performing a look-ahead read" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-03-12T12:09:00-08:00</issued>
<modified>2006-01-28T07:42:31Z</modified>
<created>2005-03-12T20:09:57Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/03/four-methods-of-performing-look-ahead.html" rel="alternate" title="Four methods of performing a look-ahead read" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-111065819758763824</id>
<title mode="escaped" type="text/html">Four methods of performing a look-ahead read</title>
<content mode="escaped" type="text/html" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">&lt;p&gt;A question about determining whether the dates in two records are adjacent was &lt;a href="http://www.listserv.uga.edu/cgi-bin/wa?S2=sas-l&amp;q=output+to+two+different+datasets&amp;s=&amp;f=&amp;a=mar+2005&amp;b=mar+2005"&gt;posed to SAS-L&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As is frequently the case, the discussion quickly morphed to a related topic; in this case, how to do a look-ahead read.&lt;/p&gt;

&lt;p&gt;I suggested the use of a second SET statement on the original data set, using the data set option FIRSTOBS=2.  Richard DeVenezia submitted sample data and code using the BY statement; I submitted sample code that doesn't use a BY statement.&lt;/p&gt;

&lt;p&gt;Here's the code to create the sample data:&lt;/p&gt;

&lt;pre&gt;
data demo;
   input k $ val @@ ;
cards;
a 11 a 12 a 13
b 21
c 31 c 32
d 41 d 42 d 43 d 44
;
&lt;/pre&gt;

&lt;p&gt;Here's my code:&lt;/p&gt;

&lt;pre&gt;
data lookahead1 (drop=nextk);

   set demo end=endmain;

   if not endnext then 
      set demo (firstobs=2 
                keep=k val
                rename=(k=nextk val=nextval))
          end=endnext;

   if (k ne nextk) or endmain then 
      nextval = .;

run;
&lt;/pre&gt;

&lt;p&gt;Here's Richard's code:&lt;/p&gt;

&lt;pre&gt;
data lookahead2 (drop=i);

   set demo;
   by k; 

   if (first.k or not last.k) then
      do i = 1 to 1 + (first.k and not last.k);
         set demo(keep=val rename=(val=nextval) );
      end;
   if last.k then 
      nextval = .;

run;
&lt;/pre&gt;

&lt;p&gt;Both methods produce the same output:&lt;/p&gt;

&lt;pre&gt;
Obs    k    val    nextval

  1    a     11       12
  2    a     12       13
  3    a     13        .
  4    b     21        .
  5    c     31       32
  6    c     32        .
  7    d     41       42
  8    d     42       43
  9    d     43       44
 10    d     44        .
&lt;/pre&gt;

&lt;p&gt;Ed Heaton later posted a method, credited to Mike Rhoads, of using MERGE for the same effect, without the need to do a special check for the last observation:&lt;/p&gt;

&lt;pre&gt;
Options mergeNoBy=noWarn ;
Data lookahead1( drop=nextk ) ;
   Merge
      demo
      demo(
         firstObs=2
         rename=( k=nextK val=nextVal )
      )
   ;
   ...
Run ;
Options mergeNoBy=error ;
&lt;/pre&gt;

&lt;p&gt;Howard Schreier posted generalized code which looks ahead an arbitrary number of observations:&lt;/p&gt;

&lt;pre&gt;
%let lookdepth = 2;

data lookahead&amp;amp;LOOKDEPTH(drop = many count close_to_last i);

   do many = 1 by 1 until (last.k);
      set demo(keep=k);
      by k;
      end;
   do count = 1 to many;
      set demo;
      by k;
      close_to_last = (many - count) &lt; &amp;amp;LOOKDEPTH ;
      if (first.k or not close_to_last) then
       do i = 1 to 1 + first.k * min(&amp;amp;LOOKDEPTH,many-1);
         set demo(keep=val rename=(val=ahead&amp;amp;LOOKDEPTH.val) );
         end;
      if close_to_last then do;
         ahead&amp;amp;LOOKDEPTH.val = .;
         end;
      output;
      end;

run;
&lt;/pre&gt;</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110912221073909270" rel="service.edit" title="Counting across variables" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-02-22T17:28:00-08:00</issued>
<modified>2006-01-28T07:43:03Z</modified>
<created>2005-02-23T01:30:10Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/02/counting-across-variables.html" rel="alternate" title="Counting across variables" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110912221073909270</id>
<title mode="escaped" type="text/html">Counting across variables</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>On SAS-L, 2/22/2005, Laughing Beggar asked about counting the occurence of strings in variables:</p>

<p>
<em>These 3 must be easy, but I am forced to defer to the wisdom of the group.
My data set has, say, ten character variables. Each variable can have a text
value that starts with a letter of the aplhabet and is then followed by some
numbers (eg A24.6). What I want to do is count the number of times a string
starting with eg "A" appears (1) in the dataset, (2) for each record and (3)
in each variable. I also need to do this for all the other letters of the
alphabet. So for (2), for example,  I'd like a new variable created for each
letter of the alphabet that has the count of the number of times that letter
appears as the leading character in the string across the ten variables. All
hints gratefully accepted. SAS V8.02, WIN-XP.</em>
</p>

<p>Arrays are one solution to this problem.</p>

<p>The code can probably be made simpler, but something like this will do it:</p>

<pre>
data test;
   infile cards;
   input (var1-var5) ($);
   cards;
A1 B1 A1 Z2 C1
A1 A1 A1 A1 A1
B1 B2 A1 T2 D1
B1 B2 B3 B4 Z5
;;;;

data colcounts (keep= c1_c1-c1_c26  c2_c1-c2_c26  
                      c3_c1-c3_c26  c4_c1-c4_c26  
                      c5_c1-c5_c26)
     rowcounts (keep= r1-r26)
     dscounts  (keep=d1-d26);
   retain alpha 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';  
   drop alpha;

   set test end=end;

   array vars var1-var5;
   array letters_in_dataset{26} d1-d26;
   array letters_in_columns{5, 26} c1_c1-c1_c26  
                                   c2_c1-c2_c26
                                   c3_c1-c3_c26
                                   c4_c1-c4_c26
                                   c5_c1-c5_c26;
   array letters_in_row{26} r1-r26;

   do p = 1 to 26;
      letters_in_row{p} = 0;
   end;

   do v = 1 to 5;
      do p = 1 to 26;
         letters_in_dataset(p) + (vars{v} =: substr(alpha, p, 1));
         letters_in_row(p) + (vars{v} =: substr(alpha, p, 1));
         letters_in_columns(v, p) + (vars{v} =: substr(alpha, p, 1));
      end;
   end;

   output rowcounts;

   if end then 
      output colcounts dscounts;

run;
;
</pre>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/114023700037474672" rel="service.edit" title="PROC REPORT with numeric calculated total title" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-02-17T20:10:00-08:00</issued>
<modified>2006-02-18T04:34:26Z</modified>
<created>2006-02-18T04:30:00Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/02/proc-report-with-numeric-calculated.html" rel="alternate" title="PROC REPORT with numeric calculated total title" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-114023700037474672</id>
<title mode="escaped" type="text/html">PROC REPORT with numeric calculated total title</title>
<content mode="escaped" type="text/html" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">&lt;p&gt;This is very similar to &lt;a href="http://www.excursive.com/sas/weblog/2007/01/proc-report-and-proc-tabulate-with.html"&gt; PROC REPORT and PROC TABULATE with ratios and total titles&lt;/a&gt;, except that it uses a numeric variable for Sex and shows only PROC REPORT code.&lt;/p&gt;

&lt;pre&gt;
options nocenter;

data test;
   do Sex = 1, 2, 2;
      do i = 1 to 847;
         Savings = round(ranuni(12345)*50, .01);
         Chgs = savings + round(ranuni(12345)*40, .01) +
(Sex=1)*Savings*.2;
         output;
      end;
   end;
   drop i;
   format chgs savings comma10.2;
run;

proc format;
   value sex
      1  = 'Male'
      2  = 'Female'
      ._ = 'Total'
      other = 'Unknown'
      ;
   picture tabpct
      0-100 = '009.9%'
      ;
run;

title 'Output from PROC REPORT';

proc report data=test 
     missing nowindows;

   column sex csex savings=nobs savings savings=meansavings chgs
chgs=meanchg SaveRate;

   define sex         / noprint group width=8;
   define csex        / computed 'Sex' format=sex.;
   define nobs        / n format=comma5.0 'N';
   define chgs        / sum;
   define meanchg     / mean 'Mean Chrgs';
   define savings     / sum;
   define meansavings / mean 'Mean Savings';
   define SaveRate    / computed format=percent7.1 'Savings Rate';

   compute saverate;
      saverate = savings.sum / chgs.sum;
   endcomp;

   compute csex;
      if _break_ = '_RBREAK_' then
         csex = ._;
      else
         csex = sex;
   endcomp;

   rbreak after / summarize;

run;
&lt;/pre&gt;

&lt;p&gt;The output:&lt;/p&gt;


&lt;h1&gt;Output from PROC REPORT&lt;/h1&gt;
&lt;p&gt;
&lt;table columns="7" cellpadding="2" border="1"&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align="center"&gt;Sex&lt;/th&gt;&lt;th align="center"&gt;N&lt;/th&gt;&lt;th align="center"&gt;Savings&lt;/th&gt;&lt;th align="center"&gt;Mean Savings&lt;/th&gt;&lt;th align="center"&gt;Chgs&lt;/th&gt;&lt;th align="center"&gt;Mean Chrgs&lt;/th&gt;&lt;th align="center"&gt;Savings Rate&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align="right"&gt;Male   &lt;/td&gt;&lt;td align="right"&gt;  847&lt;/td&gt;&lt;td align="right"&gt; 21,193.54&lt;/td&gt;&lt;td align="right"&gt;     25.02&lt;/td&gt;&lt;td align="right"&gt; 42,496.27&lt;/td&gt;&lt;td align="right"&gt;     50.17&lt;/td&gt;&lt;td align="right"&gt; 49.9% &lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align="right"&gt;Female &lt;/td&gt;&lt;td align="right"&gt;1,694&lt;/td&gt;&lt;td align="right"&gt; 43,075.18&lt;/td&gt;&lt;td align="right"&gt;     25.43&lt;/td&gt;&lt;td align="right"&gt; 77,354.14&lt;/td&gt;&lt;td align="right"&gt;     45.66&lt;/td&gt;&lt;td align="right"&gt; 55.7% &lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align="right"&gt;Total  &lt;/td&gt;&lt;td align="right"&gt;2,541&lt;/td&gt;&lt;td align="right"&gt; 64,268.72&lt;/td&gt;&lt;td align="right"&gt;     25.29&lt;/td&gt;&lt;td align="right"&gt;119,850.41&lt;/td&gt;&lt;td align="right"&gt;     47.17&lt;/td&gt;&lt;td align="right"&gt; 53.6% &lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110866827016919855" rel="service.edit" title="An example of using multiple ACROSS variables in PROC TABULATE" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-02-17T11:23:00-08:00</issued>
<modified>2006-01-28T07:43:31Z</modified>
<created>2005-02-17T19:24:30Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/02/example-of-using-multiple-across.html" rel="alternate" title="An example of using multiple ACROSS variables in PROC TABULATE" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110866827016919855</id>
<title mode="escaped" type="text/html">An example of using multiple ACROSS variables in PROC TABULATE</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>From Richard de Venezia, slightly modified:</p>

<pre>
data abc;

   group = 1;

   idx = '1'; x = 1.1;
   idy = 'a'; y = 10.10;
   output;

   idx = '2'; x = 2.2;
   idy = 'b'; y = 11.11;
   output;

   idx = '3'; x = 3.3;
   idy = 'c'; y = 13.13;
   output;

run;

proc report nowindows data=abc;
  columns group (idx, x) (idy, y);
  define group / display group;
  define idx / across ;
  define idy / across ;
  define x / sum;
  define y / sum;
run;
</pre>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110832778396026693" rel="service.edit" title="Using PROC TRANSPOSE to get a list of variable names" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-02-13T12:45:00-08:00</issued>
<modified>2006-01-28T08:01:12Z</modified>
<created>2005-02-13T20:49:43Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/02/using-proc-transpose-to-get-list-of.html" rel="alternate" title="Using PROC TRANSPOSE to get a list of variable names" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110832778396026693</id>
<title mode="escaped" type="text/html">Using PROC TRANSPOSE to get a list of variable names</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">I'm not a fan of PROC TRANSPOSE; there's almost nothing it can do that I can't do in a data step or PROC SQL.  But back in August of 2004 on SAS-L, Michael Murff proposed a use of PROC TRANSPOSE which even I would support:

<pre>
proc transpose data=sasuser.iris (obs=0)
               out=xnames (keep=_name_);
    var _all_;
run;
</pre>

I added "obs=0" to the original proposal to speed processing, and Ya Huang 
added "var _all_" to get all variables, not just numeric ones.</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110663822073878867" rel="service.edit" title="Sending a page or text message from SAS" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-24T23:30:00-08:00</issued>
<modified>2006-01-28T07:59:00Z</modified>
<created>2005-01-25T07:30:20Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/sending-page-or-text-message-from-sas.html" rel="alternate" title="Sending a page or text message from SAS" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110663822073878867</id>
<title mode="escaped" type="text/html">Sending a page or text message from SAS</title>
<content mode="escaped" type="text/html" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">You might have occasion to send a page or a text message from a SAS
program.  That's easy to do if you know the email address used by the
paging carrier.  You can usually get this information on the carrier's
web page.  For example, the address used for Verizon Wireless pagers
and cell phones is number@vtext.com.  There's a list of the major
carriers and their gateway addresses at
&lt;http://www.accutracking.com/sms-email.html&gt;.

You have to set some system options before starting SAS if you want to
use the email engine (I'm not sure why this has to be done at
invocation, but it does).  Here's a fragment of my sasv9.cfg file:

&lt;pre&gt;
-emailsys smtp
-emailhost smtp.mail.yahoo.com
-emailid kd6ttl@yahoo.com
-emailpw xxxxx
-emailauthprotocol login
&lt;/pre&gt; 

Here's code I could put in my SAS program:

&lt;pre&gt;
filename textmsg email '9999999999@vtext.com' 
         subject='Program notification';                               
                           
                                                                       
                                                                
data _null_;                                                           
                                                                
   file textmsg;                                                       
                                                                
   put 'Program ABC completed';                                        
                                                                
run; 
&lt;/pre&gt; 

A few seconds after I run this program (with a real phone number and
email password, of course), the message appears on my cell phone.  I've
used a similar technique to send messages to a pager.

There are some cautions, of course.  A logical place to put this code
would be in the TERMSTMT system option under version 9, but if
something sufficiently bad happens the termination code will not be
executed.  It's always possible that the mail system or your internet
connection is out of service.  And not all pagers handle lower case
letters correctly, so this is one of the very rare cases when it's ok to
put text in ALL CAPS.</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110663166911068017" rel="service.edit" title="Using a dynamic style directive in PROC REPORT" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-24T21:33:00-08:00</issued>
<modified>2006-01-28T07:59:18Z</modified>
<created>2005-01-25T05:41:09Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/using-dynamic-style-directive-in-proc.html" rel="alternate" title="Using a dynamic style directive in PROC REPORT" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110663166911068017</id>
<title mode="escaped" type="text/html">Using a dynamic style directive in PROC REPORT</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This example was written in response in a question on SAS-L.  Someone wanted to know how to force a line split within an cell.  As it happens, the mechanism to do that is the same mechanism used to make part of the output bold or in a different font - ODS.</p>

<p>This should work for RTF and PDF output in SAS 8.2 and later (maybe earlier, but I can't check), and for HTML in 9.1.3 (but HTML in 8.2 doesn't work right).</p>

<pre>
data x;                                                                                                                                 
  text1='hello';                                                                                                                        
  text2='everyone';                                                                                                                     
  output;                                                                                                                               
run;                                                                                                                                    
                                                                                                                                        
ods listing close;                                                                                                                      
ods rtf file='h:\temp\test.rtf';                                                                                                        
ods pdf file='h:\temp\test.pdf';                                                                                                        
ods escapechar='^';                                                                                                                     
                                                                                                                                        
proc report data=x nowindows missing;                                                                                                   
  column text1 text2 text;                                                                                                              
  define text1-text2 / noprint;                                                                                                         
  define  text / computed;                                                                                                              
  compute text / character length=200;                                                                                                  
     text = '^S={font_face=courier font_weight=bold}' || text1
            || ' ^n^S={font_face=times font_style=italic}' || text2;                 
  endcomp;                                                                                                                              
run;                                                                                                                                    
                                                                                                                                        
ods rtf close;                                                                                                                          
ods pdf close;                                                                                                                          
ods listing;                                                                                                                            
</pre>

<p>The key here is the use of style directives.  ^S tells SAS that style information will follow, and ^n means "insert a line break".</p>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110663013867733910" rel="service.edit" title="Using htmSQL in a batch SAS job" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-24T21:03:00-08:00</issued>
<modified>2006-01-28T07:59:43Z</modified>
<created>2005-01-25T05:15:38Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/using-htmsql-in-batch-sas-job.html" rel="alternate" title="Using htmSQL in a batch SAS job" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110663013867733910</id>
<title mode="escaped" type="text/html">Using htmSQL in a batch SAS job</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>One of my interests lately has been in figuring out ways to create
master/detail (invoice/lineitem, etc.) reports in SAS.  I've
concentrated on what can be done in base SAS.</p>

<p>Although it's not a base product, SAS/Intrnet also has a way to produce 
master/detail reports - the
htmSQL processor.  It takes an HTML page marked up with special tags and
expands the tags using data from SAS.  This is typically done though a
web server, but if you have SAS/Intrnet licensed you can use the module
outside of the web.  You can produce only HTML (or other text-based
output), but even so this has the potential to be a powerful tool for
certain uses.  This usage is documented, but I haven't seen a usage in
the wild, so I'm posting an example.</p>

<p>I haven't done anything at all fancy with the HTML; it's just the bare
minimum.  Also, this doesn't show the hoops I had to jump through to get
all the file permissions set right, but they would be site-dependent
anyway.</p>

<pre>
%let htmsqlpgm = /opt/nethome1/datawebapps/sas-cgi/htmSQL;

/**** Set up data ****/
options compress=no nocenter nodate nonumber;

libname me '/usr/users/hamiltja';

data me.Invoices;

  infile cards;

  input @1  Invoice_Num   3.
        @5  Customer      $14.
        @20 Invoice_Date  date9.;

  format Invoice_Date worddatx12.;

cards;
101 Hugo Furst     01Jan2004
102 Freida Peeples 15Jan2004
103 Al E. Loohah   30Jan2004
;;;;

data items;

  infile cards;

  length Item $17.;

  input @1  Item  $10.
        @12 Cost  10.2;

  format cost comma10.2;

cards;
Widget     12.00
Gadget     10.00
Frammet    36.00
Gizmo      50.37
Whatsit    100.00
ItsIts     1.00
Thingie    37.56
Sprocket   13.00
Geehaws    5.76
HooHaas    22.22
DingDing   37.54
Thing      79.43
HoopyDoopy 40.01
Mairsy     67.66
Doat       77.21
Doesy      85.31
Divy       49.37
;;;;

data me.LineItems;

  keep Invoice_Num Line_Num Item Cost;

  set me.invoices (keep=invoice_num);

  length size1-size5 $6.;
  length item $30.;

  array sizes{5} size1-size5 (' ' 'Small' 'Medium' 'Large' 'Jumbo');

  Line_Num = 0;

  do i = 1 to 6;
     if ranuni(95819) &gt; .2 then
        do;
        set items (rename=(Item=ItemName Cost=ItemCost))point=i;
        do j = 1 to 5;
           if ranuni(40324) &gt; .2 then
              do;
              size = sizes(j);
              item = catx(' ', size, itemname);
              cost = itemcost * j;
              line_num + 1;
              output;
              end;
        end;
        end;
  end;

run;

proc datasets library=me nolist;
  modify LineItems;
     index create Invoice_Num;
run; quit;

/**** The Real Work ****/

/* Create a file containing the htmSQL we want to use. */

filename hsql '/usr/users/hamiltja/batchweb.hsql';

data _null_;
  infile cards;
  file hsql;
  input;
  put _infile_;
cards4;
&lt;html&gt;
  &lt;body&gt;
  &lt;h1&gt;Invoices and Items&lt;/h1&gt;

  {query server="mtshrtst"}
  {library sqlname="invoice" path="/usr/users/hamiltja"}

  {sql}
  select   Invoice_Num,
           Customer,
           Invoice_Date
  from     invoice.invoices
  order    by invoice_num
  {/sql}

  {eachrow}
     &lt;h2&gt;Invoice {&amp;INVOICE_NUM} for {&amp;CUSTOMER} dated {&amp;INVOICE_DATE}&lt;/h2&gt;

     {sql}
     select   Line_Num,
              Item,
              Cost
     from     invoice.lineitems
     where    invoice_num = {&amp;INVOICE_NUM}
     order    by line_num
     {/sql}

     &lt;table border="1"&gt;
        &lt;tr&gt;{label var="{&amp;sys.colname[*]}" 
                         before="&lt;th&gt;" 
                         between="&lt;/th&gt;&lt;th&gt;" 
                         after="&lt;/th&gt;"}&lt;/tr&gt;
        {eachrow}
        &lt;tr&gt;{&amp;{&amp;sys.colname[*]}
                      before="&lt;td&gt;" 
                      between="&lt;/td&gt;&lt;td&gt;" 
                      after="&lt;/td&gt;"}&lt;/tr&gt; {/eachrow}
     &lt;table&gt;
  {/eachrow}

  {/query}

  &lt;/body&gt;
&lt;/html&gt;
;;;;

filename htmsql pipe 
         "&amp;HTMSQLPGM. /usr/users/hamiltja/batchweb.hsql '_debug=0'";
filename webout '/usr/users/hamiltja/batchweb.html';


/* Run program through pipe.  Throw away first four lines of output, */
/* which contain header information used by browsers.                                                                         */
data _null_;
  file webout;
  infile htmsql lrecl=1024 firstobs=4;
  input;
  put _infile_;
run;
</pre>

<p>Here's the first part of the output, with extraneous blank lines
deleted:</p>

<pre>
&lt;html&gt;

  &lt;body&gt;

  &lt;h1&gt;Invoices and Items&lt;/h1&gt;

     &lt;h2&gt;Invoice      101 for Hugo Furst dated 01JAN2004  &lt;/h2&gt;
     &lt;table border="1"&gt;
        &lt;tr&gt;&lt;th&gt;Line_Num&lt;/th&gt;&lt;th&gt;item&lt;/th&gt;&lt;th&gt;cost&lt;/th&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td&gt;       1&lt;/td&gt;&lt;td&gt;Widget&lt;/td&gt;&lt;td&gt;      12&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td&gt;       2&lt;/td&gt;&lt;td&gt;Small Widget&lt;/td&gt;&lt;td&gt;      24&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td&gt;       3&lt;/td&gt;&lt;td&gt;Medium Widget&lt;/td&gt;&lt;td&gt;      36&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;&lt;td&gt;       4&lt;/td&gt;&lt;td&gt;Small Gadget&lt;/td&gt;&lt;td&gt;      20&lt;/td&gt;&lt;/tr&gt;
</pre>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110619624360834833" rel="service.edit" title="Using the mtPrivatizeVars macro in htmSQL" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-19T20:38:00-08:00</issued>
<modified>2006-01-28T08:00:24Z</modified>
<created>2005-01-20T04:44:03Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/using-mtprivatizevars-macro-in-htmsql.html" rel="alternate" title="Using the mtPrivatizeVars macro in htmSQL" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110619624360834833</id>
<title mode="escaped" type="text/html">Using the mtPrivatizeVars macro in htmSQL</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>Here's how I would use the macro in an htmSQL page. The page is
designed to display arbitrary data sets (for a particular data set, I
could just hard code everything), and assumes that variables LIBRARY and
TABLE have been passed as parameters. I'm not showing the queery
statement or surrounding HTML here:</p>

<pre>
{sql}
select   resolve('%mtprivatizesqlvars(data={&amp;LIBRARY}.{&amp;TABLE})') 
            as selectstring length=32000
from     dictionary.columns
where    libname = upcase('{&amp;LIBRARY}') 
         and upcase(memname) = upcase('{&amp;TABLE}') 
         and memtype = 'DATA'
         and varnum = 1
{/sql}

{sql}
select    {&amp;SELECTSTRING}
from     {&amp;LIBRARY}.{&amp;TABLE}
{/sql}

&lt;table border="1"&gt;
   &lt;tr&gt;&lt;th&gt;Name&lt;/th&gt;{&amp;SYS.COLNAME[*] before="&lt;th&gt;" between="&lt;/th&gt;&lt;th&gt;" after="&lt;/th&gt;"}&lt;/tr&gt;
   &lt;tr&gt;&lt;th&gt;Label&lt;/th&gt;{label var="{&amp;SYS.COLNAME[*]}" before="&lt;th&gt;" between="&lt;/th&gt;&lt;th&gt;" after="&lt;/th&gt;"}&lt;/tr&gt;
  {eachrow}
   &lt;tr&gt;&lt;th&gt;{&amp;SYS.QROW}&lt;/th&gt;{&amp;{&amp;SYS.COLNAME[*]} before="&lt;td&gt;" between="&lt;/td&gt;&lt;td&gt;" after="&lt;/td&gt;"}&lt;/tr&gt;
  {/eachrow}
&lt;/table&gt;
</pre>

<p>The first block of code captures the output of the mtprivatizesql macro
into an htmSQL variable. The macro must have been stored in a macro
search path known to the htmSQL processor. The key here is the use of
the RESOLVE function, which returns the results of evaluating a string.
I'm assuming a reasonable number of variables (something which is under
my control), so the results will fit into the selectstring length of
32,000 bytes. By the way, this doesn't work in SAS 8.2, where you'll
get only the first 200 characters of the result.</p>

<p>The second block of code selects the records to display.</p>

<p>The third block of code displays the records. I've chosen to display
two header lines, the first showing the variable names and the second
showing the variable labels.</p>

<p>By the way, there are other checks, not shown, which prevent the end
user from changing the URL to display an arbitrary table anywhere on the
system.</p>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110615817906930004" rel="service.edit" title="Making Variable Values Private in SQL" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-19T10:07:00-08:00</issued>
<modified>2006-01-28T08:10:51Z</modified>
<created>2005-01-19T18:09:39Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/making-variable-values-private-in-sql.html" rel="alternate" title="Making Variable Values Private in SQL" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110615817906930004</id>
<title mode="escaped" type="text/html">Making Variable Values Private in SQL</title>
<content mode="escaped" type="text/html" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">&lt;p&gt;Here's another usage of the file info functions.  I wrote this because I wanted to display claims records on a web page, but without any personal information.  Various data sets might be displayed, and they might not all have all of the variables I wanted to make private.  I didn't want to write a separate SQL statement for each of the dozens of data sets, so I wrote a utility macro.&lt;/p&gt;

&lt;p&gt;Task: Given a data set which contains soc_sec_num and other variables, create a select clause which masks soc_sec num but displays the other variables normally.  Extend that to an arbitrary number of privatized variables and a large number of data set variables.&lt;/p&gt;

&lt;p&gt;Here's my solution:&lt;/p&gt;

&lt;pre&gt;
%macro mtPrivatizeSqlVars(
      data=,
      privatevars=soc_sec_num prime_prsn_ssn last_nm first_nm,
      newvalues=0 0 Lastname Firstname);
/* 10-Dec-2004 Jack Hamilton                                   */
/* Macro:      mtPrivatizeSqlVars                              */
/*                                                             */
/* Purpose:    Create a list of variables in a data set,       */
/*    separated by commas and suitable for use in an SQL       */
/*    SELECT statement, where values of specified vars are     */
/*    replaced by a fixed string.                              */
/*                                                             */
/* Parameters:                                                 */
/*    Data - the name of the dataset (libref.member)           */
/*    PrivateVars - list of variables to be privatized,        */
/*             separated by spaces                             */
/*    NewValues - values to be used instead of private values  */
/*                                                             */

   %local i j dsid nvars varlist thisvarname lowvarname varlabel vartype privatewordpos newtext thisword;

   %let privatevars = %lowcase(&amp;PRIVATEVARS.);

   %let varlist = ERROR IN mtPrivatizeSqlVars MACRO;

   /* Open the dataset.  */

   %let dsid = %SYSFUNC(open(&amp;DATA., I));
   %if &amp;DSID = 0 %then
      %do;
      %let varlist = ERROR IN mtPrivatizeSqlVars MACRO: %sysfunc(sysmsg());
      %put ERROR: &amp;VARLIST.;
      %goto EXIT;
      %end;

   %let nvars = %SYSFUNC(attrn(&amp;DSID., nvars));
   %let varlist = ;

   %do i = 1 %to %EVAL(&amp;NVARS. - 1);
      %let thisvarname = %SYSFUNC(varname(&amp;DSID. ,&amp;I.));
      %let lowvarname = %LOWCASE(&amp;thisvarname);
      %let privatewordpos = 0;

      /* Find location of varname in private list. */
      %do j = 1 %to 999;
         %let thisword = %lowcase(%scan(&amp;PRIVATEVARS., &amp;J., %str( )));
         %if "&amp;THISWORD." = "" %then %goto nomorewords;
         %if "&amp;THISWORD." = "&amp;LOWVARNAME." %then
            %do;
            %let privatewordpos = &amp;J.;
            %goto nomorewords;
            %end;
      %end;
      %NOMOREWORDS:

      %if &amp;PRIVATEWORDPOS. = 0 %then
         %let varlist = &amp;VARLIST. &amp;THISVARNAME. ,;
      %else
         %do;
         %let varlabel = %SYSFUNC(varlabel(&amp;DSID. ,&amp;I.));
         %let vartype = %SYSFUNC(vartype(&amp;DSID. ,&amp;I.));
         %let newtext = %scan(&amp;NEWVALUES., &amp;PRIVATEWORDPOS., %str( ));
         %if "&amp;VARLABEL." = "" %then %let varlabel = &amp;THISVARNAME.;
         %if &amp;VARTYPE. = N %then
            %let varlist = &amp;VARLIST. &amp;NEWTEXT. as &amp;THISVARNAME. label="&amp;VARLABEL.", ;
         %else
            %let varlist = &amp;VARLIST. "&amp;NEWTEXT." as &amp;THISVARNAME. label="&amp;VARLABEL.", ;
         %end;
   %end;

   %let thisvarname = %SYSFUNC(varname(&amp;DSID. ,&amp;I.));
   %let lowvarname = %LOWCASE(&amp;thisvarname);
      %if %sysfunc(indexw(&amp;PRIVATEVARS., &amp;LOWVARNAME.)) = 0 %then
         %let varlist = &amp;VARLIST. &amp;THISVARNAME.;
      %else
         %do;
         %let varlabel = %SYSFUNC(varlabel(&amp;DSID. ,&amp;I.));
         %let vartype = %SYSFUNC(vartype(&amp;DSID. ,&amp;I.));
         %let newtext = %scan(&amp;NEWVALUES., &amp;PRIVATEWORDPOS., %str( ));
         %if "&amp;VARLABEL." = "" %then %let varlabel = &amp;THISVARNAME.;
         %if &amp;VARTYPE. = N %then
            %let varlist = &amp;VARLIST. &amp;NEWTEXT. as &amp;THISVARNAME. label="&amp;VARLABEL." ;
         %else
            %let varlist = &amp;VARLIST. "&amp;NEWTEXT." as &amp;THISVARNAME. label="&amp;VARLABEL." ;
         %end;

   /* Close the dataset.                                          */

%EXIT:

   %let rc = %SYSFUNC(close(&amp;DSID.));

   &amp;VARLIST.

%mend mtPrivatizeSqlVars;

data test;
   last_nm = 'Hamilton'; soc_sec_num = 123456789; 
   incrd_da = '12dec2004'd; paid_amnt = 75.00; 
   proc_cd = '99213';
   format incrd_da worddatx12.;
   output;
run;

%let changedvars = %mtprivatizesqlvars(data=test);
%put &amp;CHANGEDVARS.;

proc sql;

   select   &amp;CHANGEDVARS.
   from     test
   ;

quit;
&lt;/pre&gt;

&lt;p&gt;Relevant log:&lt;/p&gt;

&lt;pre&gt;
2023
2024  %let changedvars = %mtprivatizesqlvars(data=test);
2025  %put &amp;CHANGEDVARS.;
"Lastname" as last_nm label="last_nm", 0 as soc_sec_num label="soc_sec_num", incrd_da , paid_amnt , proc_cd
&lt;/pre&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;pre&gt;
last_nm   soc_sec_num      incrd_da  paid_amnt  proc_cd
Lastname            0   12 Dec 2004         75  99213
&lt;/pre&gt;

&lt;p&gt;
I then use the macro in an htmSQL page.  htmSQL works better in this case than a SAS program in the broker would, because it's easy to generate a table with two header rows, one for the variable name and one for the variable label.
&lt;/p&gt;</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110609559414323568" rel="service.edit" title="PROC COMPUTAB Example" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-18T16:46:00-08:00</issued>
<modified>2006-02-18T04:03:40Z</modified>
<created>2005-01-19T00:46:34Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/proc-computab-example.html" rel="alternate" title="PROC COMPUTAB Example" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110609559414323568</id>
<title mode="escaped" type="text/html">PROC COMPUTAB Example</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">A Simple Example of PROC COMPUTAB

<pre>
data example;<br/>
   input year sales cost;<br/>
cards;<br/>
1988  83  52<br/>
1989 106  85<br/>
1990 120 114<br/>
;;;; run;<br/>
<br/>
ods listing;<br/>

proc computab data=example;
   columns y1988 y1989 y1990 total;
   rows sales cost gprofit pctprofit;

   gprofit = sales - cost;

   y1988 = (year = 1988);
   y1989 = (year = 1989);
   y1990 = (year = 1990);

   col: total = y1988 + y1989 + y1990;

   row: pctprofit = gprofit / cost * 100;

*****; run;
</pre>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110609548755499746" rel="service.edit" title="Drug Days" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-18T16:44:00-08:00</issued>
<modified>2006-01-28T08:07:49Z</modified>
<created>2005-01-19T00:44:47Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/drug-days.html" rel="alternate" title="Drug Days" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110609548755499746</id>
<title mode="escaped" type="text/html">Drug Days</title>
<content mode="escaped" type="text/html" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">&lt;p&gt;This program calculates the number days after a discharge date that a patient was taking (or rather, had a prescription for) a drug.&lt;/p&gt;

&lt;pre&gt;
data patients;
   input @1  patient_id 2.
         @4  discharge_da date9.;
   format discharge_da date9.;
cards;
1  01feb2001
2  01jun2001
3  31dec2001
;;;; run;

data drugs;
   input @1  patient_id 2.
         @4  fill_da date9.
         @14 rx_days 3.;
   format fill_da date9.;
cards;
1  01mar2001 31
1  05feb2001 90
1  01jun2001 90
2  01jan2001 90
2  01jun2001 90
2  01dec2001 90
;;;;; run;

%global daysbefore;
data _null_;
   length daysbefore $10000;
   daysbefore = ' ';
   do i = 720 to 1 by -1;
      daysbefore = trim(daysbefore) || ' daybefore' || put(i, 3.-L);
   end;
   call symput('DAYSBEFORE', daysbefore);
run; 
   
data info;

   merge patients 
         drugs (in=in_drugs);
      by patient_id;

   array drug_days{-720:720} &amp;DAYSBEFORE 
         day0 daysafter1-daysafter720;
   retain &amp;DAYSBEFORE day0 daysafter1-daysafter720;

   if first.patient_id then 
      do day = -720 to 720;
         drug_days{day} = 0;
      end;

   if in_drugs then 
      do day = (fill_da - discharge_da) to 
               (fill_da - discharge_da + rx_days - 1);
         drug_days{day} = 1;
      end;

   if last.patient_id then 
      do;
      days0_through_30 = sum(0, day0, of daysafter1-daysafter30);
      days31_through90 = sum(0, of daysafter31-daysafter90);
      output;
      end;

   keep patient_id discharge_da days0_through_30 days31_through90;

run;

proc report data=info nowindows split='/';
   columns patient_id discharge_da days0_through_30 days31_through90;
   define  patient_id       / order 'Patient/ID';
   define  discharge_da     / display width=10 'Discharge/Date';
   define  days0_through_30 / display width=10 'Days 0-30';
   define  days31_through90 / display width=10 'Days 31-90';
run;
&lt;/pre&gt;

&lt;p&gt;prints:&lt;/p&gt;

&lt;pre&gt;
Patient   Discharge
     ID        Date   Days 0-30  Days 31-90
      1   01FEB2001          27          60
      2   01JUN2001          31          59
      3   31DEC2001           0           0
&lt;/pre&gt;</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110609543775719387" rel="service.edit" title="FTP with some error checking" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-18T16:43:00-08:00</issued>
<modified>2006-01-28T08:07:22Z</modified>
<created>2005-01-19T00:43:57Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/ftp-with-some-error-checking.html" rel="alternate" title="FTP with some error checking" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110609543775719387</id>
<title mode="escaped" type="text/html">FTP with some error checking</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<pre>
filename cmds temp;

data _null_;
   file cmds recfm=v;
   infile datalines;
   input;
   put _infile_;
datalines;
verbose
open ftp.excursive.com
user nogood nogood
ascii on
get index.html
quit
;;;;

/* Windows */
filename ftp pipe "type %SYSFUNC(pathname(cmds)) | ftp -i -n";
/* Unix */
*filename ftp pipe "cat %SYSFUNC(pathname(cmds)) | ftp -i -n";

data _null_;
   retain errfound 1.;
   infile ftp end=end;
   input;
   put 'INFO: ' _infile_;
   if _infile_ =: '226 Transfer completed' then 
      errfound = 0;
   if end and errfound then 
      do;
      put 'ER' 'ROR: FTP failed.';
      *abort abend 12;
      end;
   return;
run;
</pre>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110609510633108039" rel="service.edit" title="PROC REPORT with pseudo-observation numbers" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-18T16:38:00-08:00</issued>
<modified>2006-01-28T08:02:55Z</modified>
<created>2005-01-19T00:38:26Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/proc-report-with-pseudo-observation.html" rel="alternate" title="PROC REPORT with pseudo-observation numbers" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110609510633108039</id>
<title mode="escaped" type="text/html">PROC REPORT with pseudo-observation numbers</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<pre>
/* Create pseudo-observation numbers, restarted at each break.  */

options ls=64;
options nodate;
title ' ';

proc report
        data=sasuser.iris
        headskip split='\'
        colwidth=12
        nowindows;

   column species seq sepallen sepalwid;

   define species  / order      'Species\--';
   define seq      / computed   'Seq\--';
   define sepallen / order      'Sepal Length\--';
   define sepalwid / sum        'Sepal Width\--';

   compute seq;
      if species ne ' ' and _break_ eq ' ' then
         obs_no = 1;
      else
         obs_no + 1;
      seq = obs_no;
   endcomp;

   break after species / page;

*****; run;
</pre>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110609504151221073" rel="service.edit" title="PROC REPORT and PROC TABULATE with ratios and total titles" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-18T16:37:00-08:00</issued>
<modified>2006-01-28T07:46:05Z</modified>
<created>2005-01-19T00:37:21Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/proc-report-and-proc-tabulate-with.html" rel="alternate" title="PROC REPORT and PROC TABULATE with ratios and total titles" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110609504151221073</id>
<title mode="escaped" type="text/html">PROC REPORT and PROC TABULATE with ratios and total titles</title>
<content mode="escaped" type="text/html" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">&lt;pre&gt;
options nocenter;

data test;
   do Sex = 'M', 'F', 'F';
      do i = 1 to 847;
         Savings = round(ranuni(12345)*50, .01);
         Chgs = savings + round(ranuni(12345)*40, .01) +
(Sex='M')*Savings*.2;
         output;
      end;
   end;
   drop i;
   format chgs savings comma10.2;
run;

proc format;
   value $osex
      'M' = 'Male'
      'F' = 'Female'
      'T' = 'Total'
      ;
   picture tabpct
      0-100 = '009.9%'
      ;
run;

title 'Output from PROC REPORT';

proc report data=test 
     missing nowindows;

   column sex csex savings=nobs savings savings=meansavings chgs
chgs=meanchg SaveRate;

   define sex         / noprint group width=8;
   define csex        / computed 'Sex';
   define nobs        / n format=comma5.0 'N';
   define chgs        / sum;
   define meanchg     / mean 'Mean Chrgs';
   define savings     / sum;
   define meansavings / mean 'Mean Savings';
   define SaveRate    / computed format=percent7.1 'Savings Rate';

   compute saverate;
      saverate = savings.sum / chgs.sum;
   endcomp;

   compute csex / character;
      if _break_ = '_RBREAK_' then
         csex = 'Total';
      else
         csex = sex;
   endcomp;

   rbreak after / summarize;

run;

title 'Output from PROC TABULATE';

proc tabulate data=test missing;

   class sex;
   var chgs savings;

   keylabel sum=' ';
   keylabel mean=' ';
   keylabel rowpctsum='Savings Rate';

   tables sex=' ' all='Total',
          n*format=comma5.0
          savings=' '*(sum='Savings' mean='Mean
Savings')*format=comma10.2
          chgs=' '*(sum='Chgs' mean='Mean Chgs')*format=comma10.2
          savings=' '*rowpctsum&lt;chgs&gt;*format=tabpct.
        / box='Sex'
          ;

run;
&lt;/pre&gt;

&lt;p&gt;The output:&lt;/p&gt;

&lt;h1&gt;Output from PROC REPORT&lt;/h1&gt;
&lt;table cellspacing="1" cellpadding="7" rules="groups" frame="box" summary="Procedure Report: Detailed and/or summarized report"&gt;
&lt;tr&gt;
&lt;th scope="col"&gt;Sex&lt;/th&gt;
&lt;th scope="col"&gt;N&lt;/th&gt;
&lt;th scope="col"&gt;Savings&lt;/th&gt;
&lt;th scope="col"&gt;Mean Savings&lt;/th&gt;
&lt;th scope="col"&gt;Chgs&lt;/th&gt;
&lt;th scope="col"&gt;Mean Chrgs&lt;/th&gt;
&lt;th scope="col"&gt;Savings Rate&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td class="r"&gt;1,694&lt;/td&gt;
&lt;td class="r"&gt; 43,075.18&lt;/td&gt;
&lt;td class="r"&gt;     25.43&lt;/td&gt;
&lt;td class="r"&gt; 77,354.14&lt;/td&gt;
&lt;td class="r"&gt;     45.66&lt;/td&gt;
&lt;td class="r"&gt; 55.7% &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td class="r"&gt;  847&lt;/td&gt;
&lt;td class="r"&gt; 21,193.54&lt;/td&gt;
&lt;td class="r"&gt;     25.02&lt;/td&gt;
&lt;td class="r"&gt; 42,496.27&lt;/td&gt;
&lt;td class="r"&gt;     50.17&lt;/td&gt;
&lt;td class="r"&gt; 49.9% &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td class="r"&gt;2,541&lt;/td&gt;
&lt;td class="r"&gt; 64,268.72&lt;/td&gt;
&lt;td class="r"&gt;     25.29&lt;/td&gt;
&lt;td class="r"&gt;119,850.41&lt;/td&gt;
&lt;td class="r"&gt;     47.17&lt;/td&gt;
&lt;td class="r"&gt; 53.6% &lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;br&gt;
&lt;h1&gt;Output from PROC TABULATE&lt;/h1&gt;
&lt;table cellspacing="1" cellpadding="7" rules="groups" frame="box" summary="Procedure Tabulate: Table 1"&gt;
&lt;tr&gt;
&lt;th scope="col" class=" c"&gt;Sex&lt;/th&gt;
&lt;th scope="col"&gt;N&lt;/th&gt;
&lt;th scope="col"&gt;Savings&lt;/th&gt;
&lt;th scope="col"&gt;Mean Savings&lt;/th&gt;
&lt;th scope="col"&gt;Chgs&lt;/th&gt;
&lt;th scope="col"&gt;Mean Chgs&lt;/th&gt;
&lt;th scope="col"&gt;Savings Rate&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th scope="row" class="l"&gt;F&lt;/th&gt;
&lt;td class="r b"&gt;1,694&lt;/td&gt;
&lt;td class="r b"&gt; 43,075.18&lt;/td&gt;
&lt;td class="r b"&gt;     25.43&lt;/td&gt;
&lt;td class="r b"&gt; 77,354.14&lt;/td&gt;
&lt;td class="r b"&gt;     45.66&lt;/td&gt;
&lt;td class="r b"&gt; 55.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th scope="row" class="l"&gt;M&lt;/th&gt;
&lt;td class="r b"&gt;  847&lt;/td&gt;
&lt;td class="r b"&gt; 21,193.54&lt;/td&gt;
&lt;td class="r b"&gt;     25.02&lt;/td&gt;
&lt;td class="r b"&gt; 42,496.27&lt;/td&gt;
&lt;td class="r b"&gt;     50.17&lt;/td&gt;
&lt;td class="r b"&gt; 49.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th scope="row" class="l"&gt;Total&lt;/th&gt;
&lt;td class="r b"&gt;2,541&lt;/td&gt;
&lt;td class="r b"&gt; 64,268.72&lt;/td&gt;
&lt;td class="r b"&gt;     25.29&lt;/td&gt;
&lt;td class="r b"&gt;119,850.41&lt;/td&gt;
&lt;td class="r b"&gt;     47.17&lt;/td&gt;
&lt;td class="r b"&gt; 53.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110609493757711420" rel="service.edit" title="Copy a ZIP file from the Internet and then read a member" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-18T16:35:00-08:00</issued>
<modified>2006-01-28T07:46:48Z</modified>
<created>2005-01-19T00:35:37Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/copy-zip-file-from-internet-and-then.html" rel="alternate" title="Copy a ZIP file from the Internet and then read a member" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110609493757711420</id>
<title mode="escaped" type="text/html">Copy a ZIP file from the Internet and then read a member</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<pre>
filename dane url
'http://www.parkiet.com:80/dane/danesesji/bazytxt/akcje/zywiec.zip'
         proxy='http://slcpx01:8080';

filename zipcopy  "d:\temp\zywiec.zip";
filename zipfile  SASZIPAM "d:\temp\zywiec.zip";

data _null_;
   nbyte=-1;
   infile dane nbyte=nbyte recfm=s;
   input;
   file zipcopy recfm=n;
   put _infile_;
run;

data _null_;
   infile zipfile(ZYWIEC.txt);
   input;
   put _infile_;
   if _n_ &gt; 10 then stop;
run;
</pre>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110609481277161445" rel="service.edit" title="Using the TRANSLATE function to validate data" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-18T16:33:00-08:00</issued>
<modified>2006-01-28T07:47:29Z</modified>
<created>2005-01-19T00:33:32Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/using-translate-function-to-validate.html" rel="alternate" title="Using the TRANSLATE function to validate data" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110609481277161445</id>
<title mode="escaped" type="text/html">Using the TRANSLATE function to validate data</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<pre>
data test;
   infile cards;
   input phone $14.;
cards;
(916) 555-1234
9165551234
916-555-1234
916 555-1234
916 555 1234
916 5551234
916555123499
1234
5551212
555-1212
(916) 555-HELP
;;;;

data validate;
   set test;
   pattern = translate(phone, '000000000', '123456789');
   if pattern in ('(000) 000-0000', '0000000000', '000-000-0000', 
                  '000 000-0000', '000 000 0000', '000 0000000') then 
      valid = '1';
   else
      valid = '0';

   put (_all_) (=);

run;
</pre>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110609477655189805" rel="service.edit" title="StatGraph Cowboy Hat" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-18T16:32:00-08:00</issued>
<modified>2006-01-28T07:48:17Z</modified>
<created>2005-01-19T00:32:56Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/statgraph-cowboy-hat.html" rel="alternate" title="StatGraph Cowboy Hat" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110609477655189805</id>
<title mode="escaped" type="text/html">StatGraph Cowboy Hat</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<pre>
data hat (compress=no pointobs=no);
   do x = -5 to 5 by .25;
      do y = -5 to 5 by .25;
         z = sin(sqrt(x*x + y*y));
         output;
      end;
   end;
run;

proc template;
   define statgraph myhat;
      layout gridded;
         surfaceplot x=x y=y z=z;
      endlayout;
   end;
run;

ods graphics on;
ods pdf file='c:\temp\hat.pdf' notoc;

data _null_;
   set hat;
   file print ods=(template="myhat");
   put _ods_;
run;

ods pdf close;
</pre>

<p>produces a <a href="../../hat.pdf">PDF file containing an image</a>.</p>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110609080776731735" rel="service.edit" title="Another way to read an Ingres table from SAS version 9" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-18T15:26:00-08:00</issued>
<modified>2006-01-28T07:49:08Z</modified>
<created>2005-01-18T23:26:47Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/another-way-to-read-ingres-table-from.html" rel="alternate" title="Another way to read an Ingres table from SAS version 9" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110609080776731735</id>
<title mode="escaped" type="text/html">Another way to read an Ingres table from SAS version 9</title>
<content mode="escaped" type="text/html" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">&lt;p&gt;SAS Version 9 doesn't support Access to Ingres, so we have to use a Connect session to version 8.&lt;/p&gt;

&lt;pre&gt;options autosignon;

rsubmit sascmd='sas8' remote=newton persist=yes;

   %sysrput v8worklib = %SYSFUNC(pathname(work));

   libname ocnrates ingres database="%SYSGET(MT_RPT_OCNRATES)"
access=readonly;

   proc sql stimer;

      create table cnvrsn_fctr_typ_r as
         select  cnvrsn_fctr_typ_cd,
                 cnvrsn_fctr_typ_desc
         from    ocnrates.cnvrsn_fctr_typ_r
         where   cnvrsn_fctr_typ_cd not in ('BYPAS', 'FACIL')
         order   by cnvrsn_fctr_typ_cd;

   quit;

endrsubmit;

libname work8 v8 "&amp;V8WORKLIB.";

proc copy in=work8 out=work constraint=yes index=yes datecopy move
clone;
   select cnvrsn_fctr_typ_r;
run;

rsubmit;
   proc datasets;
   run; quit;
endrsubmit;

libname work8 clear;
signoff;
&lt;/pre&gt;

&lt;p&gt;"options autosignon" tells SAS to start the remote session with the
rsubmit, without an explicit signon command.&lt;/p&gt;

&lt;p&gt;"rsubmit sascmd='sas8' remote=newton persist=yes" tells SAS to start a
SAS v8 session on Newton, and not to close the session after the rsubmit
block has finished executing.  This keeps the remote work library
available until a signoff command is executed.&lt;/p&gt;

&lt;p&gt;"%sysrput v8worklib = %SYSFUNC(pathname(work))" tells SAS to create a
macro variable V8WORKLIB in the local session whose value is the path to
the work library of the remote session.&lt;/p&gt;

&lt;p&gt;We then write some data to that work library.&lt;/p&gt;

&lt;p&gt;"libname work8 v8 "&amp;V8WORKLIB.";" creates a libref in the local session
that points to the remote session's work library, where the results of
the code we just executed are stored.&lt;/p&gt;

&lt;p&gt;PROC COPY moves the selected tables from the remote library to the
local library.  The work library doesn't have to be WORK, of course. 
The other options are things I think we would usually want.  For moving
ordinary data sets, PROC COPY is probably better than PROC MIGRATE.&lt;/p&gt;

&lt;p&gt;The next rsubmit block is only there to show that you can submit more
remote code to execute; after the first time you on't have to specify
any options, and the last active session will be used.&lt;/p&gt;

&lt;p&gt;Finally, we clear the libname and close the remote session.  If you
don't do this explicitly, it will happen automatically when the job
ends.&lt;/p&gt;</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110607621584324098" rel="service.edit" title="Wordcount using rxparse" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-18T11:23:00-08:00</issued>
<modified>2006-01-28T08:03:46Z</modified>
<created>2005-01-18T19:23:35Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/wordcount-using-rxparse.html" rel="alternate" title="Wordcount using rxparse" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110607621584324098</id>
<title mode="escaped" type="text/html">Wordcount using rxparse</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>Counting words in a string using SAS regular expression functions</p>

<pre>
data _null_;
   retain rx;
   infile cards eof=eof;
   input @1 string $char30.;

   if _n_ = 1 then
      do;
      rx = rxparse('" "* ((~" "+ " "+) #1)*');
      put @1 'Words'
          @7 'String';
      end;

   call rxsubstr(rx, string || ' ', pos, len, score);

   put @1  score   5.0-r
       @7  string  $char30.;

return;

EOF:

      put 'end of data';
      call rxfree(rx);

cards4;
123456789012345678901234567890
String
oneword
 oneword
two words
three words here
  big gaps a   b  c          d
a*b*c=d

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
;;;;
</pre>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/110607339004662552" rel="service.edit" title="Celko's median" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2007-01-18T10:36:00-08:00</issued>
<modified>2006-01-28T08:04:08Z</modified>
<created>2005-01-18T18:36:30Z</created>
<link href="http://www.excursive.com/sas/weblog/2007/01/celkos-median.html" rel="alternate" title="Celko's median" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-110607339004662552</id>
<title mode="escaped" type="text/html">Celko's median</title>
<content mode="escaped" type="text/html" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">&lt;p&gt;This is Joe Celko's solution for calculating a median in SQL.&lt;/p&gt;

&lt;pre&gt;
* From SQL For Smarties, Second Edition, by Joe Celko.  ;

* If there is no statistical median, the mean of the   ; 
* value just below and the value just above is used.   ;

data parts;
   input @1  pno    $2.
         @4  pname  $5.
         @10 color  $5.
         @16 weight 2.
         @19 city   $6.;
cards;
p1 Nut   Red   12 London
p2 Bolt  Green 17 Paris
p3 Cam   Blue  12 Paris
p4 Screw Red   14 London
p5 Cam   Blue  12 Paris
p6 Cog   Red   19 London
;;;; *****; run;

proc sql;

   select avg(distinct weight)
   from   (select f1.weight
           from   parts as f1,
                  parts as f2
           group  by f1.pno, f1.weight
           having sum(case when f2.weight = f1.weight then 1
                           else 0
                      end)
                  &gt;=
                  abs(sum(case when f2.weight &lt; f1.weight then 1
                               when f2.weight &gt; f1.weight then -1
                               else 0
                          end)));

*****; quit;
&lt;/pre&gt;</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/114499649391672146" rel="service.edit" title="Parsing SYSPARM with infile magic" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2006-04-13T23:05:00-07:00</issued>
<modified>2006-04-14T07:20:54Z</modified>
<created>2006-04-14T06:34:53Z</created>
<link href="http://www.excursive.com/sas/weblog/2006/04/parsing-sysparm-with-infile-magic.html" rel="alternate" title="Parsing SYSPARM with infile magic" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-114499649391672146</id>
<title mode="escaped" type="text/html">Parsing SYSPARM with infile magic</title>
<content type="application/xhtml+xml" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>Here's a method of using "infile magic" to parse the value of the &amp;SYSPARM macro variable, which is valued by setting the SYSPARM system option, typically on the command line or in JCL when you start SAS.</p>

<pre>
options sysparm='value=37.2 start=01jan2006, end=01mar2006';

data _null_;
   retain repeats 2.;
   infile cards;
   informat start end date9.;
   input @;
   _infile_ = translate(symget('sysparm'), ' ', ',');
   input start= end= value= repeats=; 
   format start end date9.;
   put (_all_) (=);
   stop;
cards;

;;;; 
</pre>

<p>prints:</p>

<pre>
start=01JAN2006 end=01MAR2006 value=37.2 repeats=2
</pre>

<p>A few comments:</p>

<ul>
   <li>The separator has to be a blank for named input.  Commas are 
       translated into blanks because under MVS a comma will appear in 
       the value of SYSPARM if you use JCL continuation when 
       specifying SYSPARM.  This will, of course, present difficulties
       if you need to have a comma in one of your values.</li>
   <li>You can assign a default value to a parameter by establishing
       its value before the INPUT statement.  In this case, I used the 
       RETAIN statement to set the value.</li>
   <li>The parameters can appear in any order.  Unfortunately, you can't
       tell (without reparsing the data) which order they did appear in.</li>
   <li>If you include in SYSPARM a variable which does not appear in the 
       INPUT statement, you'll get a note in the log and _ERROR_ will be set 
       to 1.</li>
   <li>In real life, you'd want to save the values somewhere, either in a 
       SAS data set or with CALL SYMPUT/SYMPUTX.</li>

</ul>
</div>
</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/114499441135639175" rel="service.edit" title="Creating and emailing an Excel workbook on MVS" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2006-04-13T22:46:00-07:00</issued>
<modified>2006-04-14T07:11:20Z</modified>
<created>2006-04-14T06:00:11Z</created>
<link href="http://www.excursive.com/sas/weblog/2006/04/creating-and-emailing-excel-workbook.html" rel="alternate" title="Creating and emailing an Excel workbook on MVS" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-114499441135639175</id>
<title mode="escaped" type="text/html">Creating and emailing an Excel workbook on MVS</title>
<content mode="escaped" type="text/html" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">&lt;p&gt;The sample code below shows how to create an "Excel workbook" on MVS and email it to yourself.&lt;/p&gt;  

&lt;p&gt;I put "Excel workbook" in quotes because it's not really a native Excel workbook.  It's XML which Excel knows how to read.  Before distributing a workbook created this way, you should open it in Excel and save it as a native document - recent versions of Excel can read the XML, but not everyone has upgraded.&lt;/p&gt;

&lt;p&gt;(Future versions of Excel might use XML as their native format, in which 
case SAS might be able to create native format Excel files.  But that hasn't happened as of spring 2006.)&lt;/p&gt;

&lt;pre&gt;
//EMAILXLS JOB xxxxxx,'Email XLS',NOTIFY=&amp;amp;SYSUID,
//          MSGCLASS=X,CLASS=S
//*
/*ROUTE PRINT FETCH
//*
//S1REPORT EXEC SASPROD,
//            OPTIONS='ls=71 noovp nocenter'
//ODSTEXT   DD DSN=&amp;amp;SYSUID..ODSTEMP.XLS,DISP=(MOD,CATLG,CATLG),
//             LRECL=16392,RECFM=VB,SPACE=(16392,(1000,2000)),
//             UNIT=SYSWRK
//SYSIN DD *,DLM='\\'

/* Get latest copy of tagset from SAS web site */
filename tagset http
   'http://support.sas.com:80/rnd/base/topics/odsmarkup/excltags.tpl';
%include tagset / nosource2;

/* Empty the output file, in case this is a rerun. */
data _null_;
   file odstext old;
run;

ods tagsets.excelxp file=odstext record_separator=none style=sasweb;
ods listing close;

title 'Transfer';

data VolumeExpense;
   length PurchMC ProvMC Type $8.;
   format Cost comma10.2;
   do purchmc = 'Hay', 'Rch', 'Oak';
      do provmc = 'Oak', 'Hay', 'Rch';
         do type = 'Volume', 'Expense';
            cost = round(ranuni(94612)*100, .01);
            output;
         end;
      end;
   end;
run;

/* Print the first sheet */
ods tagsets.excelxp
      options(sheet_name='Output from PRINT'
      frozen_headers='1'
      row_repeat='1'
      );

proc print data=VolumeExpense;
run;

/* Print the second sheet, triggered by a new ODS statement  */
/* to the tagset without a new FILE=.                        */
ods tagsets.excelxp
      options(sheet_name='Output from REPORT'
      frozen_headers='3'
      row_repeat='1-3'
      autofilter='2'
      );

proc report data=VolumeExpense missing nofs nocenter
      completerows completecols;
   column purchmc type provmc, cost cost=totcost;
   define purchmc / group 'Purch MC' width=8 order=data;
   define type    / group 'Type'     width=8 order=data;
   define provmc  / across 'Prov MC' width=8 order=data;
   define Cost    / sum 'Cost';
   define totcost / sum 'Total Cost' width=10 format=comma10.2;
   compute provmc;
      if type = 'Expense' then
         call define(_col_, 'style', 'style={background=yellow}');
   endcomp;
run;

ods _all_ close;
ods listing;

/* Close and free the output file. */            
data _null_;                                      
   length filename $44.;                          
   file odstext mod close=free filename=filename;
   call symput('REPORTDSN', trim(filename));      
run; 

filename email email
               attach=("&amp;amp;REPORTDSN."
                       name='EmailXLS' extension='xls')
               to='Jack.Hamilton@kp.org'
               from='Jack.Hamilton@kp.org'
               subject='Excel test ';

data _null_;

   file email;

   put 'Results are attached.';

run;

filename email clear;

\&lt;/pre&gt;

&lt;p&gt;A few interesting features of this code:&lt;/p&gt;

&lt;pre&gt;
//ODSTEXT   DD DSN=&amp;amp;SYSUID..ODSTEMP.XLS,DISP=(MOD,CATLG,CATLG),
//             LRECL=16392,RECFM=VB,SPACE=(16392,(1000,2000)),
//             UNIT=SYSWRK
&lt;/pre&gt;

&lt;p&gt;This makes sure that there's a file to which to write the output.  DISP=MOD tells the system to use an existing file if there is one, and to create a new one if there isn't.&lt;/p&gt;

&lt;pre&gt;
/* Empty the output file, in case this is a rerun. */
data _null_;
   file odstext old;
run;
&lt;/pre&gt;

&lt;p&gt;This makes sure the file is empty (which it wouldn't be if this were a rerun).&lt;/p&gt;

&lt;pre&gt;
ods tagsets.excelxp
      options(sheet_name='Output from PRINT'
      frozen_headers='1'
      row_repeat='1'
      );
&lt;/pre&gt;

&lt;p&gt;This tells SAS to create a new sheet (in this case, the first one), and specifies the sheet name and some header information.&lt;/p&gt;

&lt;pre&gt;
ods tagsets.excelxp
      options(sheet_name='Output from REPORT'
      frozen_headers='3'
      row_repeat='1-3'
      autofilter='2'
      );
&lt;/pre&gt;

&lt;p&gt;This tells SAS to start the second sheet, and specifies a different sheet name and header information.  It also specifies an autofilter on the second column.&lt;/p&gt;

&lt;pre&gt;
/* Close and free the output file. */            
data _null_;                                      
   length filename $44.;                          
   file odstext mod close=free filename=filename;
   call symput('REPORTDSN', trim(filename));      
run; 
&lt;/pre&gt;             

&lt;p&gt;This tells SAS to close and free the output data set.  In the case of a new data set, it will be catalogued (ordinarily, it wouldn't be catalogued until the job step finished).  Use of the FILENAME= option captures the data set name; this allows you to specify the data set name in only one place, the JCL, instead of in both the JCL and the SAS code.&lt;/p&gt;

&lt;pre&gt;
filename email email
               attach=("&amp;REPORTDSN." 
                       name='EmailXLS' extension='xls')
               to='Jack.Hamilton@kp.org'
               from='Jack.Hamilton@kp.org'
               subject='Excel test ';
&lt;/pre&gt;

&lt;p&gt;This sends the newly written file as an attachment to email.  Notice that SAS requires the actual catalogued data set name, not a DDname, which is why I had to free the file in the previous data step. &lt;/p&gt;

&lt;p&gt;The resulting file is &lt;a href="EmailXLS.xls"&gt;here&lt;/a&gt;.&lt;/p&gt;</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/114499100673621483" rel="service.edit" title="Creating and emailing an RTF document under MVS" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2006-04-13T21:42:00-07:00</issued>
<modified>2006-05-18T19:08:51Z</modified>
<created>2006-04-14T05:03:26Z</created>
<link href="http://www.excursive.com/sas/weblog/2006/04/creating-and-emailing-rtf-document.html" rel="alternate" title="Creating and emailing an RTF document under MVS" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-114499100673621483</id>
<title mode="escaped" type="text/html">Creating and emailing an RTF document under MVS</title>
<content mode="escaped" type="text/html" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">&lt;p&gt;RTF is a document format which can be easily read by Microsoft Word, and by other applications such as OpenOffice.org on various hardware and software platforms.  The program below shows sample JCL needed to create an RTF file containing a graph and a table, along with the resulting RTF file itself.&lt;p&gt;

&lt;p&gt;You could get much fancier, changing the font sizes and colors of various things, creating side-by-side graphics, and so forth, but I wanted to stick with a simple example.&lt;/p&gt;

p&gt;I did want to point out the graph type I used, which is relatively new:&lt;/p&gt;

&lt;img src="emailrtf.png" width="481" height="385"&gt;

&lt;p&gt;This is a radar chart, and it is often used to compare multiple values across multiple groups.  In this example with randomly generated data, you can see that Sacramento and Oakland have higher medical costs than surgical costs, but the other locations have higher surgical costs than medical costs.  PROC &lt;a href=" http://support.sas.com/onlinedoc/913/getDoc/en/graphref.hlp/a001578435.htm"&gt;GRADAR&lt;/a&gt; is documented in the Online Docs, which contain several other examples.&lt;/p&gt;

&lt;p&gt;Two other new graph types that may be of interest: &lt;a href="http://support.sas.com/onlinedoc/913/getDoc/en/graphref.hlp/a002252885.htm"&gt;GAREABAR&lt;/a&gt; lets you display the magnitude of two variables of interest, and there's a &lt;a href="http://support.sas.com/onlinedoc/913/getDoc/en/graphref.hlp/a002299564.htm"&gt;nifty example&lt;/a&gt;.  &lt;href="http://support.sas.com/onlinedoc/913/getDoc/en/graphref.hlp/a002258324.htm"&gt;GBARLINE lets you easily place a line chart on top of a bar chart.&lt;/p&gt;

&lt;p&gt;Here's the JCL and source code; you will probably need to change the JCL to
match your site's requirements (in particular, you will need to change the 
JOB card and possibly the name of the catalogued procedure in the EXEC card 
and the unit name in the ODSTEXT DD statement:&lt;/p&gt;

&lt;pre&gt;
//EMAILRTF JOB xxxxxx,'EMAIL RTF',NOTIFY=&amp;amp;SYSUID,
//          MSGCLASS=X,CLASS=S
//*
/*ROUTE PRINT FETCH
//*
//S1REPORT EXEC SASPROD,
//          OPTIONS='ls=71 noovp nocenter errorabend errorcheck=strict'
//ODSTEXT   DD DSN=&amp;amp;SYSUID..ODSTEMP.RTF,DISP=(MOD,CATLG,CATLG),
//             LRECL=16392,RECFM=VB,SPACE=(16392,(1000,2000)),
//             UNIT=SYSWRK
//SYSIN DD *,DLM='\\'

options errorcheck=strict noovp nocenter;

data Costs;
   length PurchMC ProvMC Type $8.;
   format Cost comma10.2;
   do i = 1 to 20;
      do purchmc = 'Hay', 'Rch', 'Oak', 'Sac', 'SF';
         do provmc = 'SF', 'Oak', 'Sac', 'Hay', 'Rch';
            Cost = round(ranuni(94612)*100, .01);
            if ranuni(95819) &gt; .5 then
               type = 'Medical';
            else
               type = 'Surgical';
            output;
         end;
      end;
   end;
run;

/* Empty the output file, in case this is a rerun. */
data _null_;
   file odstext old;
run;

/* Send output to RTF */
options orientation=portrait;
ods rtf file=odstext style=sasweb;
ods listing close;
ods rtf startpage=never;

/* Send graphics to PNG (somewhat arbitrary choice).  */
goptions
  reset   = goptions
  device  = png
  target  = png
  xmax    = 5in
  ymax    = 4in;

title 'Cost by Type by Medical Center';

ods rtf text='Here is a chart:';

proc gradar data=Costs;
    chart purchmc
          / sumvar=Cost
            overlay=Type
            cstars=(red blue)
            starcircles=(0.5 1.0)
            cstarcircles=black
         ;
run;

/* Create table output */

ods rtf text='And here is a table:';

proc report data=Costs nofs missing;

   column type purchmc Cost n;

   define type     / Group 'Type';
   define purchmc  / group 'Medical Center';
   define Cost     / analysis sum 'Cost';
   define n        / 'Records';

run;

/* Close ODS destination */
ods rtf close;

/* Empty the output file, in case this is a rerun. */
data _null_;
   length filename $44.;                          
   file odstext mod close=free filename=filename;
   call symput('REPORTDSN', trim(filename)); ;
run;

/* Send via email */
filename email email
               attach=("&amp;REPORTDSN." 
                       name='emailrtf'
                       lrecl=16392
                       type='text/rtf'
                       extension='rtf')
               to='Jack.Hamilton@kp.org'
               from='Jack.Hamilton@kp.org'
               subject='Test File';

/* Send the mail */
data _null_;
   file email;
   put 'SAS Output is attached.';
run;

//
&lt;/pre&gt;

The results are &lt;a href="emailrtf.rtf"&gt;here&lt;/a&gt;.</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
<entry xmlns="http://purl.org/atom/ns#">
<link href="https://www.blogger.com/atom/9861540/114498910114857893" rel="service.edit" title="Zipping and Emailing a file under MVS" type="application/atom+xml"/>
<author>
<name>Jack Hamilton</name>
</author>
<issued>2006-04-13T21:19:00-07:00</issued>
<modified>2006-04-14T07:08:03Z</modified>
<created>2006-04-14T04:31:41Z</created>
<link href="http://www.excursive.com/sas/weblog/2006/04/zipping-and-emailing-file-under-mvs.html" rel="alternate" title="Zipping and Emailing a file under MVS" type="text/html"/>
<id>tag:blogger.com,1999:blog-9861540.post-114498910114857893</id>
<title mode="escaped" type="text/html">Zipping and Emailing a file under MVS</title>
<content mode="escaped" type="text/html" xml:base="http://www.excursive.com/sas/weblog/" xml:space="preserve">&lt;p&gt;This example is completely platform-dependent, and runs under MVS and its descendents.  Windows and Unix platforms can perform a similar function using pipes.&lt;/p&gt;

&lt;p&gt;Release 9.2 of SAS will probably support the SASZIPAM engine for reading zip files, under most platforms (including MVS, where it currently doesn't work).  It doesn't sound likely that a write engine will be added, and it is almost certain that support for enhanced encryption, in either direction, will not be added.&lt;/p&gt;

&lt;p&gt;The attached SAS job shows how to write an Excel workbook and and PDF using ODS, place those files into a ZIP archive under MVS, and then email the results.&lt;/p&gt;

&lt;p&gt;The job attempts to do as much as possible using DDnames rather than using data set names; I wanted to take advantage of JCL for naming and enqueuing.  The job could undoubtedly be made shorter or simpler, but I didn't want to spend a lot of time on it.  Documentation for PKZIP for zSeries can be found at www.pkzip.com.  If you have the appropriate license, you can encrypt as well as compress files.&lt;p&gt;

&lt;p&gt;Similar code could be written to unZIP files.&lt;/p&gt;

&lt;p&gt;If you run the job, please remember to change the email addresses near the bottom from mine to yours; also, you'll have to change the JOB card.&lt;/p&gt;

&lt;pre&gt;
//ZIPSTEP JOB xxxxxx,jobtext,NOTIFY=&amp;amp;SYSUID,CLASS=S,MSGCLASS=X
/*ROUTE PRINT FETCH
/*JOBPARM L=999
//*
//ZIPFILE SET ZIPFILE=&amp;amp;SYSUID..ZIPSTEP.SAMPLE.ZIP
//XLSFILE SET XLSFILE=&amp;amp;SYSUID..ZIPSTEP.TRANSFER.XLS
//PDFFILE SET PDFFILE=&amp;amp;SYSUID..ZIPSTEP.TRANSFER.PDF
//*
//S1DELETE EXEC PGM=IEFBR14
//ZIPFILE   DD DSN=&amp;amp;ZIPFILE.,
//             DISP=(MOD,DELETE,DELETE),UNIT=SYSWRK,SPACE=(1,1)
//XLSFILE   DD DSN=&amp;amp;XLSFILE,
//             DISP=(MOD,DELETE,DELETE),UNIT=SYSWRK,SPACE=(1,1)
//PDFFILE   DD DSN=&amp;amp;PDFFILE,
//             DISP=(MOD,DELETE,DELETE),UNIT=SYSWRK,SPACE=(1,1)
//*
//S2DATA  EXEC SASPROD,
//         OPTIONS='ls=71 noovp nocenter'
//XLSFILE   DD DSN=&amp;amp;XLSFILE,DISP=(NEW,PASS,DELETE),
//             UNIT=SYSWRK,SPACE=(TRK,(20,50)),
//             RECFM=VB,LRECL=16392
//PDFFILE   DD DSN=&amp;amp;PDFFILE,DISP=(NEW,PASS,DELETE),
//             UNIT=SYSWRK,SPACE=(TRK,(20,50)),
//             RECFM=VB,LRECL=16392
//SYSIN     DD *,DLM='\\'

/* Get latest copy of ExcelXP tagset from SAS web site */
filename tagset http
   'http://support.sas.com:80/rnd/base/topics/odsmarkup/excltags.tpl';
%include tagset / nosource2;

/* Use it */
ods tagsets.excelxp file=xlsfile record_separator=none style=sasweb;
/* We also want PDF. */
ods pdf file=pdffile notoc style=sasweb;
ods listing close;

title 'Transfers';

/* Sample data */
data test;
   length purchmc provmc type $8.;
   do purchmc = 'Hay', 'Rch', 'Oak';
      do provmc = 'Oak', 'Hay', 'Rch';
         do type = 'Volume', 'Expense';
            cost = round(ranuni(94612)*100, .01);
            output;
         end;
      end;
   end;
run;

/* Set sheet options */
ods tagsets.excelxp
      options(sheet_name='Transfers'
      frozen_headers='1'
      row_repeat='1'
      );

proc report data=test missing nofs nocenter
      completerows completecols;
   column purchmc type provmc, cost cost=totcost;
   define purchmc / group 'Purch MC' width=8
                    order=data;
   define type    / group 'Type' width=8;
   define provmc  / across 'Prov MC' width=8
                    order=data;
   define cost    / sum 'Cost' format=comma8.2;
   define totcost / sum 'Total Cost' width=10 format=comma10.2;
   compute provmc;
      if type = 'expense' then
         call define(_col_, 'style', 'style={background=yellow}');
   endcomp;
run;

ods _all_ close;
ods listing;

\//*
//S3ZIP   EXEC PGM=PKZIP
//STEPLIB   DD DSN=SYSL.PKZIPMVS.NPRD.LOADLIB,DISP=SHR
//XLSFILE   DD DSN=&amp;amp;XLSFILE,DISP=(SHR,PASS)
//PDFFILE   DD DSN=&amp;amp;PDFFILE,DISP=(SHR,PASS)
//ZIPFILE   DD DSN=&amp;amp;ZIPFILE.,DISP=(NEW,CATLG,CATLG),
//             UNIT=SYSWRK,SPACE=(TRK,(20,50)),FREE=CLOSE
//SYSIN     DD *
-TRANSLATE_TABLE_DATA(EBC#850)
-DATA_DELIMITER(CRLF)
-FILE_TERMINATOR()
-ARCHIVE_OUTFILE(ZIPFILE)
-COMPRESSION_LEVEL(MAXIMUM)
-ZIPPED_DSN(*.*.*.*,++*.*)
-INFILE(XLSFILE)
-ACTION(ADD)
-INFILE(PDFFILE)
-ACTION(ADD)
//*
//S4MAIL  EXEC SASPROD,
//         OPTIONS='ls=71 noovp nocenter'
//SYSIN     DD *,DLM='\\'
filename email email
               attach=(".zipstep.sample.zip"
                       name='Sample' extension='zip')
               to='Jack.Hamilton@kp.org'
               from='Jack.Hamilton@kp.org'
               subject='Sample ZIP file';

data _null_;

   file email;

   put 'ZIP Sample attached.';

run;
\&lt;/pre&gt;

&lt;p&gt;The file &lt;a href="sample.zip"&gt;sample.zip&lt;/a&gt; contains the results
of running this job.&lt;/p&gt;</content>
<draft xmlns="http://purl.org/atom-blog/ns#">false</draft>
</entry>
</feed>
