Reading BRF+ Decision Table Data via API in ABAP
Learn how to programmatically read BRF+ decision table data using the FDT API in ABAP. This comprehensive guide covers querying applications, accessing decision tables, and extracting table data into custom structures.
SAP BRF+ (Business Rule Framework plus) provides a powerful framework for managing business rules and decision logic. While the UI tools are great for manual maintenance, there are scenarios where you need to programmatically access decision table data—for example, to sync data to external systems, create backups, or migrate decision tables to database tables for performance optimization.
In this post, I’ll show you how to use the BRF+ FDT (Formula and Decision Table) API to read decision table data programmatically.
🎯 Use Cases for Reading Decision Tables Programmatically
Before diving into the code, here are some common scenarios where reading decision table data via API is useful:
- Data Migration: Export decision table content to database tables or external systems
- Backup & Version Control: Create automated backups of business rules
- Performance Optimization: Copy frequently accessed decision tables to optimized database tables
- Integration: Share business rules with external applications
- Auditing: Track and analyze decision table content changes
🏗️ Understanding the BRF+ API Structure
The BRF+ API is built around the Factory Design Pattern, accessed through the cl_fdt_factory class. The main components we’ll work with are:
- Factory (
if_fdt_factory): Entry point for all BRF+ operations - Query (
if_fdt_query): Used to search for BRF+ objects - Decision Table (
if_fdt_decision_table): Interface for accessing table data - Expression (
if_fdt_expression): Base interface for BRF+ expressions including decision tables
📋 Step 1: Initialize the Factory
Every interaction with the BRF+ API starts with getting an instance of the factory:
1
2
3
DATA: factory TYPE REF TO if_fdt_factory.
factory = cl_fdt_factory=>get_instance( ).
This singleton instance provides access to all BRF+ query and manipulation operations.
🔍 Step 2: Find the Application ID
BRF+ organizes objects into applications. Before accessing a decision table, you need to find the application ID by name:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
" Get application ID by name
factory->get_query( )->get_ids(
EXPORTING
iv_name = 'YOUR_APPLICATION_NAME' " e.g., 'Z_PRICING_RULES'
iv_object_type = if_fdt_constants=>gc_object_type_application
iv_deleted_option = cl_fdt_query=>if_fdt_query~gc_delopt_undeleted
IMPORTING
ets_object_id = DATA(application_ids)
).
IF lines( application_ids ) NE 1.
" Handle error: application not found or multiple results
WRITE: / 'Application not found or ambiguous!'.
RETURN.
ENDIF.
DATA(application_id) = application_ids[ 1 ].
The query parameters ensure that:
- Only undeleted applications are returned
- The exact application name is matched
- We get the unique object ID for further operations
🎲 Step 3: Query for the Decision Table
Now that we have the application ID, we can search for a specific decision table within that application:
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
DATA: selection TYPE if_fdt_query=>s_selection,
selections TYPE if_fdt_query=>ts_selection,
decision_tables TYPE if_fdt_query=>ts_name.
" Build query for application
selection-queryfield = if_fdt_admin_data_query=>gc_fn_application_id.
selection-sign = 'I'.
selection-option = 'EQ'.
selection-low = application_id.
INSERT selection INTO TABLE selections.
" Filter by object type (Expression = Decision Table)
selection-queryfield = if_fdt_admin_data_query=>gc_fn_object_type.
selection-sign = 'I'.
selection-option = 'EQ'.
selection-low = if_fdt_constants=>gc_object_type_expression.
INSERT selection INTO TABLE selections.
" Filter by decision table name
selection-queryfield = if_fdt_admin_data_query=>gc_fn_name.
selection-sign = 'I'.
selection-option = 'EQ'.
selection-low = 'YOUR_DECISION_TABLE_NAME'. " e.g., 'DT_PRICE_CALCULATION'
INSERT selection INTO TABLE selections.
" Execute query
factory->get_query( )->select_data(
EXPORTING
its_selection = selections
IMPORTING
eta_data = decision_tables
).
IF lines( decision_tables ) NE 1.
" Handle error: decision table not found
WRITE: / 'Decision table not found!'.
RETURN.
ENDIF.
DATA(decision_table_info) = decision_tables[ 1 ].
The selection criteria work like database SELECT statements, using sign/option/low/high patterns familiar from ABAP ranges.
📊 Step 4: Read the Decision Table Data
With the decision table ID in hand, we can now access its actual content:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
DATA: decision_table TYPE REF TO cl_fdt_decision_table,
table_data TYPE TABLE OF if_fdt_decision_table=>s_table_data
WITH NON-UNIQUE SORTED KEY row COMPONENTS row_no col_no.
TRY.
" Get decision table instance
decision_table ?= factory->get_expression( decision_table_info-id ).
" Retrieve table data
decision_table->if_fdt_decision_table~get_table_data(
IMPORTING
ets_data = DATA(raw_table_data)
).
" Store in sorted table for easier processing
LOOP AT raw_table_data ASSIGNING FIELD-SYMBOL(<raw_data>).
INSERT <raw_data> INTO TABLE table_data.
ENDLOOP.
CATCH cx_fdt INTO DATA(exception).
" Handle error
WRITE: / 'Error reading decision table:', exception->get_text( ).
RETURN.
ENDTRY.
The get_table_data method returns the decision table content as a flat structure where each cell is represented individually with its row and column position.
🔄 Step 5: Transform Data into Custom Structure
The raw table data from BRF+ needs to be transformed into a usable format. Here’s how to map it to a target database table or structure:
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
DATA: target_table TYPE REF TO data,
target_line TYPE REF TO data.
FIELD-SYMBOLS: <target_table> TYPE STANDARD TABLE,
<target_line> TYPE any.
" Create dynamic table and line structures
CREATE DATA target_table TYPE TABLE OF ('YOUR_TARGET_TABLE').
ASSIGN target_table->* TO <target_table>.
CREATE DATA target_line TYPE ('YOUR_TARGET_TABLE').
ASSIGN target_line->* TO <target_line>.
DATA(current_row) = 1.
FIELD-SYMBOLS: <cell_value> TYPE REF TO data,
<target_field> TYPE any.
DO.
CLEAR <target_line>.
" Process all columns in current row
LOOP AT table_data ASSIGNING FIELD-SYMBOL(<cell_data>)
USING KEY row WHERE row_no EQ current_row.
" Extract the actual value from the cell
IF <cell_data>-r_value IS NOT INITIAL.
" Direct value reference
ASSIGN <cell_data>-r_value TO <cell_value>.
ELSEIF <cell_data>-ts_range IS NOT INITIAL.
" Handle range values (e.g., single equality conditions)
IF lines( <cell_data>-ts_range ) EQ 1.
READ TABLE <cell_data>-ts_range ASSIGNING FIELD-SYMBOL(<range>) INDEX 1.
" Validate that it's a simple equality or GE condition
IF <range>-sign NE 'I' OR <range>-option NE 'EQ'.
WRITE: / 'Invalid value type in decision table!'.
RETURN.
ENDIF.
ASSIGN <range>-r_low_value TO <cell_value>.
ELSE.
WRITE: / 'Complex range values not supported!'.
RETURN.
ENDIF.
ELSE.
" Empty cell, skip
CONTINUE.
ENDIF.
" Map to target structure field
" Note: +1 offset if target table has MANDT field
DATA(target_column) = <cell_data>-col_no + 1.
ASSIGN COMPONENT target_column OF STRUCTURE <target_line> TO <target_field>.
IF sy-subrc EQ 0.
<target_field> = <cell_value>->*.
ENDIF.
ENDLOOP.
" Check if we've processed all rows
IF sy-subrc NE 0.
EXIT.
ENDIF.
" Add line to result table
APPEND <target_line> TO <target_table>.
current_row += 1.
ENDDO.
This code handles the most common data types in decision tables. The key aspects are:
- Column Mapping: BRF+ columns are mapped to structure components
- Value Extraction: Handles both direct values and range conditions
- MANDT Handling: Accounts for client field if present in target structure
💾 Step 6: Persist Data to Database
Once the data is transformed, you can persist it to a database table:
1
2
3
4
5
6
7
8
9
10
11
12
13
" Clear existing data
DELETE FROM ('YOUR_TARGET_TABLE').
" Insert transformed data
LOOP AT <target_table> ASSIGNING <target_line>.
INSERT ('YOUR_TARGET_TABLE') FROM <target_line>.
ENDLOOP.
" Verify results
SELECT COUNT( * ) FROM ('YOUR_TARGET_TABLE') INTO DATA(record_count).
WRITE: / 'Processed', lines( <target_table> ), 'rows.',
/ 'Inserted', record_count, 'records into database.'.
For better performance with large datasets, consider using MODIFY with the entire table instead of individual inserts.
🎯 Complete Example: Reading and Syncing Decision Tables
Here’s a complete example that ties everything together:
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
DATA: factory TYPE REF TO if_fdt_factory,
application_id TYPE fdt_id,
decision_table TYPE REF TO cl_fdt_decision_table.
" Initialize factory
factory = cl_fdt_factory=>get_instance( ).
" Get application ID
factory->get_query( )->get_ids(
EXPORTING
iv_name = 'Z_PRICING_APP'
iv_object_type = if_fdt_constants=>gc_object_type_application
iv_deleted_option = cl_fdt_query=>if_fdt_query~gc_delopt_undeleted
IMPORTING
ets_object_id = DATA(application_ids)
).
IF lines( application_ids ) NE 1.
WRITE: / 'Application not found!'.
RETURN.
ENDIF.
application_id = application_ids[ 1 ].
" Query decision table
DATA: selection TYPE if_fdt_query=>s_selection,
selections TYPE if_fdt_query=>ts_selection.
selection-queryfield = if_fdt_admin_data_query=>gc_fn_application_id.
selection-sign = 'I'.
selection-option = 'EQ'.
selection-low = application_id.
INSERT selection INTO TABLE selections.
selection-queryfield = if_fdt_admin_data_query=>gc_fn_object_type.
selection-sign = 'I'.
selection-option = 'EQ'.
selection-low = if_fdt_constants=>gc_object_type_expression.
INSERT selection INTO TABLE selections.
selection-queryfield = if_fdt_admin_data_query=>gc_fn_name.
selection-sign = 'I'.
selection-option = 'EQ'.
selection-low = 'DT_PRICING_RULES'.
INSERT selection INTO TABLE selections.
factory->get_query( )->select_data(
EXPORTING
its_selection = selections
IMPORTING
eta_data = DATA(decision_tables)
).
IF lines( decision_tables ) NE 1.
WRITE: / 'Decision table not found!'.
RETURN.
ENDIF.
DATA(dt_info) = decision_tables[ 1 ].
" Read table data
TRY.
decision_table ?= factory->get_expression( dt_info-id ).
decision_table->if_fdt_decision_table~get_table_data(
IMPORTING
ets_data = DATA(raw_data)
).
DATA: table_data TYPE TABLE OF if_fdt_decision_table=>s_table_data
WITH NON-UNIQUE SORTED KEY row COMPONENTS row_no col_no.
LOOP AT raw_data ASSIGNING FIELD-SYMBOL(<raw>).
INSERT <raw> INTO TABLE table_data.
ENDLOOP.
" Transform and process data
DATA: target_table TYPE REF TO data,
target_line TYPE REF TO data.
FIELD-SYMBOLS: <t_table> TYPE STANDARD TABLE,
<s_line> TYPE any.
CREATE DATA target_table TYPE TABLE OF ('Z_PRICING_TABLE').
ASSIGN target_table->* TO <t_table>.
CREATE DATA target_line TYPE ('Z_PRICING_TABLE').
ASSIGN target_line->* TO <s_line>.
DATA(row_number) = 1.
FIELD-SYMBOLS: <cell_ref> TYPE REF TO data,
<table_field> TYPE any.
DO.
CLEAR <s_line>.
LOOP AT table_data ASSIGNING FIELD-SYMBOL(<cell>)
USING KEY row WHERE row_no EQ row_number.
IF <cell>-r_value IS NOT INITIAL.
ASSIGN <cell>-r_value TO <cell_ref>.
ELSEIF <cell>-ts_range IS NOT INITIAL.
IF lines( <cell>-ts_range ) EQ 1.
READ TABLE <cell>-ts_range ASSIGNING FIELD-SYMBOL(<range>) INDEX 1.
IF <range>-sign NE 'I' OR <range>-option NE 'EQ'.
WRITE: / 'Invalid range condition!'.
RETURN.
ENDIF.
ASSIGN <range>-r_low_value TO <cell_ref>.
ELSE.
WRITE: / 'Complex ranges not supported!'.
RETURN.
ENDIF.
ELSE.
CONTINUE.
ENDIF.
DATA(column_index) = <cell>-col_no + 1.
ASSIGN COMPONENT column_index OF STRUCTURE <s_line> TO <table_field>.
IF sy-subrc EQ 0.
<table_field> = <cell_ref>->*.
ENDIF.
ENDLOOP.
IF sy-subrc NE 0.
EXIT.
ENDIF.
APPEND <s_line> TO <t_table>.
row_number += 1.
ENDDO.
" Persist to database
DELETE FROM z_pricing_table.
LOOP AT <t_table> ASSIGNING <s_line>.
INSERT z_pricing_table FROM <s_line>.
ENDLOOP.
SELECT COUNT( * ) FROM z_pricing_table INTO DATA(db_count).
WRITE: / 'Successfully synced', lines( <t_table> ), 'rows to Z_PRICING_TABLE',
/ 'Database contains', db_count, 'records'.
CATCH cx_fdt INTO DATA(exception).
WRITE: / 'Error:', exception->get_text( ).
RETURN.
ENDTRY.
⚠️ Important Considerations
Performance
- Caching: Consider caching decision table data if accessed frequently
- Batch Processing: For multiple tables, process them in batches
- Database Operations: Use
MODIFYinstead of individualINSERTfor better performance
Data Types
The code above handles the most common scenarios:
- Direct values (
r_value) - Simple range conditions (EQ)
For more complex scenarios, you may need to extend the logic to handle:
- Multiple range conditions
- Complex expressions
- Nested decision tables
Authorization
Ensure that the user executing this code has appropriate authorizations:
- S_DEVELOP: For accessing ABAP development objects
- S_FDT_APP: For BRF+ application access
- S_TABU_DIS: For database table access (if using dynamic table operations)
Error Handling
Always implement proper error handling:
- Check for empty result sets
- Handle BRF+ exceptions (
cx_fdt) - Validate data integrity before persisting
🔗 Related Topics
🧠 Final Thoughts
Using the BRF+ FDT API provides powerful programmatic access to decision table data. The key steps are:
- Initialize the factory with
cl_fdt_factory=>get_instance( ) - Query for the application and decision table using selections
- Retrieve table data with
get_table_data( ) - Transform the row/column structure into your target format
- Persist or process the data as needed
This approach enables automation scenarios like synchronization, backup, migration, and integration that would be tedious or impossible through the UI alone.
The BRF+ API is extensive and offers many more capabilities beyond reading decision tables—including creating, modifying, and executing business rules programmatically. This foundation will help you explore those advanced scenarios as your requirements grow.
