BookmarkSubscribeRSS Feed
MINX
Obsidian | Level 7

@JohnChen_TW, There are a lot of ways to page break tables. Below is one simple example to page break for 16 observations per page.

 

data have;
	set have;
	p = ceil (_n_/16);
run;

options nodate nonumber papersize=A4 topmargin="0.75in" missing=' ' orientation=landscape; 
ods escapechar='^';
ods document name=work.testdoc(write);
ods rtf file="&outpath\test.rtf";
PROC REPORT DATA=have NOWD
	center                
	headskip
	split='|' 
	ls=256;
	title1 j=r "Page ^{thispage} of ^{lastpage}";
	title5 j=c "Example";
	title6	j=c "Proc Report";
	footnote1 j=l "footnote";
    columns p sno group name sex res ans;
	define p 						/order noprint;
	define sno	 					/center	style=[cellwidth=20mm]   	'Subject ID';
	define group 		 			/center	style=[cellwidth=45mm]   	'Test Group';
	define name						/left		style=[cellwidth=60mm]   	'Test Name';
	define sex  					/center	style=[cellwidth=15mm]   	'Gender';
	define res 						/center	style=[cellwidth=40mm]   	'Result';
	define ans  					/center	style=[cellwidth=25mm]   	'Yes/ No';
	break after p/page;
	compute after _page_/style={just=l font_face='Arial' font_size=9pt borderbottomcolor=white};
		line "Note:";
		line "Why the note cannot be displayed on each page...?";
	endcomp;
RUN;
ods rtf close;
ods document close;
ods rtf file="&outpath\test.rtf" sectiondata='\headery540\footery540' ;
ods pdf file="&outpath\test.pdf" ;
proc document name=work.testdoc;
replay;
run;
quit;

ods _all_ close;

  

 

Cynthia_sas
SAS Super FREQ
John,
The issue with your original code is that you are not telling PROC REPORT anything that will cause the COMPUTE AFTER _PAGE_ to execute.

You are correct that when the RTF or PDF output is RENDERED, the file appears on multiple pages in paged destinations. However, without using a BY statement (to cause a logical page break) or a "helper" variable, as shown in my code or the code from Minx, then there is nothing for PROC REPORT to do except generate the output as though it is one logical page, therefore only at the bottom will you see the note. However, with BY group processing, the BY group is a logical group and the BY statement causes a LOGICAL page break between every BY group. This happens within PROC REPORT -- before the output is rendered -- so SAS and PROC REPORT write the NOTE at the end of every BY group, every logical page.

If you need to have something appear on the physical pages, at the end of every physical page, either use a FOOTNOTE statement or use a COMPUTE AFTER _PAGE_ where you control the page break within PROC REPORT.

You've now gotten 2 examples of how to do this and with my example, I provided a picture of the note appearing on every page when the output was rendered in Word.

There should be something in all the code that's been posted to help you figure it out.

cynthia
JohnChen_TW
Quartz | Level 8

Thank you, @Cynthia_sas for your reply.

Ok, as you said, the note would not be displayed on every page without using BY statement or other logical statements.

But as my previous example (in PDF), I'm wondering why the note can be displayed on the last two pages, not only at the bottom of the table?

 

I've tried to break page by counting the number of lines in one page, and then made a flag variable to identify when to break at one page as the way Minx provided. However, this is not a general resolution for me since sometimes the data would be cut off to next row due to the width of the layout. Also, the number of lines in one page I counted is the "number of observations in SAS data set" instead of the practical lines displayed on the output.

 

Also, I've tried another way to break page as well using BREAK AFTER "Specific variable" / PAGE. However, assuming the specific variable is "sno (Subject ID)" and the number of observations among subjects is quite different, then the output will look like a discontinuous table.

 

Sorry, FOOTNOTE was populated other information, so I'd like to put the note at the bottom of table on each page.

 

Or is there a suggestion how to keep the space between the bottom of table on one page and the footnote in order to populate the note?

 

JC

 

 

Cynthia_sas
SAS Super FREQ

