Variables and Totals

Variables are the report engine’s built-in accumulators. They provide counts, sums, averages, and other aggregate values such as group totals, page totals, running balances, and grand totals. The basics of creating a variable in the Designer are shown in the "Variables and Groups" section of the Getting Started chapter, and the XML format is listed in the Specification part. This chapter explains how variables work at run time: when they are updated and reset, what each section of the report sees, and how to build typical totals with them. The last section is the complete reference of the built-in system variables.

How variables work

A variable is defined once for the whole report, in the <variables> element of the report definition. In the Designer, select Report  Variables…​ and edit the variables in the table of the dialog (columns ID, Expression, Function, Scope, and Init Value).

<variables>
    <variable id="total_value" type="num" funcType="sum" scope="group" initValue="0">value</variable>
    <variable id="balance" type="num" funcType="sum" scope="report" initValue="0">value</variable>
    <variable id="rows" type="num" funcType="count" scope="page" initValue="0">ProductID</variable>
</variables>
id

The name of the variable. In expressions and templates, the variable is referenced as $V{id}; in a field, set the field source type to Variable (ftype="var") and enter the ID as the column.

funcType

The aggregate function: count, sum, average, std, min, or max (case-insensitive). An unknown name is read as count.

scope

The reset scope: report, page, or group (case-insensitive). An unknown name is read as report.

initValue

The value the variable starts from and returns to whenever it is reset.

type

The data type. Variables are always numeric (floating point) in the engine; the Designer writes type="num", and the attribute is not evaluated when the report is loaded.

Element text

The variable expression: the source of the value that is aggregated (see The variable expression).

Function types

Each time the variable is updated, the current value of its expression is passed to the aggregate function.

Function Result

COUNT

The number of updates, that is, the number of processed rows (the expression value itself is not used, but it must be a valid column). The initial value is the starting count.

SUM

The sum of the values.

AVERAGE

The arithmetic mean of the values processed since the last reset.

STD

The standard deviation of the values processed since the last reset. The engine calculates the population standard deviation (the squared deviations are divided by n, not n-1).

MIN

The smallest value. The initial value setting is ignored: a MIN variable always starts from the largest representable number (about 1.8e308), so do not print it before the first row has been processed.

MAX

The largest value. The comparison starts from the initial value, so if all values can be negative, set the initial value to a number smaller than any expected value (the default 0 would otherwise be the result).

The variable expression

The variable expression can take two forms:

Data source column

The name of a column of the detail’s data source, for example value, or a column of a specific data source in the datasourceID.column form, for example DataSource1.value. Note that the column is written without the $D{} reference syntax. The variable is then bound to that data source (see Variables in reports with more than one detail).

Script expression

If the expression contains the <% and %> tokens, the text is evaluated like a field expression: the data source, parameter, and variable references are substituted, the embedded script is executed, and the result is converted to a number. The Variable_Expression.ncr demo report sums a calculated ratio this way:

<variable id="calculations" type="num" funcType="sum" scope="report" initValue="0">&lt;%$D{width}/$D{height}%&gt;</variable>

In the Designer you simply type <%$D{width}/$D{height}%> into the Expression column; the XML escaping is done automatically.

A variable based on a script expression is not bound to any data source, so the automatic page break correction described in Evaluation timing does not apply to it. Use a plain column expression for page totals and carried-forward balances whenever possible.

Reset scopes

The scope determines when the variable returns to its initial value:

Report

The variable is reset only when the report starts. It accumulates through the whole report: use it for grand totals and running balances.

Page

The variable is reset at the beginning of every page, before the page header is printed. Use it for page totals.

Group

The variable is reset after the footer of a group is printed, but only if the variable is listed in that group’s Reset variables list (the resetVariables attribute of the <group> element). A Group scope variable that no group resets behaves like a Report scope variable.

In addition, all variables are reset when the report starts (in double pass mode, at the start of both passes), and when the engine moves to the next detail section, the variables bound to the data source of the new detail are reset, whatever their scope.

In the Designer, only Group scope variables are offered in the Reset variables list of the group dialog. The engine itself resets every variable named in the resetVariables attribute, regardless of its scope.

Evaluation timing

All variables are updated once for every data row, before the row is printed. For each row of a detail section, the engine performs the following steps:

  1. If the value of a group expression has changed, the footers of the closed groups are printed (the innermost group first). The footers still see the variable values of the previous row, that is, the totals of the closed group. After each footer, the variables listed in that group’s Reset variables list are reset.

  2. All variables are updated with the current row.

  3. If a new group starts, the headers of the new groups are printed. During the group header, the update made by the current row is temporarily taken back, so a group header shows the value before the first row of the group (0 for a freshly reset group variable).

  4. The detail section is printed. The detail sees the value including the current row, so a Report scope SUM variable printed in the detail is a running total.

