ABAP Blog Posts
cancel
Showing results for 
Search instead for 
Did you mean: 
Lenngoetz
Associate
Associate
741

RAP Analytical Tables

Dear ABAP Community,

If you've ever built a Fiori app with reporting, you know how much setup the old analytics stack required. Cubes, dimensions, analytical queries, all wired together through the Analytical Engine. It worked, but you paid for it in modeling effort.

RAP Analytical Tables skip most of that ceremony. Introduced with SAP BTP ABAP Environment 2511, SAP S/4HANA Cloud Public Edition 2602, and SAP S/4HANA Cloud Private Edition 2027, they run on OData V4, sit inside a regular RAP Fiori Elements app, and all it takes is a projection view with a few annotations. No cubes, no dimensions, no Analytical Engine.

This post walks through the setup, the CDS annotations that matter, and the limits you'll run into. Code included.

So What Exactly Is This?

A RAP Analytical Table is a Fiori list report that can do math. Sums, minimums, maximums, averages, and distinct counts. The numbers show up right in the table, no need to export to Excel or jump into a separate analytics tool.

Under the hood, it's a projection view annotated with @odata.applySupportedForAggregation: #FULL. OData V4 picks that up, enables the aggregation features, and Fiori Elements renders the right table control automatically.

There are catches, though. You can't hook into the processing with calculation, filter, or sort exits.

Five Aggregate Functions

You get five options:

 

Annotation
What it does
#SUMAdds up values. Pick a data type that won't overflow.
#MAXHighest value. Works for dates too.
#MINLowest value. Also works for dates.
#AVGAverage. Use a precise numeric type or you'll get rounding surprises.
#COUNT_DISTINCTCounts how many different values exist for a referenced element.

One thing to keep in mind: each element can only carry one aggregation. Need both sum and average for the same field? Create two elements, one for each.

COUNT_DISTINCT Is a Bit Different

This one needs extra attention. Unlike the other four, #COUNT_DISTINCT requires an @Aggregation.ReferenceElement annotation that points to the field you want to count.

Say you want to know how many different currencies appear in your data:

@Aggregation.default: #COUNT_DISTINCT
@EndUserText.label: 'Different Currencies (#COUNT_DISTINCT)'
@Aggregation.referenceElement: ['CurrencyCode']
differentCurrencies,

Here's the gotcha: on a single, ungrouped row, the count is always 1 regardless of the actual value stored in the field. The field's own content is completely ignored.

Note: Because of this, add a dedicated field for the count. If you reuse an existing field, the values on ungrouped rows will confuse everyone.

What It Looks Like

Once the annotations are in place, Fiori Elements does the rest. Totals, averages, counts show up directly in the list report, either as a summary row at the bottom or inline per column.

Bildschirmfoto 2026-04-22 um 10.15.56.png

RAP analytical table with aggregated data: totals per currency and a distinct count of 4.

One thing that tripped me up at first: Fiori groups by whatever columns are currently visible. Behind the scenes, that's the $select in the OData request. So if you add a column, the grouping changes.

To lock the grouping to specific key fields instead, use:

@UI.presentationVariant: [{
  requestAtLeast: ['TravelUUID']
}]

That tells Fiori to always include TravelUUID in the grouping, regardless of which columns are on screen.

The same annotation also supports groupBy and sortOrder if you want to define a default grouping and sort order upfront.

Users can also group manually through the app settings or by right-clicking a column header.

Setting It Up

You need a read-only business object exposed through an OData V4 UI service. If you need an editable variant, that is currently in the works. Check the ABAP Platform Roadmap.

The example here is based on /DMO/A_TRVL_ANA from the ABAP Flight Reference Scenario.

Three steps.

1. Interface View

Your foundation. Fields you want to aggregate need to exist here. If you need MIN and MAX on the same source field, define it twice with different aliases. Currency fields need @Semantics.amount.currencyCode. For COUNT_DISTINCT, add a dedicated element using cast(1 as abap.int4).

2. Projection View

This is where it gets interesting. Add @odata.applySupportedForAggregation: #FULL at the view level and tag individual elements with @Aggregation.Default:

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Travel Analytics Projection'
@OData.applySupportedForAggregation: #FULL
define root view entity C_TravelAnalytics
  provider contract transactional_query
  as projection on R_TravelAnalytics
{
  key TravelUUID,
      AgencyID,
      CustomerID,

      @Aggregation.default: #SUM
      TotalPrice,

      @Aggregation.default: #AVG
      TotalPrice as AveragePrice,

      @Aggregation.default: #MAX
      BeginDate,

      @Aggregation.default: #MIN
      EndDate,

      CurrencyCode,

      @Aggregation.default: #COUNT_DISTINCT
      @Aggregation.referenceElement: ['CurrencyCode']
      differentCurrencies
}

3. Metadata Extension and Service

Define the UI layout (line items, selection fields, presentation variant), then expose it: service definition, service binding, publish.

That's it. Seriously. Most of the work is in the CDS annotations, and once those are right, the framework takes over.

The Fine Print

Let's be upfront about the limits:

  • Read-only. An editable variant is in the works. ABAP Platform Roadmap.
  • No exits. Currently, calculation, filter, and sort exits cannot be annotated.
  • No extensibility. CDS entities annotated for RAP analytical tables consumption are not enabled for extensibility.
  • The classic @analytics.query stack could do more: virtual elements, calculated measures, complex hierarchies. RAP Analytical Tables can't touch any of that.

But that's kind of the point. You trade flexibility for simplicity. And if all you need is a reporting table with sums, averages, and grouping, you'll be done before lunch.

RAP Analytical Tables do not cover every analytics use case. For virtual elements, calculated measures, or deep hierarchies, the classic @analytics.query approach is still the way to go. But for straightforward reporting with sums, averages, and grouping, the setup is minimal and the result is immediately usable.