Extracting BRF+ Decision Table Data to Internal Tables for High-Performance ABAP Access
Learn how to extract SAP BRF+ decision table data into ABAP internal tables for high-performance access. This guide covers API usage, buffering strategies, HANA integration, and best practices for optimizing SAP business rule evaluations and system integration.
When working with SAP BRF+ decision tables, it’s easy to fall into the trap of assuming they’re the most efficient way to manage business rules in ABAP applications. And in many cases, they are - especially when you’re dealing with occasional lookups or simpler rule sets. But once you scale up the number of calls - say, 20,000+ evaluations per order processing cycle - things can go downhill quickly.
This post is about a performance-optimized approach: extracting BRF+ decision table data into ABAP internal tables for repeated, buffered access. We’ll cover:
- What this method is
- When it works (and when it doesn’t)
- How to implement it using the BRF+ API
- Coupling it with exit classes or HANA tables
- Pros and cons of this architecture
📚 What is BRF+ and Why Use Its API?
BRF+ (Business Rule Framework Plus) is SAP’s rules engine that allows business users and developers to define rules outside of application logic. The most commonly used element is the decision table, which maps input values to output values in a spreadsheet-like fashion.
However, evaluating BRF+ decision tables repeatedly in tight loops (e.g., one evaluation per sales order or item) can cause significant overhead. Each BRF+ evaluation involves:
- Context setup and validation
- Scanning all lines of the decision table for a hit
- Potential duplicate or inconsistent entries
SAP provides an API for reading BRF+ artifacts programmatically. And that’s the key here: we can extract a decision table once, transform it into a hashed internal table, and then use it directly in ABAP logic.
🛠️ Use Case: Improving Performance by Extracting BRF+ Decision Tables
Let’s say you have a BRF+ decision table for pricing rules, discounts, or workflow routing. Each time an order is processed, BRF+ is called to evaluate the rule. This happens thousands of times per job - resulting in a measurable performance hit.
Solution: Use the BRF+ API to load the decision table once at startup or during initialization, and store the data in a hashed internal table.
🔍 Caveat:
This method only supports key fields with exact match conditions (EQUALS
). Any decision table using conditions like:
SUBSTRING
GREATER THAN / LESS THAN
EXCLUDING
- …
…won’t work with this direct extraction method.
📦 How to Extract Data from a BRF+ Decision Table
SAP provides classes like CL_FDT_FUNCTION
, CL_FDT_DECISION_TABLE
, and CL_FDT_SESSION
to programmatically interact with BRF+.
Here’s a basic pattern to extract data from a decision table:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
DATA: ls_sel TYPE if_fdt_query=>s_selection,
lt_sel TYPE if_fdt_query=>ts_selection,
lt_names TYPE if_fdt_query=>ts_name,
lt_dt_data TYPE TABLE OF if_fdt_decision_table=>s_table_data
WITH NON-UNIQUE SORTED KEY row COMPONENTS row_no,
lr_table TYPE REF TO data,
lr_line TYPE REF TO data,
lo_decision_table TYPE REF TO cl_fdt_decision_table.
FIELD-SYMBOLS: <t_table> TYPE STANDARD TABLE.
" find the decision table for that application
ls_sel-queryfield = if_fdt_admin_data_query=>gc_fn_application_id.
ls_sel-sign = 'I'.
ls_sel-option = 'EQ'.
ls_sel-low = iv_application_id.
INSERT ls_sel INTO TABLE lt_sel.
ls_sel-queryfield = if_fdt_admin_data_query=>gc_fn_object_type.
ls_sel-sign = 'I' .
ls_sel-option = 'EQ'.
ls_sel-low = if_fdt_constants=>gc_object_type_expression.
INSERT ls_sel INTO TABLE lt_sel.
ls_sel-queryfield = if_fdt_admin_data_query=>gc_fn_name.
ls_sel-sign = 'I' .
ls_sel-option = 'EQ'.
ls_sel-low = iv_dt_table_name.
INSERT ls_sel INTO TABLE lt_sel.
go_factory->get_query( )->select_data(
EXPORTING
its_selection = lt_sel
IMPORTING
eta_data = lt_names ).
IF lines( lt_names ) NE 1.
print_error( |Decision table { iv_dt_table_name } not found!| ).
RETURN.
ENDIF.
DATA(ls_dt) = lt_names[ 1 ].
CREATE DATA lr_table TYPE TABLE OF (iv_hana_table_name).
ASSIGN lr_table->* TO <t_table>.
CREATE DATA lr_line TYPE (iv_hana_table_name).
ASSIGN lr_line->* TO FIELD-SYMBOL(<s_line>).
" read the data
TRY.
lo_decision_table ?= go_factory->get_expression( ls_dt-id ).
lo_decision_table->if_fdt_decision_table~get_table_data(
IMPORTING
ets_data = DATA(lt_msg_data)
).
lt_dt_data = lt_msg_data. " convert for using the key
DATA(lv_line) = 1.
DO. " read lines
CLEAR <s_line>.
LOOP AT lt_dt_data ASSIGNING FIELD-SYMBOL(<s_dt_data>) USING KEY row WHERE row_no EQ lv_line.
IF <s_dt_data>-r_value IS NOT INITIAL.
ASSIGN <s_dt_data>-r_value TO FIELD-SYMBOL(<f_field_dt>).
ASSERT sy-subrc EQ 0.
ELSEIF <s_dt_data>-ts_range IS NOT INITIAL.
IF lines( <s_dt_data>-ts_range ) EQ 1.
READ TABLE <s_dt_data>-ts_range ASSIGNING FIELD-SYMBOL(<s_range>) INDEX 1.
IF <s_range>-sign NE 'I' OR NOT ( <s_range>-option EQ 'EQ' OR <s_range>-option EQ 'GE' ).
print_error( |Ungültiger Wert in { iv_dt_table_name }| ).
RETURN.
ENDIF.
ASSIGN <s_range>-r_low_value TO <f_field_dt>.
ASSERT sy-subrc EQ 0.
ELSE.
print_error( |Ungültiger Wert in { iv_dt_table_name }| ).
RETURN.
ENDIF.
ELSE.
CONTINUE.
ENDIF.
DATA(lv_column) = <s_dt_data>-col_no + 1. " +1 because we have MANDT in the Hana Table
ASSIGN COMPONENT lv_column OF STRUCTURE <s_line> TO FIELD-SYMBOL(<f_field_table>).
IF sy-subrc EQ 0.
<f_field_table> = <f_field_dt>->*.
ENDIF.
ENDLOOP. " read columns
IF sy-subrc NE 0.
EXIT.
ENDIF.
APPEND <s_line>
TO <t_table>.
lv_line += 1.
ENDDO.
CATCH cx_fdt INTO DATA(lx_exception).
print_error( |DEcision table { iv_dt_table_name } could not be read: { lx_exception->get_text( ) }| ).
RETURN.
ENDTRY.
Import parameters:
iv_hana_table_name
the HANA table name which has the same structure (+ MANDT) as the decision table.iv_application_id
the id of the BRF+ application.iv_dt_table_name
the name of the decision table.
This code refers to a HANA table which structure is identical (+ MANDT) to the BRF+ decision table.
🧵 Buffering via Exit Class
One powerful way to encapsulate this logic is through a BRF+ Exit Class (implementing IF_FDT_DECISION_TABLE_EXIT
or similar). On the first call, extract the decision table and store it in a hashed internal table.
Example pattern:
1
2
3
4
5
6
7
8
9
10
11
CLASS zcl_my_brf_exit DEFINITION
PUBLIC
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_fdt_decision_table_exit.
PRIVATE SECTION.
CLASS-DATA: gt_cache TYPE HASHED TABLE OF your_structure
WITH UNIQUE KEY your_key_field.
ENDCLASS.
This way, you combine the flexibility of BRF+ with the raw speed of internal tables.
🧩 Alternative: Store BRF+ Data in a HANA Table
Another approach - especially useful for large and slowly changing decision tables - is to extract the data and store it in a custom SAP HANA table. You can:
- Load the data manually or via a job
- Use CDS views or native SQL to access it
- Benefit from HANA-level optimizations (indexes, joins, filters)
Just ensure the table is kept in sync with BRF+ changes.
⚖️ Pros and Cons
✅ Benefits
- 🔥 Massive performance boost (up to 80% faster in real scenarios)
- 🎯 Clean, predictable access logic
- 🧼 Eliminate duplicates (often found during migration)
- 🧠 Better control over data structure and access logic
❌ Drawbacks
- ⚠️ Tightly coupled to the structure of the decision table
- 🛠️ ABAP code must be updated when BRF+ changes
- 🧪 Requires careful mapping and maintenance
🧪 Real-World Example: Migration Insights
During a recent migration, I extracted over a dozen BRF+ tables and discovered:
- 10–20% duplicate entries, due to inconsistent key definitions
- Performance improved by 80%+ after switching to internal tables
- Rule logic became clearer once extracted and analyzed outside of BRF+
This also opened the door to build test suites around the logic, which is much harder to do in standard BRF+ UI.
🧠 Final Thoughts
BRF+ is great for managing business rules - but like any abstraction, it comes with trade-offs. When you find yourself hitting performance ceilings or debugging complex rule behavior, it might be time to step back and extract the rules into ABAP logic.
Using the BRF+ API for this gives you the best of both worlds: business-defined rules with developer-defined performance.
Just remember: with great power comes great responsibility. Keep your ABAP clean, document your mapping logic, and always validate the extracted data.