The page and report level sections see the following values:

Section Value of the variables

Report header

The initial values (the first row is temporarily taken back).

Page header

The values after the page reset: Page scope variables show their initial value, Report and Group scope variables show the total of all rows printed on the previous pages (the "brought forward" value).

Detail

The values including the current row.

Group header

The values before the first row of the new group.

Group footer

The values including the last row of the group.

Page footer

The values including the last row printed on the page.

Report footer

The final values. Note that Group scope variables have already been reset by the final group footers, so use a Report scope variable for a grand total in the report footer.

The page break needs special care, because when the engine finds that a detail row does not fit on the page, the variables have already been updated with that row. In this case the engine steps back one row and takes back the last update of the variables bound to the current data source, prints the page footer, begins the new page (resetting the Page scope variables and printing the page header), and only then reapplies the update. As a result, the page footer of the old page does not contain the row that moved to the next page, and the row is counted in the Page scope variables of the new page. The same correction is applied when a group header does not fit on the page.

This correction is controlled by the Data trimming report option (the dataTrimming option element), which is on by default. If you turn it off, the page footer and the page header of the next page show values that already include the first row of the next page.

The correction can take back only the last update. With MIN and MAX variables the step back cannot restore the previous value, so these functions are not suitable for page level values. A repeated group header (Reprint header on new page) is printed after the update has been reapplied, so in a repeated group header a group variable already includes the first row of the new page.

Variables in reports with more than one detail

By default, every variable is updated for every row of every detail. A variable bound to a column of another data source is updated with that data source’s current value, which is rarely what you want. If the report has more than one detail section, turn on the updateOnlyCurrentDataSourceVariables report option: then a variable bound to a data source column is updated only while a detail of the same data source is being processed. Variables with a script expression are always updated.

To get an alert when a variable expression cannot be evaluated (for example, a misspelled column name), turn on the varErrorAlert report option; otherwise an invalid variable is silently left unchanged.

Page totals and running balances

Invoices, ledgers, and long lists often need three kinds of totals on every page: the total of the current page, the running balance up to the end of the page, and the balance carried over from the previous page at the top of the next page. With the timing rules above, all three need only two variables:

  • a Page scope SUM variable for the page total, printed in the page footer;

  • a Report scope SUM variable for the running balance, printed in the page footer as the "carried forward" value and in the page header as the "brought forward" value.

The PageTotalsAndBalanceInProductlist.xml demo report prints the running balance of the value column in the detail, in the page footer, and in the page header. The page_total_example.xml demo does the same in a grouped product list, and prints the grand total in the footer of a constant (%CONST) group. The following example is based on these demos, extended with a page total variable. Geometry and font attributes are omitted.

<variables>
    <variable id="page_total" type="num" funcType="sum" scope="page" initValue="0">value</variable>
    <variable id="balance" type="num" funcType="sum" scope="report" initValue="0">value</variable>
</variables>
<pageheader id="phead">
    <label id="1">Brought forward:</label>
    <field id="2" type="num" ftype="var" formatting="true" format="f" precision="2"
        printWhen="$V{pageno} &gt; 1">balance</field>
</pageheader>
<pagefooter id="pfoot">
    <label id="1">Page total:</label>
    <field id="2" type="num" ftype="var" formatting="true" format="f" precision="2">page_total</field>
    <label id="3">Carried forward:</label>
    <field id="4" type="num" ftype="var" formatting="true" format="f" precision="2">balance</field>
    <field id="5" type="num" ftype="sys" arg="Page: %1">pageno</field>
</pagefooter>
<details>
    <detail id="Detail1" datasource="DataSource1">
        <items>
            <field id="1" type="num" ftype="ds">ProductID</field>
            <field id="2" type="txt" ftype="ds">ProductName</field>
            <field id="3" type="num" ftype="ds" formatting="true" format="f" precision="2">value</field>
            <!-- running balance in every row -->
            <field id="4" type="num" ftype="var" formatting="true" format="f" precision="2">balance</field>
        </items>
    </detail>
</details>

Suppose the first page holds rows with the values 10, 20, and 30, and the next row (40) does not fit. The report then prints:

Place page_total balance

Page 1 header

0

0 (hidden by the print condition)

Page 1 detail rows

10, 30, 60

10, 30, 60

Page 1 footer

60

60 (carried forward)

Page 2 header

0

60 (brought forward)

Page 2 first detail row

40

100

The printWhen expression of the brought-forward field hides it on the first page, where there is nothing to bring forward. The same technique works for counts (COUNT) and for group level running totals: a Group scope variable printed in the page header shows the running total of the current group up to the end of the previous page.

