Hi all,
In SAP RAP development, it is common to work with fields that should not be physically stored in database tables but must still appear in the service output. Such fields are often required for real-time calculations, business validations, or UI-level display logic.
This blog demonstrates two powerful techniques to handle these derived fields:
Table Functions (AMDP) for database-side calculations
Virtual Elements for ABAP-based runtime logic
To make the concept relatable, we use a simple Student Marks Management scenario where:
Total Marks is calculated using a Table Function (AMDP)
Pass/Fail Status is determined using Virtual Elements
This article will walk you through all necessary components—including CDS view entities, AMDP class implementation, RAP behavior definition, and the virtual element class.
I am implementing this requirement using two approaches:
Table Function for calculating Total Marks
Virtual Field for determining Status
Both methods will be covered in this blog.
Step 1: Create a new database table to store student information.
Code:
@EndUserText.label : ‘Student Information’
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zstudent_info {
key client : abap.clnt not null;
key id : zstudentid_de not null;
name : abap.char(50);
class : abap.int4;
telugu : abap.int4;
hindi : abap.int4;
english : abap.int4;
maths : abap.int4;
science : abap.int4;
social : abap.int4;
}
Step 2: Create a Table Function
Real-time Use Case for Table Function
In a Student Marks Management System, Total Marks are calculated dynamically across subjects without storing the result physically. A Table Function, implemented with AMDP and SQLScript, performs this calculation efficiently in the database, improving performance and keeping the data model clean by avoiding redundant storage.
For now, I am adding only the Total Marks field to implement the AMDP approach.
@EndUserText.label: ‘Student Info with Total Marks’
define table function ZTABLE_FUNCTION_STUDENT
with parameters
@Environment.systemField: #CLIENT
p_client : abap.clnt
returns {
client : abap.clnt;
id : zstudentid_de;
name : abap.char(50);
class : abap.int4;
telugu : abap.int4;
hindi : abap.int4;
english : abap.int4;
maths : abap.int4;
science : abap.int4;
social : abap.int4;
totalmarks : abap.int4; // 🆕 Calculated field
}
implemented by method ZCL_TABLE_FUNC_STUDENT=>get_total_data;
Step 3: Now create a class ZCL_TABLE_FUNC_STATUS to implement the logic for calculating Total Marks.
Example Code:
CLASS zcl_table_func_student DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_amdp_marker_hdb.
” Table function method declaration
CLASS-METHODS get_total_data
FOR TABLE FUNCTION ztable_function_student.
ENDCLASS.
CLASS zcl_table_func_student IMPLEMENTATION.
METHOD get_total_data
BY DATABASE FUNCTION
FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING zstudent_info.
RETURN
SELECT
client,
id,
name,
class,
telugu,
hindi,
english,
maths,
science,
social,
telugu + hindi + english + maths + science + social AS totalmarks
FROM zstudent_info
WHERE client = :p_client;
ENDMETHOD.
ENDCLASS.
Step 4: Execute and test the code by pressing F8 On Table Function.
Output:
Now I am going with Virtual Elements.
Virtual elements are defined using annotations such as:
@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy: ‘ABAP:className’
The logic for the virtual element is implemented in the specified ABAP class.
Now I am creating the Root View
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Student Info with Status’
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType: {
serviceQuality: #X,
sizeCategory: #S,
dataClass: #MIXED
}
define root view entity zstudent_info_view
as select from ZTABLE_FUNCTION_STUDENT( p_client : $session.client )
{
key id as Id,
name as Name,
class as Class,
telugu as Telugu,
hindi as Hindi,
english as English,
maths as Maths,
science as Science,
social as Social,
totalmarks as TotalMarks,
@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy: ‘ABAP:ZCL_VIRTUAL_ELEMENT_CALCC’
@EndUserText.label: ‘Pass/Fail Status’
cast( ” as abap.char(10) ) as Status
}
Root View Code :
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Root View’
@Metadata.ignorePropagatedAnnotations: true
define root view entity zstudent_info_view_root as select from zstudent_info_view
{
key Id as Id,
Name as Name,
Class as Class,
Telugu as Telugu,
Hindi as Hindi,
English as English,
Maths as Maths,
Science as Science,
Social as Social,
TotalMarks as TotalMarks,
@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy: ‘ABAP:ZCL_VIRTUAL_ELEMENT_CALCC’
@EndUserText.label: ‘Pass/Fail Status’
cast( ” as abap.char(10) ) as Status
}
Projection View Code :
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Projection View’
@Metadata.ignorePropagatedAnnotations: true
@Metadata.allowExtensions: true
define root view entity ZSTUDENT_INFO_VIEW_ROOT_Pro as projection on zstudent_info_view_root
{
//@Consumption.valueHelpDefinition: [
// { entity: {
// name: ‘ZSTUDENT_INFO_VIEW_ROOT’,
// element: ‘ID’
// } }
// ]
key Id,
// @Consumption.valueHelpDefinition: [
// { entity: {
// name: ‘ZSTUDENT_INFO_VIEW_ROOT’,
// element: ‘Name’
// } }
// ]
Name,
Class,
Telugu,
Hindi,
English,
Maths,
Science,
Social,
TotalMarks, // Virtual field
@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy: ‘ABAP:ZCL_VIRTUAL_ELEMENT_CALCC’
@EndUserText.label: ‘Pass/Fail Status’
Status // Virtual field (Pass/Fail)
}
Definition of behavior for the root view
managed implementation in class zbp_student_info_view_root unique;
strict ( 2 );
define behavior for zstudent_info_view_root alias student
persistent table zstudent_informa
lock master
authorization master ( instance )
// etag master <field_name> ” Uncomment if using ETag for concurrency control
{
create;
update;
delete;
field ( readonly ) TotalMarks, Status;
mapping for zstudent_informa
{
Id = id;
Name = name;
Class = class;
Telugu = telugu;
Hindi = hindi;
English = english;
Maths = maths;
Science = science;
Social = social;
}
}
Create classes for the root view and projection view.
Create a class and logic for the virtual element.
CLASS zcl_virtual_element_calcc DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_sadl_exit_calc_element_read.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_virtual_element_calcc IMPLEMENTATION.
METHOD if_sadl_exit_calc_element_read~calculate.
CHECK NOT it_original_data IS INITIAL.
DATA lt_calculated_data TYPE STANDARD TABLE OF zstudent_info_view WITH DEFAULT KEY.
MOVE-CORRESPONDING it_original_data TO lt_calculated_data.
LOOP AT lt_calculated_data ASSIGNING FIELD-SYMBOL(<student>).
” Calculate TotalMarks by summing all subject marks
* <student>-totalmarks = <student>-telugu
* + <student>-hindi
* + <student>-english
* + <student>-maths
* + <student>-science
* + <student>-social.
” Check if any subject marks are less than 25
* IF <student>-telugu < 25 OR
* <student>-hindi < 25 OR
* <student>-english < 25 OR
* <student>-maths < 25 OR
* <student>-science < 25 OR
* <student>-social < 25.
* <student>-status = ‘Failed’.
* ELSE.
* <student>-status = ‘Passed’.
** ENDIF.
* ENDIF.
IF <student>-totalmarks > 250 .
<student>-status = ‘Passed’.
ELSE.
<student>-status = ‘Failed’.
ENDIF.
ENDLOOP.
MOVE-CORRESPONDING lt_calculated_data TO ct_calculated_data.
ENDMETHOD.
METHOD if_sadl_exit_calc_element_read~get_calculation_info.
” Tell framework which fields you need from original data
et_requested_orig_elements = VALUE #( BASE et_requested_orig_elements
( CONV #( ‘TELUGU’ ) )
( CONV #( ‘HINDI’ ) )
( CONV #( ‘ENGLISH’ ) )
( CONV #( ‘MATHS’ ) )
( CONV #( ‘SCIENCE’ ) )
( CONV #( ‘SOCIAL’ ) )
( CONV #( ‘TOTALMARKS’ ) )
).
ENDMETHOD.
ENDCLASS.
Now, create a service definition for the projection view and bind the service.
Now, we are able to see the virtual element output field in the web application.
Conclusion
This blog demonstrated how to enrich a RAP data model using both Table Functions and Virtual Elements, allowing you to compute fields dynamically without storing redundant values in the database.
Table Functions (AMDP) perform high-performance calculations on the HANA database, such as Total Marks.
Virtual Elements allow ABAP-based runtime logic for UI-visible derived fields like Pass/Fail Status.
By combining these methods, you achieve a clean, efficient, and scalable RAP model that supports real-time business calculations while keeping the underlying data model simple and maintainable.
This approach can be extended for validations, analytical measures, financial calculations, or any scenario where derived fields should remain virtual yet visible in the application layer.
Hi all,In SAP RAP development, it is common to work with fields that should not be physically stored in database tables but must still appear in the service output. Such fields are often required for real-time calculations, business validations, or UI-level display logic.This blog demonstrates two powerful techniques to handle these derived fields:Table Functions (AMDP) for database-side calculationsVirtual Elements for ABAP-based runtime logicTo make the concept relatable, we use a simple Student Marks Management scenario where:Total Marks is calculated using a Table Function (AMDP)Pass/Fail Status is determined using Virtual ElementsThis article will walk you through all necessary components—including CDS view entities, AMDP class implementation, RAP behavior definition, and the virtual element class.I am implementing this requirement using two approaches:Table Function for calculating Total MarksVirtual Field for determining StatusBoth methods will be covered in this blog.Step 1: Create a new database table to store student information.Code:@EndUserText.label : ‘Student Information’
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zstudent_info {
key client : abap.clnt not null;
key id : zstudentid_de not null;
name : abap.char(50);
class : abap.int4;
telugu : abap.int4;
hindi : abap.int4;
english : abap.int4;
maths : abap.int4;
science : abap.int4;
social : abap.int4;
}Step 2: Create a Table FunctionReal-time Use Case for Table FunctionIn a Student Marks Management System, Total Marks are calculated dynamically across subjects without storing the result physically. A Table Function, implemented with AMDP and SQLScript, performs this calculation efficiently in the database, improving performance and keeping the data model clean by avoiding redundant storage.For now, I am adding only the Total Marks field to implement the AMDP approach.@EndUserText.label: ‘Student Info with Total Marks’
define table function ZTABLE_FUNCTION_STUDENT
with parameters
@Environment.systemField: #CLIENT
p_client : abap.clnt
returns {
client : abap.clnt;
id : zstudentid_de;
name : abap.char(50);
class : abap.int4;
telugu : abap.int4;
hindi : abap.int4;
english : abap.int4;
maths : abap.int4;
science : abap.int4;
social : abap.int4;
totalmarks : abap.int4; // 🆕 Calculated field
}
implemented by method ZCL_TABLE_FUNC_STUDENT=>get_total_data;Step 3: Now create a class ZCL_TABLE_FUNC_STATUS to implement the logic for calculating Total Marks.Example Code:CLASS zcl_table_func_student DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_amdp_marker_hdb.
” Table function method declaration
CLASS-METHODS get_total_data
FOR TABLE FUNCTION ztable_function_student.
ENDCLASS.
CLASS zcl_table_func_student IMPLEMENTATION.
METHOD get_total_data
BY DATABASE FUNCTION
FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING zstudent_info.
RETURN
SELECT
client,
id,
name,
class,
telugu,
hindi,
english,
maths,
science,
social,
telugu + hindi + english + maths + science + social AS totalmarks
FROM zstudent_info
WHERE client = :p_client;
ENDMETHOD.
ENDCLASS.Step 4: Execute and test the code by pressing F8 On Table Function.Output: Now I am going with Virtual Elements.Virtual elements are defined using annotations such as:@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy: ‘ABAP:className’The logic for the virtual element is implemented in the specified ABAP class.Now I am creating the Root View@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Student Info with Status’
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType: {
serviceQuality: #X,
sizeCategory: #S,
dataClass: #MIXED
}
define root view entity zstudent_info_view
as select from ZTABLE_FUNCTION_STUDENT( p_client : $session.client )
{
key id as Id,
name as Name,
class as Class,
telugu as Telugu,
hindi as Hindi,
english as English,
maths as Maths,
science as Science,
social as Social,
totalmarks as TotalMarks,
@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy: ‘ABAP:ZCL_VIRTUAL_ELEMENT_CALCC’
@EndUserText.label: ‘Pass/Fail Status’
cast( ” as abap.char(10) ) as Status
}Root View Code :@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Root View’
@Metadata.ignorePropagatedAnnotations: true
define root view entity zstudent_info_view_root as select from zstudent_info_view
{
key Id as Id,
Name as Name,
Class as Class,
Telugu as Telugu,
Hindi as Hindi,
English as English,
Maths as Maths,
Science as Science,
Social as Social,
TotalMarks as TotalMarks,
@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy: ‘ABAP:ZCL_VIRTUAL_ELEMENT_CALCC’
@EndUserText.label: ‘Pass/Fail Status’
cast( ” as abap.char(10) ) as Status
}Projection View Code :@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Projection View’
@Metadata.ignorePropagatedAnnotations: true
@Metadata.allowExtensions: true
define root view entity ZSTUDENT_INFO_VIEW_ROOT_Pro as projection on zstudent_info_view_root
{
//@Consumption.valueHelpDefinition: [
// { entity: {
// name: ‘ZSTUDENT_INFO_VIEW_ROOT’,
// element: ‘ID’
// } }
// ]
key Id,
// @Consumption.valueHelpDefinition: [
// { entity: {
// name: ‘ZSTUDENT_INFO_VIEW_ROOT’,
// element: ‘Name’
// } }
// ]
Name,
Class,
Telugu,
Hindi,
English,
Maths,
Science,
Social,
TotalMarks, // Virtual field
@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy: ‘ABAP:ZCL_VIRTUAL_ELEMENT_CALCC’
@EndUserText.label: ‘Pass/Fail Status’
Status // Virtual field (Pass/Fail)
}Definition of behavior for the root viewmanaged implementation in class zbp_student_info_view_root unique;
strict ( 2 );
define behavior for zstudent_info_view_root alias student
persistent table zstudent_informa
lock master
authorization master ( instance )
// etag master <field_name> ” Uncomment if using ETag for concurrency control
{
create;
update;
delete;
field ( readonly ) TotalMarks, Status;
mapping for zstudent_informa
{
Id = id;
Name = name;
Class = class;
Telugu = telugu;
Hindi = hindi;
English = english;
Maths = maths;
Science = science;
Social = social;
}
}Create classes for the root view and projection view.Create a class and logic for the virtual element.CLASS zcl_virtual_element_calcc DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_sadl_exit_calc_element_read.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_virtual_element_calcc IMPLEMENTATION.
METHOD if_sadl_exit_calc_element_read~calculate.
CHECK NOT it_original_data IS INITIAL.
DATA lt_calculated_data TYPE STANDARD TABLE OF zstudent_info_view WITH DEFAULT KEY.
MOVE-CORRESPONDING it_original_data TO lt_calculated_data.
LOOP AT lt_calculated_data ASSIGNING FIELD-SYMBOL(<student>).
” Calculate TotalMarks by summing all subject marks
* <student>-totalmarks = <student>-telugu
* + <student>-hindi
* + <student>-english
* + <student>-maths
* + <student>-science
* + <student>-social.
” Check if any subject marks are less than 25
* IF <student>-telugu < 25 OR
* <student>-hindi < 25 OR
* <student>-english < 25 OR
* <student>-maths < 25 OR
* <student>-science < 25 OR
* <student>-social < 25.
* <student>-status = ‘Failed’.
* ELSE.
* <student>-status = ‘Passed’.
** ENDIF.
* ENDIF.
IF <student>-totalmarks > 250 .
<student>-status = ‘Passed’.
ELSE.
<student>-status = ‘Failed’.
ENDIF.
ENDLOOP.
MOVE-CORRESPONDING lt_calculated_data TO ct_calculated_data.
ENDMETHOD.
METHOD if_sadl_exit_calc_element_read~get_calculation_info.
” Tell framework which fields you need from original data
et_requested_orig_elements = VALUE #( BASE et_requested_orig_elements
( CONV #( ‘TELUGU’ ) )
( CONV #( ‘HINDI’ ) )
( CONV #( ‘ENGLISH’ ) )
( CONV #( ‘MATHS’ ) )
( CONV #( ‘SCIENCE’ ) )
( CONV #( ‘SOCIAL’ ) )
( CONV #( ‘TOTALMARKS’ ) )
).
ENDMETHOD.
ENDCLASS.Now, create a service definition for the projection view and bind the service. Now, we are able to see the virtual element output field in the web application.ConclusionThis blog demonstrated how to enrich a RAP data model using both Table Functions and Virtual Elements, allowing you to compute fields dynamically without storing redundant values in the database.Table Functions (AMDP) perform high-performance calculations on the HANA database, such as Total Marks.Virtual Elements allow ABAP-based runtime logic for UI-visible derived fields like Pass/Fail Status.By combining these methods, you achieve a clean, efficient, and scalable RAP model that supports real-time business calculations while keeping the underlying data model simple and maintainable.This approach can be extended for validations, analytical measures, financial calculations, or any scenario where derived fields should remain virtual yet visible in the application layer. Read More Technology Blog Posts by Members articles
#SAP
#SAPTechnologyblog