Hi:

  When I ran your original code, I noticed the wrapping -- that will interfere with page breaking, so in my program, I adjusted the widths so that all the columns fit in the page width. I generally do not try to overcontrol the column width by specifying a width for every column, but if you do, you have to also make sure that the widths you specify do not exceed the width remaining after you subtract the left and right margin width.

 

  I found other oddities in your code, such as a LS specification in PROC REPORT, which is ignored for RTF, PDF and ODS DOCUMENT, so that was unnecessary. Here's the modified code I tried:

data have; 
length sno group name sex res ans $200;
infile cards dlm=',';
input sno group name sex res ans;
cards;
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 1,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 2,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 3,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 3,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 3,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 4,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 4,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 4,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 5,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 5,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 5,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 5,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 5,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 5,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 5,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 5,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 5,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 5,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 5,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 5,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 6,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 6,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 6,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 6,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 6,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 6,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 6,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 6,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 7,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 8,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 9,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 10,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 10,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 10,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 10,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 10,xxxxxxxxxxxxx,Female,xxxxx,xxx
2,Group 10,xxxxxxxxxxxxx,Female,xxxxx,xxx
;
run;

 
ods html close;
** use original data -- with adjusted widths;
** so that all columns fit in the papersize;
** in this instance, LS=256 is not relevant for RTF;
** because only the papersize and margins impact RTF and PDF;
** the note will ONLY appear on the last page because that is the end of the logical page;
** even though the table will need multiple physical pages to display in RTF.;
** _PAGE_ only comes into play when PROC REPORT issues an internal "page break" and;
** NOT when RTF renders a physical page break in Word;
options nodate nonumber papersize=A4 topmargin="0.75in" 
        leftmargin=2.5mm rightmargin=2.5mm bottommargin=0.75in missing=' '; 
ods escapechar='^';
ods rtf file="c:\temp\test.rtf";
PROC REPORT DATA=have NOWD
    center                
    split='|' ;
    title1 j=r "Page ^{thispage} of ^{lastpage}";
    title3 j=c "1 Example";
    title5  j=c "Proc Report without paging variable";
    footnote1 j=l "This appears on every page";
    columns sno group name sex res ans;
    define sno   /display center style(column)=[cellwidth=20mm] 'Subject ID';
    define group /display center style(column)=[cellwidth=25mm] 'Test Group';
    define name /display left   style(column)=[cellwidth=80mm] 'Test Name';
    define sex  /display center style(column)=[cellwidth=15mm] 'Gender';
    define res  /display center style(column)=[cellwidth=30mm] 'Result';
    define ans  /display center style(column)=[cellwidth=15mm] 'Yes/No';
    compute after _page_/style={just=l font_face='Arial' font_size=9pt};
        line "Note:";
        line "Why the note cannot be displayed on each page...?";
    endcomp;
RUN;
ods rtf close;

This document is one logical page. And if you look, PROC REPORT writes the note only at the end, as designed:

pg3.png

I did change the code to show a footnote at the bottom of each page, which is traditionally, how you put something on each page in RTF because SAS writes the footnote text using the RTF controls for footers, just as the title text is written using the RTF controls for headers.

 

There was an alternate TAGSET for RTF written -- TAGSETS.RTF_SAMPLE -- I generally don't use it because the developer said (here https://communities.sas.com/t5/ODS-and-Base-Reporting/RTF-Tagset-and-PROC-REPORT-SPANROWS/td-p/12905 ) that it was never tested enough to be production. It was improved in SAS 9.3 and this tagset does give you the results you want with your COMPUTE AFTER, as shown below -- but I found it better to remove your width specifications to get this:

trsample_pg3.png

 

here's the changed code I used for tagsets.rtf_sample destination:

options nodate nonumber papersize=A4 topmargin="0.75in" 
        leftmargin=2.5mm rightmargin=2.5mm bottommargin=0.75in missing=' '; 