In the Designer, set the Reset scope to Page for the page total, and Report for the balance. No group is needed for these variables. If you also need a grand total at the end of the report, print the Report scope variable in the report footer or in the footer of a constant group.

Group totals and nested group totals

A group total is a Group scope variable that is reset by its group. Since the reset happens after the group footer is printed, the footer shows the total of the whole group, and the next group starts from the initial value again. The GroupFunc.ncr demo, for example, sums the col0 column by month:

<variables>
    <variable id="total" type="num" funcType="sum" scope="group" initValue="0">col0</variable>
</variables>
...
<group id="Group0" groupExp="$MONTH{col1}" resetVariables="total" reprintHeader="false" startsOnNewPage="false">
    <groupheader id="GH_Group0">...</groupheader>
    <groupfooter id="GF_Group0">
        <field id="0R2HF" type="num" ftype="var">total</field>
    </groupfooter>
</group>

For nested groups, define one variable for each level, each with the same expression, and let every group reset only its own variable. The groups of a detail are nested in the order in which they are listed: the first group is the outermost one. When an outer group changes, all of the inner groups are closed too, so the inner footers are printed first (and reset their variables), then the outer footer. The group_simple_totals.xml demo uses a constant outer group for the grand total and an inner group for the subtotals:

<variables>
    <variable id="total_0" type="num" funcType="sum" scope="group" initValue="0">price</variable>
    <variable id="total_1" type="num" funcType="sum" scope="group" initValue="0">price</variable>
</variables>
...
<groups>
    <group id="Group0" groupExp="%CONST" resetVariables="total_0">
        <groupheader/>
        <groupfooter>
            <field id="1" type="num" ftype="var">total_0</field>   <!-- grand total -->
        </groupfooter>
    </group>
    <group id="Group1" groupExp="groupID" resetVariables="total_1">
        <groupheader>...</groupheader>
        <groupfooter>
            <field id="0" type="num" ftype="var">total_1</field>   <!-- subtotal of the groupID group -->
        </groupfooter>
    </group>
</groups>

The resetVariables attribute is a comma-separated list, so one group can reset several variables, for example a sum and a count of the same group (resetVariables="sum_price,count_items"). A variable must not be listed in an outer group only, or the inner footers would show the accumulated value of the outer group.

Other useful patterns:

  • A row number restarting in every group: a COUNT variable reset by the group and printed in the detail. The variable-row-index.ncr demo uses this technique.

  • A group average: an AVERAGE variable reset by the group, printed in the group footer.

  • A group total printed in the page header and footer: the GroupTotals.xml demo prints Group scope variables in the page header, the page footer, the group headers (where they show the value before the first row of the group), and the group footers.

Modifying variables

In special cases, the regular aggregation is not enough: you may want to add an extra amount to a total, subtract a value under a condition, or restart a counter in the middle of a group. For this purpose, the report engine contains a variable modifier (the NCReportVariableModifier class), which changes the current value of a variable directly.

