HANA Code Pushdown: 5 Patterns That Actually Work
HANA

HANA Code Pushdown: 5 Patterns That Actually Work

If you've been writing ABAP for more than a few years, you've almost certainly written code that looks perfectly reasonable — a SELECT to fetch a list, a LOOP to calculate something, maybe a nested IF to filter rows — and then watched it crawl on a production system with 50 million records. The fix, more often than not, is HANA code pushdown: moving the computation from the application server down into the database engine, where it belongs.

This isn't just a buzzword. Pushdown is the single biggest lever you have for ABAP performance on S/4HANA. In this post I'll walk you through five concrete patterns — things I've used in real projects, not textbook examples — that will help you move logic where it actually runs fast.

Why HANA Code Pushdown Matters

The old R/3 mental model was: fetch data to the app server, process it in ABAP. The database was a dumb store. HANA flips this entirely. HANA is a columnar, in-memory engine with massive parallel processing. It can aggregate, filter, and transform billions of rows in milliseconds — but only if you let it.

Every time you pull raw data into an internal table and then loop over it to compute something, you're throwing away that advantage. You're transferring potentially gigabytes over the network, allocating memory on the app server, and running single-threaded ABAP logic. The database already has the data in cache. Just ask it to do the work.

The golden rule: never bring data to the logic — push the logic to the data.

Pattern 1: Aggregation in SQL, Not in LOOP AT

This is the most common anti-pattern I see in legacy code. Someone needs the total invoice amount per company code. They do a SELECT to pull all line items, then loop over them summing up values in ABAP. On a small system it worked. On S/4HANA with millions of FI documents, it kills performance.

The fix is trivial — let SQL aggregate:

" Anti-pattern: fetch everything, aggregate in ABAP
SELECT * FROM bseg
  WHERE bukrs IN @bukrs_range
  INTO TABLE @DATA(lt_bseg).

LOOP AT lt_bseg INTO DATA(ls_bseg).
  lv_total += ls_bseg-dmbtr.
ENDLOOP.

" Pushdown pattern: aggregate in HANA
SELECT bukrs,
       SUM( dmbtr ) AS total_amount
  FROM bseg
  WHERE bukrs IN @bukrs_range
  GROUP BY bukrs
  INTO TABLE @DATA(lt_totals).

The second version transfers a handful of rows. The first might transfer millions. On a real production system, I've seen this change reduce report runtime from 4 minutes to under 3 seconds. Same logic, completely different data volume crossing the network.

Apply this to any SUM, COUNT, MIN, MAX, AVG you're currently computing in ABAP. If the data lives in the database, the aggregation should too.

Pattern 2: Filtering with Expressions, Not Post-Select IF Blocks

Another classic: a broad SELECT with minimal WHERE clause, followed by a LOOP with complex filtering logic in ABAP. This made sense when the database optimizer was weak and you wanted to keep SQL simple. HANA's optimizer is not weak.

Push your conditions into the WHERE clause or use CASE expressions inside SELECT:

" Anti-pattern: filter in ABAP after loading
SELECT matnr, werks, labst, eindt
  FROM mard
  INTO TABLE @DATA(lt_stock).

LOOP AT lt_stock INTO DATA(ls_stock)
  WHERE labst > 0 AND eindt < @lv_cutoff.
  " process...
ENDLOOP.

" Pushdown pattern: filter in HANA
SELECT matnr, werks, labst, eindt
  FROM mard
  WHERE labst > 0
    AND eindt < @lv_cutoff
  INTO TABLE @DATA(lt_stock).

For more complex conditional logic, CASE expressions let you do branching inside the query itself:

SELECT matnr,
       werks,
       labst,
       CASE WHEN labst > 100 THEN 'HIGH'
            WHEN labst > 10  THEN 'MED'
            ELSE                  'LOW'
       END AS stock_level
  FROM mard
  WHERE labst > 0
  INTO TABLE @DATA(lt_classified).

You've just pushed a classification that would've been a nested IF chain in ABAP straight into HANA. The result set already has the derived field when it arrives.

Pattern 3: Window Functions for Running Totals and Rankings

This one surprises a lot of ABAP developers who haven't caught up with Open SQL's modern capabilities. You need row numbers, rankings, or running totals? Stop fetching sorted data and computing position in ABAP. HANA has window functions — and Open SQL exposes them.

" Anti-pattern: sort in ABAP, compute rank manually
SELECT vbeln, posnr, netwr
  FROM vbap
  WHERE vkorg = @lv_org
  ORDER BY netwr DESCENDING
  INTO TABLE @DATA(lt_items).

DATA lv_rank TYPE i VALUE 1.
LOOP AT lt_items INTO DATA(ls_item).
  ls_item-rank = lv_rank.
  lv_rank += 1.
  MODIFY lt_items FROM ls_item.
ENDLOOP.

" Pushdown pattern: rank in HANA
SELECT vbeln, posnr, netwr,
       RANK() OVER( ORDER BY netwr DESCENDING ) AS rank
  FROM vbap
  WHERE vkorg = @lv_org
  INTO TABLE @DATA(lt_ranked).

For running totals — say cumulative revenue per month — it looks like this:

SELECT budat,
       dmbtr,
       SUM( dmbtr ) OVER( ORDER BY budat
                          ROWS BETWEEN UNBOUNDED PRECEDING
                          AND CURRENT ROW ) AS running_total
  FROM bkpf
  JOIN bseg ON bkpf~belnr = bseg~belnr
            AND bkpf~bukrs = bseg~bukrs
  WHERE bkpf~bukrs = @lv_bukrs
  INTO TABLE @DATA(lt_running).

