@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;
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
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:
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:
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:
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
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
Are you ready for the spotlight? We're accepting content ideas for SAS Innovate 2025 to be held May 6-9 in Orlando, FL. The call is open until September 25. Read more here about why you should contribute and what is in it for you!
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.