The modification lines are written into the conditional formatting code of a Label or a Field (see Conditional Formatting). A line that starts with $V{ is not a style definition but a variable modification, in the following form:

$V{variableID} operator expression

The available operators are:

Operator Effect

=

Sets the variable to the value of the expression.

+=

Adds the value to the variable.

-=

Subtracts the value from the variable.

*=

Multiplies the variable by the value.

/=

Divides the variable by the value.

The expression on the right side can contain data source, parameter, and variable references, and it is evaluated by the script engine, so it can be a constant, a reference, or a script expression such as $D{price}*$D{qty} or ($D{discount}>0 ? 1 : 0). A COUNT variable is set to the integer part of the result.

The modification is executed when the item is printed: each time the section that contains the item is rendered and the item’s print condition (Print only when expression is true) is true. Therefore the print condition of the item acts as the condition of the modification. The new value is visible for the items rendered after the modifying item in the same section, and for all later sections. The variable’s own aggregation continues from the modified value at the next row.

The Variable-Modify-By-CondFormat-Demo.xml demo report has three Report scope variables. The label in the detail section changes its font weight conditionally, and also adds 2 to the v3 variable in every row:

<variables>
    <variable id="v2" type="num" funcType="sum" scope="report" initValue="0">col0</variable>
    <variable id="v3" type="num" funcType="sum" scope="report" initValue="0">col0</variable>
    <variable id="v1" type="num" funcType="count" scope="report" initValue="0">col0</variable>
</variables>
...
<detail id="Detail1" datasource="DataSource1">
    <items>
        <label id="1" cf="1">
            <tr>Lorem ipsum </tr>
            <cf>font-bold:{if($D{col1}&gt;20) true; else false;}
$V{v3}+=2
</cf>
        </label>
        <field id="N7Q30" type="txt" ftype="var">v1</field>
        <field id="HTPFP" type="txt" ftype="var">v2</field>
        <field id="ROE94" type="num" ftype="var" formatting="true" format="f" precision="2">v3</field>
    </items>
</detail>

The v2 and v3 variables sum the same column, but because the label is rendered before the v3 field, the v3 column shows the running sum plus 2 for every row printed so far (in the n-th row, v2 + 2 * n).

Keep the following rules in mind:

  • The $V{ must be at the very beginning of the line, and every modification must be on its own line. The style lines and the modification lines can be mixed in any order; the style evaluation skips the modification lines.

  • The operator is identified by the first of the strings +=, -=, /=, *=, and = that occurs in the line. If you use the = operator, the expression must not contain any further = character (for example ==, >=, or <=); use another form, or move the condition into the print condition of the item.

  • An unknown variable ID or an empty expression makes the engine skip the rest of the modification lines of the item.

  • The modification is applied every time the item is printed. If the section is printed more than once (for example, a section with automatic height that did not fit on the page and is printed again on the next page), the modification is applied again. The automatic page break correction (see Evaluation timing) does not take back a modification.

System variables

System variables are values provided by the report engine itself, such as the page number, the current date, or the name of the application. They are read-only and do not need to be defined. A system variable can be used:

  • in a field with the System variable source type (ftype="sys"), entering the name as the column;

  • in a field with the Variable source type, in an expression, or in a template, with the $V{name} syntax, for example Page $V{pageno} of $V{pagecount}.

With the $V{} syntax, the name is first looked up among the report’s own variables, so a user variable with the same name hides the system variable. The names are case-sensitive. An unknown name returns the text #VAR#.

The following table lists all of the system variables recognized by the report engine.

Name Value Notes

pageno, pagenum

The current page number of the report.

At least 1. Restarts in every report of a batch, in every forced copy, at a group with the Reset page number option, and, if the subReportResetPageNo option is on, in every sub-report iteration.

pagecount

The total number of pages of the report.

Available only in double pass mode (see "Double pass mode") and in Text Document mode; otherwise it returns 0. If page numbering is restarted by a group, it is the number of pages counted since the last restart.

gpageno

The global page number.

Counted continuously across the reports of a batch (see Batch Report Mode) and across forced copies. Without batch mode and forced copies it equals pageno, unless page numbering is restarted.

reportno, reportnum

The sequence number of the current report in a batch, starting from 1.

1 if the report does not run in batch mode.

reportcount

The number of reports in the batch.

1 if the report does not run in batch mode.

forcecopies

The number of forced copies.

Set by NCReport::setForceCopies(); the default is 1.

currentcopy

The number of the copy being rendered when forced copies are used.

Zero-based: the first copy is 0, the last is forcecopies - 1.

currentrow

The current row number of the detail.

Zero-based in normal reports: the first row of a detail is 0. It restarts in every detail section. In a group footer, it refers to the last row of the group.

groupcount

The number of groups of the current group level printed so far.

Meaningful only in group headers and group footers; the counter belongs to the group of the section being printed. It is not offered in the Designer’s system variable list; type the name manually.

date

The current date.

QDate::currentDate(), read each time it is evaluated. Use the date formatting options of the field to format it.

time

The current time.

QTime::currentTime(), read each time it is evaluated.

datetime

The current date and time (timestamp).

QDateTime::currentDateTime(), read each time it is evaluated.

appname

The name of the report engine: NCReport.

Constant of the NCReport library, not of your application.

applongname

The long name of the report engine: NCReport Report Generator.

Constant of the NCReport library.

appversion

The version number of the NCReport library, for example 2.35.2.

Constant of the NCReport library.

appinfo

The full info string of the report engine: name, version, release name (Commercial or Evaluation Version), and copyright.

Depending on the build, the text may end after the version number.

appcopyright

The copyright notice of NCReport.

Constant of the NCReport library.

coreappname

The name of the host application.

QCoreApplication::applicationName(), as set by your application.

coreappversion

The version of the host application.

QCoreApplication::applicationVersion().

coreorgname

The organization name of the host application.

QCoreApplication::organizationName().

coreorgdomain

The organization domain of the host application.

QCoreApplication::organizationDomain().

os

The operating system: Windows, Linux, or MacOS.

Determined at compile time. Empty on other platforms.

qtversion

The version of the Qt library at run time.

qVersion(), for example 6.8.2.

lang, language

The current language code of the report.

See "Internationalization". The default (no translation) language code is 00.

The app* variables describe the NCReport engine. To print the name and version of your own application, set them with QCoreApplication::setApplicationName() and setApplicationVersion() and use the coreappname and coreappversion variables.