ods escapechar='^';
ods tagsets.rtf_sample file="c:\temp\tr_sample.rtf";
PROC REPORT DATA=have NOWD
    style(report)={width=100%}
    center                
    split='|' ;
    title1 j=r "Page ^{thispage} of ^{lastpage}";
    title3 j=c "1 alt Example";
    title5  j=c "Proc Report without paging variable";
    footnote1 j=l "This appears on every page";
    columns sno group name sex res ans;
    define sno   /display center 'Subject ID';
    define group /display center  'Test Group';
    define name /display left  'Test Name';
    define sex  /display center 'Gender';
    define res  /display center  'Result';
    define ans  /display center  'Yes/No';
    compute after _page_/style={just=l font_face='Arial' font_size=9pt};
        line "Note:";
        line "Why the note cannot be displayed on each page...?";
    endcomp;
RUN;
ods tagsets.rtf_sample close;

However, this was just an example with TAGSETS.RTFSAMPLE outside the realm of using ODS DOCUMENT. So I don't know whether that is going to work for you in the long run.

 

Typically what people do when they need to control what appears from a COMPUTE AFTER _PAGE_ block is to take control of page breaks. This is how I did it with your data and modified widths -- so the columns on the report fit within the margins:

** create a "fake" or "helper" page variable called page_break;
** so that every 30 obs will be assigned the same value for page_break;
data newhave;
  set have;
  page_break = ceil(divide(_n_,30));
run;
  
** now use that page_break variable to control what gets written at the end of every;
** logical page because the page_break variable has break processing turned on with;
** the BREAK statement;
options nodate nonumber papersize=A4 topmargin="0.75in" 
        leftmargin=2.5mm rightmargin=2.5mm bottommargin=0.75in missing=' '; 
ods escapechar='^';
ods rtf file="c:\temp\alt_page_test.rtf";
PROC REPORT DATA=newhave NOWD
    center                
    split='|' ;
    title1 j=r "Page ^{thispage} of ^{lastpage}";
    title3 j=c "2 Example";
    title5  j=c "Proc Report with paging variable";
    footnote1 j=l "This appears on every page";
    columns page_break sno group name sex res ans;
    define page_break / order page noprint;
    define sno   /display center style(column)=[cellwidth=20mm] 'Subject ID';
    define group /display center style(column)=[cellwidth=25mm] 'Test Group';
    define name /display left   style(column)=[cellwidth=80mm] 'Test Name';
    define sex  /display center style(column)=[cellwidth=15mm] 'Gender';
    define res  /display center style(column)=[cellwidth=30mm] 'Result';
    define ans  /display center style(column)=[cellwidth=15mm] 'Yes/No';
    break after page_break / page;
    compute after _page_/style={just=l font_face='Arial' font_size=9pt};
        line "Note:";
        line "Now the note can be displayed on each page...because of BREAK after page_break statement";
    endcomp;
RUN;
ods rtf close;

with these results:

 

using_helper_pg3.png

As you can see, I had to make a special "helper" variable called page_break and then my BREAK statement became BREAK AFTER page_break / page; which allowed the COMPUTE AFTER _PAGE_ to write the line for every logical page, as set by the page_break variable. Typically, unless you are doing BY group processing, you would not use a variable like your SNO for page breaking unless it really was being used for BY group processing. That's why my example and the other example both created a "helper" variable.

 

cynthia

JohnChen_TW
Quartz | Level 8

Hi, @Cynthia_sas

Many thanks for your suggestion.

However, if there are too many columns in one table, the data will be cut off to the next page when without width specifications.

Maybe there is no appropriate code to resolve this non-logical data...

It is an uncertainty that how long of column length will data employed before generating the table.

 

Thanks again!

JC

 

sas-innovate-2024.png

Don't miss out on SAS Innovate - Register now for the FREE Livestream!

Can't make it to Vegas? No problem! Watch our general sessions LIVE or on-demand starting April 17th. Hear from SAS execs, best-selling author Adam Grant, Hot Ones host Sean Evans, top tech journalist Kara Swisher, AI expert Cassie Kozyrkov, and the mind-blowing dance crew iLuminate! Plus, get access to over 20 breakout sessions.

 

Register now!

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 19 replies
  • 2670 views
  • 0 likes
  • 4 in conversation