These were impossible to express cleanly in ABAP without either a CDS view or a LOOP with manual accumulation. Now they're a single query. If you haven't read through the ABAP SQL optimization patterns we covered earlier, that's a good companion read for understanding what Open SQL can do on HANA.

Pattern 4: CDS Views for Reusable Pushdown Logic

Sometimes the logic is complex enough that embedding it in inline SQL becomes unmaintainable. The right answer is a CDS view — a named, reusable, database-resident definition that any consumer (ABAP, OData, Fiori) can reference.

Think of a CDS view as a pushdown contract: you define the logic once at the database level, and consumers just reference it by name. The computation always happens on HANA, regardless of where the call comes from.

@AbapCatalog.sqlViewName: 'ZMAT_STOCK_KPI'
@AccessControl.authorizationCheck: #CHECK
define view Z_MATERIAL_STOCK_KPI
  as select from mard
  association [0..1] to mara as _Material
    on $projection.matnr = _Material.matnr
{
  key matnr,
  key werks,
      sum( labst )                          as unrestricted_stock,
      sum( einme )                          as stock_in_transfer,
      sum( labst ) + sum( einme )           as total_available,
      case when sum( labst ) > 0
           then 'X'
           else ''
      end                                   as has_stock,
      _Material
}

Now any ABAP SELECT against Z_MATERIAL_STOCK_KPI runs that aggregation logic on HANA. No duplication. No risk of one developer computing it differently in their own LOOP. We've written extensively about CDS view performance anti-patterns — worth reading alongside this to make sure your CDS definitions don't accidentally undo the pushdown benefit.

One important caveat: CDS views with ABAP-managed virtual elements or complex ABAP expressions don't push down. Keep the logic in SQL-expressible constructs. The moment you introduce ABAP-side computation in a CDS context, you lose the pushdown benefit for that computation.

Pattern 5: AMDP — When You Truly Need Procedural Logic on HANA

Patterns 1–4 cover most real-world scenarios. But occasionally you genuinely need procedural logic — loops, conditionals, cursor-like processing — that SQL can't express cleanly. For those cases, ABAP Managed Database Procedures (AMDP) let you write SQLScript that executes directly on HANA.

SQLScript runs inside the database engine. No network round-trips. No app-server memory pressure. It's the deepest form of pushdown.

CLASS zcl_stock_amdp DEFINITION PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_amdp_marker_hdb.

    CLASS-METHODS get_critical_stock
      IMPORTING VALUE(iv_plant)   TYPE werks_d
                VALUE(iv_threshold) TYPE menge_d
      EXPORTING VALUE(et_result)  TYPE STANDARD TABLE
                               OF zstock_result_s.
ENDCLASS.

CLASS zcl_stock_amdp IMPLEMENTATION.
  METHOD get_critical_stock
    BY DATABASE PROCEDURE FOR HDB
    LANGUAGE SQLSCRIPT
    USING mard mara.

    et_result = SELECT m.matnr,
                       m.werks,
                       m.labst,
                       a.matkl
                FROM   mard AS m
                JOIN   mara AS a ON m.matnr = a.matnr
                WHERE  m.werks    = :iv_plant
                  AND  m.labst    < :iv_threshold
                  AND  m.labst    > 0;
  ENDMETHOD.
ENDCLASS.

Use AMDP when:

  • Logic genuinely requires iteration or complex branching that SQL can't handle
  • You need to call native HANA functions not exposed by Open SQL
  • The procedure will be called frequently enough that its HANA-resident execution matters

Don't use AMDP for simple aggregations or filtering — Patterns 1–4 are cleaner and more maintainable. AMDP is a power tool for specific situations, not a general-purpose pushdown mechanism.

Also: AMDP bypasses the ABAP authorization framework. You'll need to handle authorization checks explicitly in the calling ABAP code. And since it's database-specific, you lose portability — not a practical concern for most S/4HANA shops, but worth knowing.

How to Identify What Needs Pushdown

Before you start refactoring, measure first. The performance tools — ST05, SAT, and SQLM — will show you exactly which SQL statements are expensive and how much data they're transferring. High transfer volumes with simple WHERE clauses are the clearest signal that application-side processing is happening on data that should stay in the database.

Look for these patterns in your traces:

  • Large row counts fetched, small row counts actually used — filtering happening in ABAP
  • Repeated similar queries in a loop — N+1 problem, solvable with a single aggregating query
  • Full table scans on large tables with minimal WHERE conditions

For SELECT tuning specifically, ABAP SQL optimization patterns and the discussion of FOR ALL ENTRIES vs JOIN performance both tackle complementary problems you'll hit in the same codebase.

Pushdown Isn't Always the Answer

I want to be honest: pushdown has limits. Complex business logic with many branches, object-oriented abstractions, and ABAP-specific features (BAPIs, BADIs, business object processing) all belong on the application server. The goal isn't to write everything in SQL — it's to make sure data-intensive computation happens where data lives.

A good heuristic: if an operation's cost scales linearly with the number of rows in a database table, it should run on HANA. If an operation's cost depends on business rules, conditionals, and external system calls, it belongs in ABAP.

Putting It Together

Start with Pattern 1 (aggregation) and Pattern 2 (filtering) — they deliver the biggest wins for the least effort and touch the most common legacy code patterns. Pattern 3 (window functions) is worth learning because it eliminates whole categories of sort-and-loop code. Pattern 4 (CDS views) is where you invest for reusability and architectural cleanliness. Pattern 5 (AMDP) is in your toolkit for the edge cases.

The shift in mindset is the hard part. After years of thinking "fetch, then process", you need to flip to "express the result I want, let HANA figure out how". Once that click happens, you'll start seeing pushdown opportunities everywhere — and your runtime traces will look very different.