S4D400 - Basic ABAP Programming
S4D400 - Basic ABAP Programming
.
.
PARTICIPANT HANDBOOK
INSTRUCTOR-LED TRAINING
.
Course Version: 24
Course Duration:
SAP Copyrights, Trademarks and
Disclaimers
No part of this publication may be reproduced or transmitted in any form or for any purpose without the
express permission of SAP SE or an SAP affiliate company.
SAP and other SAP products and services mentioned herein as well as their respective logos are
trademarks or registered trademarks of SAP SE (or an SAP affiliate company) in Germany and other
countries. Please see https://www.sap.com/corporate/en/legal/copyright.html for additional
trademark information and notices.
Some software products marketed by SAP SE and its distributors contain proprietary software
components of other software vendors.
National product specifications may vary.
These materials may have been machine translated and may contain grammatical errors or
inaccuracies.
These materials are provided by SAP SE or an SAP affiliate company for informational purposes only,
without representation or warranty of any kind, and SAP SE or its affiliated companies shall not be liable
for errors or omissions with respect to the materials. The only warranties for SAP SE or SAP affiliate
company products and services are those that are set forth in the express warranty statements
accompanying such products and services, if any. Nothing herein should be construed as constituting an
additional warranty.
In particular, SAP SE or its affiliated companies have no obligation to pursue any course of business
outlined in this document or any related presentation, or to develop or release any functionality
mentioned therein. This document, or any related presentation, and SAP SE’s or its affiliated companies’
strategy and possible future developments, products, and/or platform directions and functionality are
all subject to change and may be changed by SAP SE or its affiliated companies at any time for any
reason without notice. The information in this document is not a commitment, promise, or legal
obligation to deliver any material, code, or functionality. All forward-looking statements are subject to
various risks and uncertainties that could cause actual results to differ materially from expectations.
Readers are cautioned not to place undue reliance on these forward-looking statements, which speak
only as of their dates, and they should not be relied upon in making purchasing decisions.
Demonstration
Procedure
Warning or Caution
Hint
Facilitated Discussion
TARGET AUDIENCE
This course is intended for the following audiences:
● Development Consultant
● Developer
Lesson 1
Preparing the Development Environment 3
Lesson 2
Taking a First Look at ABAP 8
Exercise 1: Create an ABAP Cloud Project and Investigate ABAP Coding 11
Lesson 3
Software Structure And Logistics 17
Lesson 4
Developing your First ABAP Program 20
Exercise 2: Create a Package and a Hello World Application 25
UNIT OBJECTIVES
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Create an ABAP Cloud project in ADT
To develop ABAP applications for SAP BTP, you need the Eclipse development environment.
You can download it free of charge from eclipse.org. The default Eclipse installation does not
contain the ABAP Development Tools (ADT) - you must download these separately. To do so,
open Eclipse and choose Help -> Install New Software. A dialog box appears, in which you
enter the URL, tools.hana.ondemand.com/latest. This downloads the ABAP tools and installs
them in Eclipse.
You should periodically check for updates to both Eclipse and the ABAP tools. You do this in
Eclipse by choosing Help -> Check for Updates.
To develop ABAP applications for SAP BTP, you need to first install and prepare the Eclipse
development environment. Watch this video to know how.
When you have installed Eclipse and ADT, you need to switch to the ABAP perspective. A
perspective in Eclipse is a window in which you can perform a certain task. The default
perspective in a fresh Eclipse installation is the Java development perspective. To develop
ABAP applications, choose Window → Perspective → Open Perspective → Other. A dialog box
appears with a list of all of the available perspectives. Double-click ABAP.
A perspective is made up of a set of views, which appear as tabs in the Eclipse window. The
most important views are the source code editor and the project explorer, which you use to
navigate between different development objects. There is a wide range of other views that you
will need, such as the Problems view to display error messages, the documentation view, and
views for searching for objects or displaying where in the system a particular object is used.
Eclipse as a development environment is not embedded in the ABAP system. Instead, you
have to connect to each ABAP system in which you want to work, and each connection is
represented in Eclipse by a project. There are two kinds of project in ADT - ABAP Projects,
which you use to connect to an on-premise ABAP system, and ABAP Cloud Projects, which
you use to connect either to SAP BTP ABAP or to SAP S/4HANA Cloud. Here we are only
going to discuss how to access a cloud system.
The SAP Business Technology Platform is SAP's platform as a service (PAAS). To access it,
you need to create a global account. There are various subscription models available,
depending on whether you need to run large-scale productive environments or just a single-
user environment for your own continuing professional development.
Within a global account, there are one or more subaccounts. Each subaccount can be
configured differently, so that a single enterprise can run several different platforms but
manage their subscription using just the single global account. Inside the subaccount, you
deploy a runtime such as Cloud Foundry or Kyma. Once you have done this, you can deploy
an ABAP instance.
In this course we are using an ABAP instance deployed on the SAP Business Technology
Platform. However, the material is also relevant for other ABAP deployments, such as an on-
premise SAP S/4HANA system or an SAP S/4HANA Cloud system.
To log onto your ABAP instance, you will need a service key. In this course, we assume that
you have already created a suitable account on SAP BTP and that you have deployed an
ABAP instance and created the service key already. For further information, refer to https://
account.hana.ondemand.com.
To retrieve the service key, log onto your global account and navigate to the corresponding
subaccount in which the ABAP service is running. Choose Instances and Subscriptions on the
left-hand side of the screen, then scroll to Instances. You should see the ABAP instance, and
in the Credentials column, a link to the service key. Select the link to access the dialog box
shown in the figure, Creating an ABAP Cloud Project. To copy the contents of the key to the
clipboard, choose Copy JSON.
In order to develop, you must log onto your ABAP account. To do this, choose File → New →
ABAP Cloud Project. A dialog box appears, in which you should select the option, Use a
Service Key. Choose Next, and then paste the service key that you copied from your BTP
account into the editor. Choose Next again, and then select Open Logon Page in Browser. This
connects to the BTP and authenticates you. Close the browser window and return to Eclipse.
Set the language that you want to use, and choose Finish.
LESSON SUMMARY
You should now be able to:
● Create an ABAP Cloud project in ADT
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Work with a development object in ADT
To work with ABAP development objects, you usually display the contents of a package in the
project explorer. Packages are containers for development objects that logically belong
together.
Here are the steps to add a package to your favorites.
To add a package to your favorites, right-click the Favorite Packages node in the project
explorer and choose Add Package. A dialog box appears, in which you can enter a filter term.
The system then displays only the packages that contain this term. Double-click the package
that you want to add to your favorites.
Animation
For more information on this topic please view the animation in the lesson Taking
a First Look at ABAP in your online course.
Animation
For more information on this topic please view the animation in the lesson Taking
a First Look at ABAP in your online course.
The first is to locate the object name in the project explorer and double-click it. The other is to
use the keyboard shortcut, Ctrl + Shift + A. This opens a dialog box in which you can enter
part of the name of an object.
When you are working with ABAP code, there are certain function keys in ADT that will help
you.
Animation
For more information on this topic please view the animation in the lesson Taking
a First Look at ABAP in your online course.
F1
The F1 key displays the ABAP language help for the current statement. A statement is the
name for a command in ABAP.
F2
The F2 key displays information about the element on which the cursor is placed.
F3
The F3 key navigates to the definition of the object on which the cursor is placed. You can
return from there to the original object using the key combination, ALT + LEFT ARROW,
on your keyboard.
You must already have an SAP BTP account with an ABAP service and service key. You must
also have installed Eclipse and the SAP ABAP Development Tools.
1. Log on to your subaccount in SAP BTP and copy the ABAP service key into your clipboard.
Note:
You are not supposed to actually read the code you are going to see in this
exercise. Concentrate on the navigation and display functions in the development
environment.
Note:
If you cannot find class /DMO/CL_FLIGHT_LEGACY or any other development
object starting with /DMO/, you have to import the ABAP Flight
Reference Scenario for the ABAP RESTful Application
Programming Model into your system. To do so, please follow the
instructions under the following link:https://github.com/SAP-samples/abap-
platform-refscen-flight
2. Open the Properties tab to display the administrative data of development object /DMO/
CL_FLIGHT_LEGACY.
3. Locate ABAP class /DMO/CL_FLIGHT_LEGACY in the Project Explorer view on the left.
4. Find the string get_instance( )->get in the source code of ABAP class /DMO/
CL_FLIGHT_LEGACY.
Note:
There has to be exactly one blank between the brackets.
6. A few code lines down there is a line starting with SELECT. Without navigating away,
display some information about code element /dmo/bookingafter keyword FROM.
You must already have an SAP BTP account with an ABAP service and service key. You must
also have installed Eclipse and the SAP ABAP Development Tools.
1. Log on to your subaccount in SAP BTP and copy the ABAP service key into your clipboard.
a) Start the SAP BTP Cockpit and choose the subaccount containing your ABAP service.
f) Paste the service key into the editor with the key combination, Ctrl + V, and choose
Next.
g) Choose Open Logon Page in Browser. When you see the message, You have been
successfully logged on, close the browser window and return to Eclipse.
Note:
You are not supposed to actually read the code you are going to see in this
exercise. Concentrate on the navigation and display functions in the development
environment.
Note:
If you cannot find class /DMO/CL_FLIGHT_LEGACY or any other development
object starting with /DMO/, you have to import the ABAP Flight
Reference Scenario for the ABAP RESTful Application
Programming Model into your system. To do so, please follow the
instructions under the following link:https://github.com/SAP-samples/abap-
platform-refscen-flight
a) In the Eclipse menu, choose Navigate → Open ABAP Development Object ... or press
Ctrl + Shift + A
2. Open the Properties tab to display the administrative data of development object /DMO/
CL_FLIGHT_LEGACY.
a) Place the cursor anywhere in the source code of class /DMO/CL_FLIGHT_LEGACY.
b) In the tab strip below the editor view, navigate to tab Properties.
c) Analyze the administrative data that are displayed there, for example the original
language, the time stamp of the last change or the user that at first created the object.
3. Locate ABAP class /DMO/CL_FLIGHT_LEGACY in the Project Explorer view on the left.
a) Place the cursor anywhere in the source code of class /DMO/CL_FLIGHT_LEGACY.
b) On the tool bar of the project explorer, choose Link with Editor. This should expand a
part of the tree under your ABAP project with ABAP class /DMO/CL_FLIGHT_LEGACY
as its end point.
4. Find the string get_instance( )->get in the source code of ABAP class /DMO/
CL_FLIGHT_LEGACY.
Note:
There has to be exactly one blank between the brackets.
c) In the input field labelled with Find: enter get_instance( )->get and choose Find.
6. A few code lines down there is a line starting with SELECT. Without navigating away,
display some information about code element /dmo/bookingafter keyword FROM.
a) In the code line starting with SELECT, place the cursor on /dmo/booking and choose
Source Code → Show Code Element Information. Alternatively, press F2.
Note:
There is no need to read or understand the documentation at this point. If
you are familiar with Structured Query Language (SQL) you can have a look
at the first paragraph in section Effect.
LESSON SUMMARY
You should now be able to:
● Work with a development object in ADT
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Create an ABAP package
When you create a development object in the ABAP environment, you must assign it to a
package. Packages serve as containers for all of the development objects that logically belong
together. Each package is also assigned to a software component. The complete set of
development objects in the system is referred to as the ABAP Repository. Consequently,
development objects are also often called repository objects.
You develop your applications in a development environment, but must then ensure that they
can be tested in an appropriate test environment before being moved on to the production
environment. Typically, you will have a single global account and a subaccount for each of the
development, test, and production environments. Software components allow you to
transport your objects.
Transport Request
When you create a new development object or change an existing one, you must assign it to a
transport request. Transport requests ensure that all development objects that logically
belong together are transported together into the test, and subsequently the production
system.
Each transport request has an owner, and the owner can assign other users to the request. In
this way, transport requests support team development.
When an object is included in a transport request, it is locked. This means that it can only be
edited by a user who is assigned to the same request.
When work on all of the objects in the request is finished, all of the developers assigned to it
must release their work. After this, the owner of the request can release the entire request. If
the transport request belongs to a transportable software component, the system
administrator can import it into the test system for testing.
When you release any kind of transport request, the system releases the locks on the objects
in the request, so that any developer can access them again.
To learn how to create an ABAP package, refer to the demonstration, How to Create a
Package.
To create a new package, choose File → New → ABAP Package. Enter the name of the
package, which must begin with the letter Z or Y. This is a requirement for all ABAP
development in the customer name-space, and serves to avoid naming collisions, as objects
delivered by SAP never use Z or Y as the first character of the name. Enter a description for
the package, and choose Next.
In the next dialog box, you must assign the package to a software component. Since you are
creating practice objects that are not intended for productive use, enter the software
component ZLOCAL and choose Next.
You must assign your package to a transport request. As you have not yet worked with
transport requests, select Create a new request. Enter a description for the transport request
and choose Finish.
You are now ready to start developing. You will assign all of the further objects that you create
to your package and to the same transport request.
LESSON SUMMARY
You should now be able to:
● Create an ABAP package
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Create a Hello World Application
As in all other programming languages, the first thing that you should do in ABAP is familiarize
yourself with the development environment and the most elementary aspects of the language
by writing a short “Hello World” app.
The main user interface technology that you will use in modern ABAP programming is Fiori
Elements. However, ADT provides a console for you to create output quickly and simply in
test applications.
You will write your ABAP code in a class. To create one, choose File → New → ABAP Class. A
dialog box appears. Ensure that the project is correct, and enter the name of the package that
you have already created.
Next you must name your class, following ABAP naming conventions; the name of the class
must start with Z or Y to avoid naming collisions with SAP's own objects. Naming conventions
also require that the name of the class is also prefixed with CL for class.
Following the prefix ZCL, enter the actual name of the class. This should be as easily
understandable as possible. The name of the class (including prefix) can be up to 30
characters long and may consist of the letters A-Z, the digits 0-9, and the underscore symbol
_. You use the underscore to separate the individual words that you use in the name of the
class.
Once you have entered the name of your class, choose Add to add an interface to a class.
Interfaces ensure that a user of the class can use it in a particular way. The interface
IF_OO_ADT_CLASSRUN ensures that the development environment can run the class and
display output in the Eclipse console. In the Add ABAP Interface dialog box, use the filter field
to restrict the number of entries in the hit list. Once the interface IF_OO_ADT_CLASSRUN is
displayed, double-click it. When you return to the New ABAP Class dialog box, the interface
appears in the list of interfaces.
Choose Next.
On the last screen, you assigned the class to a package. Now you must assign it to a transport
request. Under Choose from requests in which I am involved, mark the request that you used
to create your package and choose Finish.
The interface IF_OO_ADT_CLASSRUN allows you to run a class in ADT using the F9 key. When
you do so, the system executes the code between METHOD if_oo_adt_classrun~main and
ENDMETHOD. In this code block you can output information in the ADT console.
In your code block, you can use out->write( ) to display information in the console. The line
out->write( “Hello World”). prints Hello World to the console. Crucially, you do not have to
know at this point how it works, you just have to type in the code, ensuring the following:
When you write ABAP code, you will inevitably make mistakes. ADT checks your code as you
go along, and flags up errors in the left-hand margin of the editor with a white cross on a red
background.
You can see the corresponding error messages in the Problems view below the editor. ADT
also displays the same message as a pop-up if you move the mouse over the error symbol in
the editor.
In order to run an ABAP object, you must activate it. To do this, choose the Activate icon in
the toolbar or use the keyboard shortcut Ctrl + F3. You can see whether an object is active
or not by looking in the Properties view, usually located in the tab below the ABAP Editor.
During the activation, the system compiles the object into a form that the ABAP runtime
system can understand.
To run the class, press F9 or right-click in the editor and choose Run as → ABAP Application
(Console). The output “Hello World” appears in the console. If you cannot see the console
view, choose Window → Show View → Other... and select the Console view.
1. Create ABAP package ZS4D400_##, where ## is your group number. Assign the package
to the software component ZLOCAL. When you are prompted to assign the package to a
transport request, create a new one.
1. In your package, create a new ABAP class with the name ZCL_##_HELLO_WORLD. Ensure
that it uses the interface IF_OO_ADT_CLASSRUN. When you are prompted to assign the
class to a transport request, use the transport request that you created in the previous
task.
1. Create ABAP package ZS4D400_##, where ## is your group number. Assign the package
to the software component ZLOCAL. When you are prompted to assign the package to a
transport request, create a new one.
a) Choose File → New → ABAP Package.
d) Choose Next.
f) Mark Create a new request and enter a description for the new transport request.
g) Choose Finish.
c) In the dialog box, enter the package name ZS4D400_##, where ## is your group
number.
d) Double-click the name of the package in the hit list to add it to your list of favorite
packages.
1. In your package, create a new ABAP class with the name ZCL_##_HELLO_WORLD. Ensure
that it uses the interface IF_OO_ADT_CLASSRUN. When you are prompted to assign the
class to a transport request, use the transport request that you created in the previous
task.
a) Choose File → New → ABAP Class.
e) Enter the filter text IF_OO_ADT_CLASSRUN. Double-click the matching entry in the hit
list.
f) Choose Next.
g) Mark Choose from requests in which I am involved and your own transport request.
h) Choose Finish.
b) If the Console view is not visible, open it by choosing Window → Show view → Other.
Double-click Console in the hit list.
LESSON SUMMARY
You should now be able to:
● Create a Hello World Application
Learning Assessment
1. An ABAP Cloud Project in ABAP Development Tools (ADT) allows you to connect to what
kinds of system?
Choose the correct answers.
2. In ABAP source code, how do you navigate to the definition of a development object?
Choose the correct answer.
X D Declare what other packages can use the contents of the package.
4. Which of the following characters is not allowed in the name of an ABAP class?
Choose the correct answer.
X A 8
X B _
X C $
Lesson 1
Understanding the Basics of ABAP 33
Lesson 2
Working With Basic Data Objects and Data Types 39
Lesson 3
Processing Data 53
Lesson 4
Working with Simple Internal Tables 59
Lesson 5
Using Control Structures in ABAP 67
Lesson 6
Debugging an ABAP Program 79
Exercise 3: Debug an ABAP Program 89
UNIT OBJECTIVES
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Describe the evolution of ABAP
● Describe the basics of ABAP syntax
the existing statements. Again, ABAP combines traditional statement-based syntax elements
and modern, expression-based syntax elements in one programming language.
For new ABAP developments, we recommended using object-oriented and expression-based
syntax elements wherever possible.
Watch this video to know how ABAP has evolved over the years.
Each ABAP program has the program attribute “ABAP language version”, which is defined
internally by a version ID. The version of a program determines which language elements and
which repository objects can be used in the program and which syntax rules apply. The
following versions are currently available:
Let's explore the available ABAP language versions.
Animation
For more information on this topic please view the animation in the lesson
Understanding the Basics of ABAP in your online course.
Note:
This course gives a universal introduction to ABAP development. It restricts itself
to syntax elements and language features available in all three language versions.
An exception is the introduction to the ABAP RESTful Application Programming
Model which is not part of ABAP for key users.
Animation
For more information on this topic please view the animation in the lesson
Understanding the Basics of ABAP in your online course.
These code examples illustrate some of the basic features of the ABAP programming
language. The upper code consists of three statements, each of them ends with a period (.) .
Comments in ABAP
Note:
You can place the " sign in any column. The star sign (*) only works if placed in the
first column. In other positions it causes a syntax error, unless it is a syntactically
correct part of an ABAP statement.
Hint:
To comment one or several code lines, that is, to add star sign (*) in the first
column, select the code lines and press Ctrl + <. Alternatively, from the
context menu, you can choose Source Code → Add Comment. To uncomment
the selected code lines, that is, to remove the star sign (*) from the first column,
press Ctrl + > or choose Souce Code → Remove Comment from the context
menu.
LESSON SUMMARY
You should now be able to:
● Describe the evolution of ABAP
● Describe the basics of ABAP syntax
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Declare data objects
● Assign values
A data object in an ABAP program represents a reserved section of the program memory.
ABAP knows three types of data objects: Variables, Constants, and Literals.
Variables
A variable is a data object with content that can change during runtime. A variable is
identified by a name. The name is also used to address the data object at runtime. The
starting value of an ABAP variables is always well-defined.
Constants
Constants are similar to variables. But in contrast to variables the value is hard coded in
the source code and must not change during runtime. Like variables, constants have a
name by which they can be re-used.
Literals
The value of literals is also hard-coded in the source code. In contrast to constants,
literals don't have a name. Because of that you cannot reuse a literal. Only use literals to
specify the values for constants and the starting values for variables.
ABAP data objects are always typed: Every data object is based on a data type which
determines the kind of information they can contain. The data type of an ABAP data object
stays the same throughout a program execution.
Declaration of Variables
DATA
Keyword DATA is followed by the name of the variable. The name of a variable may be up
to 30 characters long. It may contain the characters A-Z, the digits 0-9, and the
underscore character. The name must begin with a letter or an underscore.
TYPE
The type of the variable is specified after addition TYPE. In the example, built-in types i
(for integer numbers) and string (character string with variable length) are used.
VALUE
Addition VALUE is optional and you can use it to specify a start value for the variable. If
VALUE is missing, the variable is created with an initial value that depends on the
technical type of the variable.
Animation
For more information on this topic please view the animation in the lesson
Working With Basic Data Objects and Data Types in your online course.
ABAP Built-in
ABAP has a set of 13 predefined data types for simple numeric, char-like, and binary data
objects.
TYPES Statement
Statement TYPES allows you to define data types and reuse them in different places,
depending on the location of the definition.
ABAP Dictionary
The ABAP Dictionary is a part of the ABAP Repository. Among other things, it manages
global data types which are available throughout the system. ABAP Dictionary types not
only define technical properties, they add semantic information, for example, labels.
ABAP Dictionary types are particularly useful when implementing user interfaces.
Animation
For more information on this topic please view the animation in the lesson
Working With Basic Data Objects and Data Types in your online course.
Some important ABAP types are listed in the figure, Some Predefined ABAP Types. For some
of these types you can add addition LENGTH to specify a fixed length. These types are called
incomplete types. In the case of type P, you may also specify a number of decimal places. The
ABAP types for which additions LENGTH and DECIMALS are not supported are called
complete types.
TYPE T
A field of type Type T represents a time. In ABAP, this has the format HHMMSS (without
separators in 24 hour format). If the current locale uses 12 hour format, the system
converts the values automatically.
A field of type C is a character-like field of specific length. You specify its length in
characters; the runtime system then assigns double the number of bytes in order to
accommodate the field. You use this type when a fixed length is important.
Type N
A field of type N is a character field of specific length that only contains digits. This field
should contain sequence of digits that you do not want to regard as a number and
perform calculations with. For example, this could be a personnel number or cost center.
Type P
A field of type P (P for “packed number”) is a field that contains a numeric value with a
specified number of digits and decimal places. Use this type for numbers with decimal
places or where the value range type I is not sufficient.
* Output
**********************************************************************
variable = '19891109'.
4. Analyze the console output. Uncomment different declarations of variable. Try your
own declarations to get familiar with the concepts.
Instead of using built-in types in the DATA statement directly, you can use statement TYPES
to define the type first. You can then use the type in a DATA statement after the TYPE
addition.
In an SAP system, there are thousands of business entities, such as country code, plant,
material number, fiscal year, cost center, and so on. Technically, it would be possible to define
these entities in every single program using the built-in ABAP types that you have just seen.
However, this would be extremely labor-intensive and error-prone. Instead, SAP provides the
ABAP Dictionary, which is a central store for important data types and also the tool that you
use to create database tables.
In the ABAP Dictionary, single business entities are described by data elements. In the
example, variable airport is typed with data element /DMO/AIRPORT_ID.
When you press the F2 key to display the details of this data type you can see that technically
this type is a character of length 3. In addition, the data element provides the description
“Flight Reference Scenario: Airport ID” and four field labels of different length.
When you press the F3 key to navigate to the definition of the type, a new view opens with the
editor for data elements.
A constant is a data object with a hard-coded value that must not be changed during runtime.
Any write access to a constant leads to a syntax error.
In ABAP, you declare a constant using keyword CONSTANTS. A CONSTANT statement
consists of the same parts as a DATA statement. The only difference is, that the VALUE
addition is mandatory.
You can use the VALUE addition in the special form VALUE IS INITIAL, if the value of the
constant should be the type-specific initial value.
Literals in ABAP
Literals are anonymous data objects with a hard-coded value. Literals are often used to define
non-initial values for constants and non-initial starting values for variables.
Technically, you can use literals anywhere in your code. To support readability and
maintainability, it is recommended to define and use constants, instead.
Animation
For more information on this topic please view the animation in the lesson
Working With Basic Data Objects and Data Types in your online course.
* Example 3: Constants
**********************************************************************
* Example 4: Literals
**********************************************************************
"uncomment this line to see syntax error (no number literal with
digits)
* out->write( 12345.67 ).
4. Play around with the source code to get familiar with the concepts.
● ...
As shown in the figure, if possible, try to avoid type conversions for the following reasons:
● Additional Runtime consumption: Values with type conversions require more runtime than
value assignments with identical types.
● Potential Runtime Errors: Some combinations of source type and target type can lead to
runtime errors. If, for example, the target variable has a numeric type l and the source
expression has a character like type, then the runtime will raise an error if it cannot
translate the text into a number.
● Potential Information Loss: Some combinations of source type and target type do not
cause runtime errors but can lead to a loss of data. If, for example, source and target are of
both of type C but the type of the target variable is shorter. Then the runtime simply
truncates the value of the source.
Resetting Variables
Note:
The CLEAR statement disregards the starting value from the VALUE addition.
After CLEAR the variable always contains the type-specific initial value.
Until now, we used the DATA statement to explicitly declare our variables before we used
them in executable coding.
As an alternative, you can declare a variable at the place where you first fill it with data. We call
this an Inline Declaration. For an inline declaration you surround the name of the new variable
with brackets and add keyword DATA before the opening bracket.
Note:
No blanks are allowed inside the brackets or between DATA and the opening
bracket.
After the inline declaration, you can use the variable just like any explicitly declared variable.
The ABAP compiler derives the type for the inline declared variable from the context of the
operand position. If, for example, an inline declaration is used on the left hand side of a value
assignment the type of the new variable is the same as the type of the expression on the right-
hand side.
In the example shown in the figure Inline Declarations in Value Assignments, my_var1 is of
type string, because that is the type of the string literal on the right hand side. The other three
variables are of type integer.
Watch this video to learn about inline declarations and how they work in value assignments.
Video
For more information on this topic please view the video in the lesson Working
With Basic Data Objects and Data Types in your online course.
LESSON SUMMARY
You should now be able to:
● Declare data objects
● Assign values
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Perform arithmetic calculations
● Apply string processing
Arithmetic Calculations
Arithmetic Expressions
Arithmetic expressions are ABAP expressions with a combination of values, operators, and
functions that the runtime system processes to calculate a result. For arithmetic expressions
the result type depends on the type of the operands used as input to the expression.
You can use an arithmetic expression in any reading operand position, for example, the right-
hand side of a value assignment.
The first example is a simple addition. The contents of amount1 and amount2 are added
together and the result placed in variable total.
The second example is a bit more sophisticated. Before adding them, the contents of
amount1 and amount2 are weighed with factors 2 and 3. The result of this addition is then
divided by 5 to calculate a weighed average.
For basic arithmetic, ABAP provides the operators + for addition, - for subtraction, * for
multiplication and / for division. Additionally, you can use the operators DIV for whole-number
division and MOD for the whole-number remainder of a division. Thus, 6 DIV 4 is 1, and 6 MOD
4 is 2.
ABAP has a range of built-in functions for various tasks. Many of these are used for string
processing, but here you can see some examples for numeric functions. You use sqrt( )
function to pull the square root and ipow( ) function to raise a number to a whole-numbered
power.
In complex expressions involving more than one operator, multiplication and division take
precedence over addition and subtraction. Expressions with identical precedence are
processed from left to right.
Note:
ABAP syntax requires at least one blank between operators and operands. 1 + 1 is
correct. 1+1 leads to a syntax error.
Blanks are also needed after opening brackets and before closing brackets.
* Declarations
**********************************************************************
* Calculations
**********************************************************************
" comment/uncomment these lines for different calculations
result = 2 + 3.
* result = 2 - 3.
* result = 2 * 3.
* result = 2 / 3.
*
* result = sqrt( 2 ).
* result = ipow( base = 2 exp = 3 ).
*
* result = ( 8 * 7 - 6 ) / ( 5 + 4 ).
* result = 8 * 7 - 6 / 5 + 4.
* Output
**********************************************************************
out->write( result ).
4. Play around with the source code to get familiar with the concepts.
Processing Strings
String templates are ABAP expressions of result type string. You can use string templates in
any reading operand position, for example, the right-hand side of a value assignment.
A string template begins and ends with a pipe-symbol ( | ). The simplest possible string
template contains nothing but literal text. In this form a string template is not really different
from a string literal.
What distinguishes a string template from a string literal is the ability to embed expressions.
An embedded expression is an ABAP expression surrounded by a pair of curly brackets
( { and } ). At runtime, ABAP evaluates the embedded expression and translates the result into
a string. In the final result, this string replaces the embedded expression (together with the
surrounding curly brackets).
Note:
ABAP syntax requires at least one blank after the opening bracket and at least one
blank before the closing bracket.
Of course one string template can contain more than one embedded expression.
Inside the curly brackets you can place any kind of ABAP expression: arithmetic expressions,
like in the example above, but single variables or even literals can serve as embedded
expressions.
Let's see how can process strings using ABAP.
Animation
For more information on this topic please view the animation in the lesson
Processing Data in your online course.
One important use case for string templates is the controlled formatting of data for output.
In the first example, variable the_date is of type d and contains a date in the internal (raw)
format YYYYMMDD (where YYYY stands for the year, MM for the two-digit month and DD for
the two-digit date). When you use variable the_date as embedded expression in a string
template the result will be the same as the internal format. But when you add format option
DATE = <date_format> within the curly brackets, the system will format the value as a date. If
you add DATE = ISO, the output will be in ISO format. With DATE = USER the output format
depends on the user settings of the current user.
The second example illustrates some of the options you can use for formatting numbers.
Using NUMBER you control the general formatting of numbers, for example, whether a
decimal point is used or a decimal comma. Using SIGN you control the position of the sign and
whether a plus sign (+) is displayed or not. Using STYLE you can choose from several pre-
defined styles, like a scientific style or an engineering style.
You can join fields together using the concatenation operator &&. You can join any
combination of data objects and string expressions.
The constituent parts of the expression are joined with no space or other separator between
them. If you need spaces or another separator character, you must remember to insert it
yourself as part of the expression as shown in the second example.
METHOD if_oo_adt_classrun~main.
* Declarations
**********************************************************************
TYPES t_amount TYPE p LENGTH 8 DECIMALS 2.
* String Templates
**********************************************************************
* Format Options
**********************************************************************
"Date
* DATA(text) = |Raw Date: { the_date }|.
* DATA(text) = |ISO Date: { the_date Date = ISO }|.
* DATA(text) = |USER Date:{ the_date Date = USER }|.
"Number
* DATA(text) = |Raw Number { my_numer }|.
* DATA(text) = |User Format{ my_numer NUMBER = USER }|.
* DATA(text) = |Sign Right { my_number SIGN = RIGHT }|.
* DATA(text) = |Scientific { my_number STYLE = SCIENTIFIC }|.
* Output
**********************************************************************
out->write( text ).
4. Play around with the source code to get familiar with the concepts.
LESSON SUMMARY
You should now be able to:
● Perform arithmetic calculations
● Apply string processing
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Define simple internal tables
● Process data using simple internal tables
Each value occupies one row of the internal table. The number of rows is not restricted.
Theoretically, you can store any number of values in one internal table. Limitations only come
from technical boundaries like available memory or system configuration.
The initial value of an internal table is an empty table or, in other words, a table with 0 lines.
There are different techniques for filling an internal table. The example uses the APPEND
statement to add a new row at the end of the internal table and fill it with a value.
Table Types
There are also table types in the ABAP Dictionary. These table type are maintained with a
dedicated editor. They are called global table types because they are visible anywhere in the
system.
One way to fill an internal table with data is the APPEND statement. With each APPEND
statement a new row is added to the internal table. This new row is then filled with the value of
the expression after keyword APPEND.
Note:
The type of the expression has to be compatible with the line type of the internal table. If it is
not identical, the system attempts an implicit type conversion.
Let's look at how you can process data with a simple internal table.
Animation
For more information on this topic please view the animation in the lesson
Working with Simple Internal Tables in your online course.
The initial value of an internal table is an empty table, that is, an internal table with zero rows.
You already learned that with statement CLEAR you can reset an ABAP variable to its type-
specific initial value.
When you use CLEAR for an internal table, you delete all its content and set the number of
rows to zero.
There are various ways to retrieve data from an internal table. This example retrieves the
content of a single row using an internal table expression. In the table expression, the name of
the internal table is followed immediately by a pair of square brackets. An integer expression
inside the brackets specifies the position of the row to be read.
Note:
Correct syntax requires at least one blank after the opening bracket and before the closing
bracket.
You can read all rows of an internal table and process them in turn by using a combination of
statements LOOP AT and ENDLOOP. LOOP AT and ENDLOOP always come in pairs, that is,
for each LOOP AT statement there has to be exactly one corresponding ENDLOOP statement.
Between LOOP AT and ENDLOOP, you can place any amount of ABAP code. The code you
place there is processed several times, once for each row of the internal table.
Note:
If the internal table is empty, the coding between LOOP AT and ENDLOOP is not
processed at all.
The data object listed after addition INTO is called the work area for the internal table. The
type of this data object must be identical to, or at least compatible with, the line type of the
internal table.
At the beginning of each round, the work area is filled with the data from the next table row.
In the example internal table numbers contains 3 rows. Therefore the coding between LOOP
AT and ENDLOOP is processed 3 times. During the first round, variable number contains 4711,
the value of the first row. This value is the written to the console. At the beginning of the
second round, the value in variable number is replaced with value 1234 from the second row,
and so on.
When you implement a loop over an internal table, you can use an inline declaration after
addition INTO instead of declaring the work area explicitly with a DATA statement.
By doing so, you not only reduce the amount of code you have to type, you also ensure that
the type of work area fits the line type of the internal table, because the type of the inline
declared data object is derived from the context, which, in this case, is the internal table.
* Declarations
**********************************************************************
* Example 1: APPEND
**********************************************************************
out->write( `-----------------` ).
out->write( numbers ).
* Example 2: CLEAR
**********************************************************************
CLEAR numbers.
out->write( `----------------` ).
out->write( `Example 2: CLEAR` ).
out->write( `----------------` ).
out->write( numbers ).
out->write( `---------------------------` ).
out->write( `Example 3: Table Expression` ).
out->write( `---------------------------` ).
number = numbers[ 2 ] .
ENDLOOP.
4. Analyze the console output. Play around with the source code to get familiar with the
concepts.
LESSON SUMMARY
You should now be able to:
● Define simple internal tables
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Implement conditional branching
● Implement Iterations
● Handle Exceptions
A conditional branching is a control structure that allows you to make the execution of code
dependent on logical conditions.
The most common conditional branching consists of a pair of keywords IF and ENDIF. The
code block between IF and ENDIF is only executed, if the condition after IF is fulfilled.
You can add more code blocks by extending the IF … ENDIF structure with up to one keyword
ELSE and an arbitrary number of keywords ELSEIF. By adding keyword ELSE you ensure that
always exactly one of the code blocks is executed. If ELSE is missing it can happen that none
of the code blocks is executed.
The code block to be executed is determined as follows:
● First, the IF condition is evaluated. If it is fulfilled, the related code block is executed and
the program continues after ENDIF.
● Only if the IF condition is not fulfilled, the condition after the first ELSEIF is evaluated. If it is
fulfilled, the related code block is executed and the program continues after ENDIF.
● This is done consecutively for all ELSEIF conditions. If none of the conditions is fulfilled and
the structure contains ELSE, the code block after ELSE is executed. Otherwise the none of
the code blocks is executed.
Hint:
As opposed to many other programming languages, ABAP requires a delimiter
(.) after each of the logical conditions and even after keyword ELSE.
Animation
For more information on this topic please view the animation in the lesson Using
Control Structures in ABAP in your online course.
For simple value comparisons you can use operators =, <>, >, <, >=, and <=. You can not only
compare the values of data objects, but the values of many other expressions, like the
arithmetic expression 2 * y in the example.
Note:
ABAP uses the same symbol (=) for value assignments and for value
comparisons. The distinction is made based on the position.
You can use operators AND and OR to combine logical expressions and operator NOT to
negate an expression. Without brackets, NOT binds stronger than AND and AND stronger
than OR.
ABAP knows some special logical expressions:
● <data object> IS INITIAL is true if <data object> contains its type-specific initial value
● <data object> IS NOT INITIAL is true if <data object> contains a value that is different from
the type-specific initial value
● <data object> BETWEEN <expression1> AND <expression2>
Some special ABAP functions are predicate functions. This means that they are logical
conditions themselves. Contains( ) is a function that compares character-like values and
line_exists( ) performs an existence check for a row in an internal table.
A second technique for conditional branching is the CASE … WHEN .. ENDCASE control
structure.
Conditional branching with CASE .. ENDCASE is a special case of the more general branching
with IF … ENDIF. You can use CASE in situations where the branching depends on the value of
a single data object, which you consecutively compare to a set of possible values, using an
equals comparison each time.
In the example, the value of data object number is compared to values 1 and 2. If the value
equals 1, <code_block_1> is executed and if the value euqals 2, <code_blocl_2> is executed
instead. For any other value, the code block after WHEN OTHERS is executed.
Any conditional branching with CASE … ENDCASE could be implemented with an IF … ENDIF
structure, as well. This is illustrated with the example on the right.
Hint:
You should use CASE … ENDCASE when dealing with the special case to increase
readability of your code.
* Declarations
**********************************************************************
out->write( `--------------------------------` ).
out->write( `Example 1: Simple IF ... ENDIF.` ).
out->write( `-------------------------------` ).
IF c_number = 0.
out->write( `The value of C_NUMBER equals zero` ).
ELSE.
out->write( `The value of C_NUMBER is NOT zero` ).
ENDIF.
out->write( `--------------------------------------------` ).
out->write( `Example 2: Optional Branches ELSEIF and ELSE` ).
out->write( `--------------------------------------------` ).
IF c_number = 0.
out->write( `The value of C_NUMBER equals zero` ).
ELSEIF c_number > 0.
out->write( `The value of C_NUMBER is greater than zero` ).
ELSE.
out->write( `The value of C_NUMBER is less than zero` ).
ENDIF.
out->write( `---------------------------` ).
out->write( `Example 3: CASE ... ENDCASE` ).
out->write( `---------------------------` ).
CASE c_number.
WHEN 0.
out->write( `The value of C_NUMBER equals zero` ).
WHEN 1.
out->write( `The value of C_NUMBER equals one` ).
WHEN 2.
out->write( `The value of C_NUMBER equals two` ).
WHEN OTHERS.
out->write( `The value of C_NUMBER equals non of the above` ).
ENDCASE.
4. Analyze the console output. Play around with the source code to get familiar with the
concepts; Uncomment different declarations of constant c_number with different values
to see, which branches of the code get executed.
Implementing Iterations
Iterations are control structures that define a block of code which is executed several times.
The simplest form of iteration consists of a code block surrounded by pair of statements DO
and ENDDO. Without further measures this establishes an endless loop which must be
avoided by one of the following possibilities:
In the code block between DO and ENDDO, you can implement read-accesses to ABAP built-
in data object sy-index. This integer variable serves as an iteration counter, that is, the ABAP
runtime increases it by one at the beginning of each new iteration.
Note:
In contradiction to what you might be used to from other programming
languages, sy-index starts with value 1 during the first iteration.
ABAP built-in variable sy-tabix can fulfill a similar purpose for iterations with LOOP. But be
aware that strictly speaking sy-tabix is not really a counter but it identifies the position of the
table row that is processed in the current iteration. Later we will see the difference when not
all rows of an internal table are processed in a LOOP … ENDLOOP structure.
* Declarations
**********************************************************************
out->write( `----------------------------------` ).
out->write( `Example 1: DO ... ENDDO with TIMES` ).
out->write( `----------------------------------` ).
DO c_number TIMES.
out->write( `Hello World` ).
ENDDO.
out->write( `-------------------------------` ).
out->write( `Example 2: With Abort Condition` ).
out->write( `-------------------------------` ).
"abort condition
IF number <= c_number.
EXIT.
ENDIF.
ENDDO.
4. Analyze the console output. Play around with the source code to get familiar with the
concepts; Uncomment different declarations of constant c_number to see how the
different values affect the result.
Handling Exceptions
Exceptions
All application exceptions and many system exceptions are catchable. Later in this course you
will learn how to raise application exceptions. At this point we will focus on the handling of
catchable system exceptions.
What are exceptions? Let's take a look.
Animation
For more information on this topic please view the animation in the lesson Using
Control Structures in ABAP in your online course.
In the figure, Some Examples for Catchable System Exceptions, you can see some examples
of runtime errors:
● The first code example attempts a division with zero as the denominator. This is not
defined.
● The second code example wants to convert a character string into an integer number but
the character string cannot be interpreted as a number.
● The third code example tries to read the first row from an internal table. But the internal
table is initial and therefore no first row can be found.
When you execute the code examples on the left, the ABAP runtime raises catchable system
exceptions. These exceptions are not treated by the program yet, which leads to runtime
errors.
The screenshots on the right are snippets from the short dumps created by these runtime
errors. Beside a short text to describe the situation, you see the ID of the runtime error and
the ID of the uncaught exception.
Exception Handling
1. If no exception is raised during the TRY block, the CATCH blocks are ignored. Execution
continues after the ENDTRY statement.
2. If an exception is raised during the TRY block, for which a matching CATCH exists,
execution of the TRY block is terminated and the CATCH block for this exception is
executed. Afterward, execution continues after the ENDTRY statement.
3. If an exception is raised during the TRY block for which no matching CATCH exists, the
program terminates with a runtime error.
Now that you have learned about exceptions, let's see how you can handle them.
Animation
For more information on this topic please view the animation in the lesson Using
Control Structures in ABAP in your online course.
* Declarations
**********************************************************************
DATA result TYPE i.
* Preparation
**********************************************************************
out->write( `---------------------------` ).
TRY.
result = c_text.
out->write( |Converted content is { result }| ).
CATCH cx_sy_conversion_no_number.
out->write( |Error: { c_text } is not a number!| ).
ENDTRY.
out->write( `---------------------------` ).
out->write( `Example 2: Division by Zero` ).
out->write( `---------------------------` ).
TRY.
result = 100 / c_number.
out->write( |100 divided by { c_number } equals { result }| ).
CATCH cx_sy_zerodivide.
out->write( `Error: Division by zero is not defined!` ).
ENDTRY.
out->write( `-------------------------` ).
out->write( `Example 3: Line Not Found` ).
out->write( `-------------------------` ).
TRY.
result = numbers[ c_index ].
out->write( |Content of row { c_index } equals { result }| ).
CATCH cx_sy_itab_line_not_found.
out->write( `Error: Itab has less than { c_index } rows!` ).
ENDTRY.
out->write( `----------------------` ).
out->write( `Example 4: Combination` ).
out->write( `----------------------` ).
TRY.
result = numbers[ 2 / c_char ].
out->write( |Result: { result } | ).
CATCH cx_sy_zerodivide.
out->write( `Error: Division by zero is not defined` ).
CATCH cx_sy_conversion_no_number.
out->write( |Error: { c_char } is not a number! | ).
CATCH cx_sy_itab_line_not_found.
4. Analyze the console output. Play around with the source code to get familiar with the
concept:
● Comment the exception handling to make the system raise a runtime error.
● Change the values of constants c_number, c_text, c_index, and c_charin a way
that no exceptions are raised.
LESSON SUMMARY
You should now be able to:
● Implement conditional branching
● Implement Iterations
● Handle Exceptions
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Enter debugging mode
● Control the execution of code
● Analyze the content of data objects
There is no way around the fact that errors occur in programs. However, they manifest
themselves in different ways. When a user starts a faulty application, it may crash, something
unexpected may happen, or nothing at all may happen. From the user's point of view, at user
interface level, it is impossible to say just how and why this error occurred.
As a developer, you now need to examine the program more closely - line-by-line in fact - to
establish just what statements and combinations of values in the different program variables
caused the error. This is where the Debugger comes in.
Watch this video to understand the need for debugging.
To debug an ABAP program, you set a breakpoint then run the program normally. When the
program reaches the breakpoint, the system interrupts it and opens the ABAP Debug
perspective in ADT. You can then execute each subsequent statement individually to see what
effect it has on the program. You can also inspect the contents of all of the variables in the
program to see if any of the values are unexpected.
To set or remove a breakpoint, right-click the left margin of the editor and choose Toggle
Breakpoint. As an alternative you can double-click the left margin. Note that the program has
to be activated before you can set breakpoints.
Breakpoints are user-specific and are persistent - they remain active even after you have
logged off from ADT and back on again. To prevent the debugger from starting at a breakpoint
you must either delete the breakpoint (using the Toggle Breakpoint function) or deactivate it
using the corresponding function in the context menu.
Note:
Depending on your personalization settings, ADT will ask for confirmation, first,
before automatically opening the debug perspective.
When you debug an ABAP program using ABAP Development Tools, you use the Debug
perspective. This is a customized version of the standard Eclipse Debug perspective, and it
contains views and functions that are particularly important for debugging.
Some important elements of the debugger perspective ar as follows:
Hint:
Because many important editor tools are missing from the Debugger
perspective it is recommended to switch back to the ABAP perspective
once you finished debugging.
Animation
For more information on this topic please view the animation in the lesson
Debugging an ABAP Program in your online course.
Once you started debugging, use the navigation functions to control the execution of the
code.
Some important navigation functions are as follows:
Animation
For more information on this topic please view the animation in the lesson
Debugging an ABAP Program in your online course.
Special Breakpoints
You learned that you can create and manage breakpoints by clicking on the left margin of the
ABAP editor view. This also works with the ABAP editor view in the Debug perspective.
In addition, you can switch to the Breakpoints view and manage your breakpoints there.
In the Breakpoints view you can also create special breakpoints:
Statement Breakpoint
A statement breakpoint is not attached to a specific line of code but to a specific ABAP
statement. A statement breakpoint on statement CLEAR, for example, causes the
program to stop in the debugger whenever a CLEAR statement is executed - no matter
where this statement is located.
To create a statement breakpoint, open the dropdown list from the toolbar of the
Breakpoints view and choose Add Statement Breakpoint … .
Exception Breakpoint
An exception breakpoint is attached to a specific exception. It causes the program to
stop in the debugger whenever this particular exception is raised - no matter if this
exception is handled by the program or causes a runtime error. To create an exception
breakpoint, open the dropdown list from the toolbar of the Breakpoints view and choose
Add Exception Breakpoint … .
Conditional Breakpoints
You turn a breakpoint into a conditional breakpoint by adding a condition. If program
execution hits a conditional breakpoint, the program only stops in the debugger, if the
condition is fulfilled. If, for example, a breakpoint is located between DO and ENDDO it will
cause the program to stop in the debugger in every iteration. But if you add a condition
sy-index > 20 the debugger will ignore this breakpoint during the first 20 iterations and
only stop in the following iterations.
To add a condition to a breakpoint, choose it in the list of breakpoints and enter the
condition in field Condition. Press Enter to save the breakpoint with the condition.
Watchpoints
If an unexpected value of a variable is causing you problems, you can track its value during the
course of the program using a watchpoint.
A simple watchpoint on a variable causes the program to stop in the debugger whenever the
value of this variable changes. By adding a condition, you can achieve that the program does
not stop at every value change of the variable but only in those cases where also the condition
is the fulfilled.
To set a watchpoint on a variable, double-click the variable in the source-code display, then
right-click it and choose Set Watchpoint. This creates a watchpoint on this variable, which
you can then see in the Breakpoints view.
To add a condition to a watchpoint, choose it in the list of breakpoints and enter the condition
in field Condition. Press Enter to save the watchpoint with the condition.
Watch this video to see how.
Video: Watchpoints
For more information on Watchpoints, please view the video in the lesson
Debugging an ABAP Program in your online course.
One way to analyze the content of data objects in the debugger is the mouse-over
functionality of the ABAP Editor. While in debugging mode, place the cursor on the name of a
data object and wait a moment. A dialog box opens with the content of the data object.
Another way to analyze the content of data objects in the debugger is the Variables view. This
view displays a list of data objects and their current values. The main list, the so-called top-
level variables, contains some built-in data objects, by default. In the example, these are SY-
SUBRC and ME. Expand node Locals to see a list of all variable data objects defined in the
current processing block.
There are three ways to add data objects to the main list on the Variables view:
● In the editor, double-click on the name of a data object
● In the variables list, left-click on placeholder <Enter variable> and enter the name of the
data object
● Right-click on a variable in Locals node and choose Show as Top Level Variable
Hint:
To remove a data object from the list, right-click on it and choose Remove.
Watch this video to learn how to display the content of data objects in the debugger.
For internal tables, the Variables view displays an overview with the number of rows and the
number of columns. Expand the hierarchy below the name of the internal table to see the
content of selected rows. Double-click the name of the internal table to analyze it in the ABAP
Internal Table view.
The ABAP Internal Table view is a debugger tool to analyze the content of internal tables. In
the default configuration of the debugger perspective the ABAP Internal Table view is one of
the views below the source code. You can filter the table content by entering a filter pattern
and pressing Enter.
Watch this video to learn how to display the content of internal tables.
For simple variables, locate the variable in the Variables view, right-click on it and choose
Change Value ….
To change the content of an internal table, we have to distinguish between changing the value
of an existing row and adding or deleting of a rows.
As shown in the figure, the following functions are available when you right-click in the ABAP
internal table view:
Change Value …
Choose Change Value …. to change the content of an existing row.
Insert Row …
Choose Insert Row …. to add a new row. You can decide whether you want to append the
new row or insert it at the chose position.
Delete Selected Rows …
Choose Delete Selected Rows … to remove the rows you selected before you right-
clicked. To select a row, left-click it. To select more than one row, hold down the Ctrl key
or the Shift key when you left-click additional rows.
Delete Rows …
Choose Delete Rows …to remove a larger range of rows, or even all rows. You are asked
for the number of the start row and the end row you want to delete.
Task 1: Preparation
Before you can start debugging you have to create the program and copy the source code.
1. Create a new global class ZCL_##_DEBUG, where ## is your group number. Ensure that
the class implements the interface IF_OO_ADT_CLASSRUN.
* Declarations
**********************************************************************
" Types
TYPES t_amount TYPE p LENGTH 8 DECIMALS 2.
TYPES t_percentage TYPE p LENGTH 2 DECIMALS 1.
" Output
DATA repayment_plan TYPE TABLE OF string.
* Processing
**********************************************************************
" Initializations
loan_remaining = loan_total.
CASE spec_repay_mode.
WHEN 'A'.
months_btw_spec_pay = 12.
special_repayment = spec_repay_year.
WHEN 'H'.
months_btw_spec_pay = 6.
special_repayment = spec_repay_year / 2.
WHEN 'Q'.
months_btw_spec_pay = 3.
special_repayment = spec_repay_year / 4.
WHEN OTHERS.
out->write( 'Invalid extra payment mode' ).
EXIT.
ENDCASE.
" Calculations
DO.
IF loan_remaining <= 0.
EXIT.
ENDIF.
DO months_btw_spec_pay TIMES.
months_counter = months_counter + 1.
IF loan_remaining < 0.
EXIT.
ENDIF.
ENDDO.
IF loan_remaining < 0.
EXIT.
ENDIF.
* Output
**********************************************************************
{ months_btw_spec_pay } months. | ).
out->write( |-----------------------
Result-----------------------| ).
out->write( |Total repayment after { months_counter DIV 12 } years
and { months_counter MOD 12 } months. | ).
out->write( |Total interest paid: { interest_total } | ).
"Repayment Plan
out->write(
name = `Repayment Plan:`
data = repayment_plan
).
1. Set a breakpoint at the first statement that does not define a type or declare a data object.
Hint:
TYPES defines a data type, CONSTANTS declares a constant data object,
DATA declares a variable data object.
3. Display the value of data object loan_remaining and loan_total in the Variables view.
1. Execute a single step to debug the value assignment in the current line.
2. Display the content of data object spec_repay_mode. Then execute another single step
to see which WHEN branch of the CASE - control structure is executed.
3. Set a watch point for variable loan_remaining and resume program execution. Where
does the program execution stop again?
4. Display the content of data object repayment_plan. Then execute another single step to
see how it is filled with the APPEND statement.
Note:
Because repayment_plan is an internal table, it not only displays in the
Variables view but also in the ABAP Internal Table (Debugger) view below the
editor.
5. Inspect the string template in the APPEND statement and relate it to the resulting first row
in internal table repayment_plan. Display the content of the data objects that appear in
the embedded expressions.
6. Resume program execution for a few times. Whenever execution reaches one of the watch
points, analyze the value of loan_remaining and new rows are added to
repayment_plan.
7. After a while, set a statement break point for all EXIT statements and delete the two watch
points.
8. Resume program execution until you reach the first EXIT statement. Deactivate the
statement breakpoint for the EXIT statement. Then execute single steps until you reach
the output part of the program.
9. Open the Console view. Execute the remaining program and pursue the output on the
console view.
Note:
Do not press F5 to debug the output. Use F6 instead.
Note:
As you will learn later in the course out->write( ... ) is not an ABAP
statement but a reusable code block that consists of many ABAP statements.
By pressing F5 you Step Into this code to analyze it in detail. With F6 you Step
Over the code block, treating it like a single statement.
10. When the application is terminated, do not forget to switch back to the ABAP perspective
Task 1: Preparation
Before you can start debugging you have to create the program and copy the source code.
1. Create a new global class ZCL_##_DEBUG, where ## is your group number. Ensure that
the class implements the interface IF_OO_ADT_CLASSRUN.
a) Choose File → New → ABAP Class.
b) Enter the name of your package in the Package field. In the Name field, enter the name
ZCL_##_CDS, where ## is your group number. Enter a description.
d) Enter IF_OO_ADT_CLASSRUN. When the interface appears in the hit list, double-click it
to add it to the class definition.
e) Choose Next.
* Declarations
**********************************************************************
" Types
TYPES t_amount TYPE p LENGTH 8 DECIMALS 2.
TYPES t_percentage TYPE p LENGTH 2 DECIMALS 1.
" Output
DATA repayment_plan TYPE TABLE OF string.
* Processing
**********************************************************************
" Initializations
loan_remaining = loan_total.
CASE spec_repay_mode.
WHEN 'A'.
months_btw_spec_pay = 12.
special_repayment = spec_repay_year.
WHEN 'H'.
months_btw_spec_pay = 6.
special_repayment = spec_repay_year / 2.
WHEN 'Q'.
months_btw_spec_pay = 3.
special_repayment = spec_repay_year / 4.
WHEN OTHERS.
out->write( 'Invalid extra payment mode' ).
EXIT.
ENDCASE.
" Calculations
DO.
IF loan_remaining <= 0.
EXIT.
ENDIF.
DO months_btw_spec_pay TIMES.
months_counter = months_counter + 1.
IF loan_remaining < 0.
EXIT.
ENDIF.
ENDDO.
IF loan_remaining < 0.
EXIT.
ENDIF.
* Output
**********************************************************************
out->write( |-----------------------
Result-----------------------| ).
out->write( |Total repayment after { months_counter DIV 12 } years
and { months_counter MOD 12 } months. | ).
out->write( |Total interest paid: { interest_total } | ).
"Repayment Plan
out->write(
name = `Repayment Plan:`
data = repayment_plan
).
a) On the Global Class tab, insert the source code between METHOD
if_oo_adt_classrun~main. and ENDMETHOD..
b) If the Console view is not visible, open it by choosing Window → Show view → Other.
Double-click Console in the hit list.
1. Set a breakpoint at the first statement that does not define a type or declare a data object.
Hint:
TYPES defines a data type, CONSTANTS declares a constant data object,
DATA declares a variable data object.
a) Double-click the left-hand margin of the editor next to the line loan_remaining =
loan_total.to set a break point.
b) If you are asked whether you want to switch to the Debug perspective, mark
Remember my decision and choose OK.
3. Display the value of data object loan_remaining and loan_total in the Variables view.
a) In the current code line (the one with a green background) double-click
loan_remaining.
1. Execute a single step to debug the value assignment in the current line.
a) In the toolbar, choose Step Into (F5) or press F5.
b) Check that the the value of loan_remaining changed from 0..00to 5000.00.
2. Display the content of data object spec_repay_mode. Then execute another single step
to see which WHEN branch of the CASE - control structure is executed.
a) In the next code line, double-click spec_repay_mode.
b) Press F5 to see that the program jumps to code line WHEN 'Q'..
3. Set a watch point for variable loan_remaining and resume program execution. Where
does the program execution stop again?
a) In the Variables view, right-click on LOAN_REMAINING and choose Set Watchpoint.
4. Display the content of data object repayment_plan. Then execute another single step to
see how it is filled with the APPEND statement.
Note:
Because repayment_plan is an internal table, it not only displays in the
Variables view but also in the ABAP Internal Table (Debugger) view below the
editor.
5. Inspect the string template in the APPEND statement and relate it to the resulting first row
in internal table repayment_plan. Display the content of the data objects that appear in
the embedded expressions.
a) Double-click the data objects that appear between the curly brackets to display their
contents.
6. Resume program execution for a few times. Whenever execution reaches one of the watch
points, analyze the value of loan_remaining and new rows are added to
repayment_plan.
a) Press F8 to resume program execution.
b) Analyze the data objects in the Variables view and the ABAP Internal Table (Debugger)
view.
7. After a while, set a statement break point for all EXIT statements and delete the two watch
points.
a) Navigate to the Breakpoints view.
Hint:
You find the Breakpoints view next to the Variables view.
b) In the toolbar of the Breakpoints view, expand the dropdown button on the very left
and choose Add Statement Breakpoint ....
c) On the dialog window that appears, enter EXIT as search string, click on the value EXIT
in the hitlist, and choose OK.
8. Resume program execution until you reach the first EXIT statement. Deactivate the
statement breakpoint for the EXIT statement. Then execute single steps until you reach
the output part of the program.
a) Press F8 to resume program execution.
b) Switch to the Breakpoints view and deselect the line that says EXIT [Statement].
c) Press F5 until you reach the first code line that starts with out->write(.
9. Open the Console view. Execute the remaining program and pursue the output on the
console view.
Note:
Do not press F5 to debug the output. Use F6 instead.
Note:
As you will learn later in the course out->write( ... ) is not an ABAP
statement but a reusable code block that consists of many ABAP statements.
By pressing F5 you Step Into this code to analyze it in detail. With F6 you Step
Over the code block, treating it like a single statement.
a) Press F6 several times until you reach the end of the application.
b) Analyze the addition output on the Console view and compare it to the string template
in the previous code line.
10. When the application is terminated, do not forget to switch back to the ABAP perspective
a) Choose ABAP on the very right of the Eclipse toolbar.
LESSON SUMMARY
You should now be able to:
● Enter debugging mode
● Control the execution of code
● Analyze the content of data objects
Learning Assessment
X A C
X B D
X C P
X D I
3. You declare a variable as follows: DATA var TYPE I VALUE 100. Subsequently, you use the
statement CLEAR var. What is the value of var after the CLEAR statement?
Choose the correct answer.
X A 0
X B 100
4. The result of the expression result = var MOD 2. is 1. What does this tell you about the
value of variable var?
Choose the correct answer.
X A +
X B &
X C ++
X D &&
6. When you declare an internal table, you must specify how many rows it may contain.
Determine whether this statement is true or false.
X True
X False
X C A variable with the same type as the row type of the internal table
8. The IF condition IF a > 10. is followed by the ELSEIF condition ELSEIF a = 25.The variable a
has the value 25. Which code branch or branches are executed?
Choose the correct answer.
X C Both branches.
11. What information do you see when you position the mouse pointer over a variable in the
debugger?
Choose the correct answer.
12. When you press F8 (Continue) in the debugger, where could the program processing next
stop?
Choose the correct answers.
X B At a subsequent breakpoint
Lesson 1
Defining a local class 106
Exercise 4: Define a Local Class 111
Lesson 2
Creating Instances Of A Class 115
Exercise 5: Create and Manage Instances 121
Lesson 3
Defining And Calling Methods 128
Exercise 6: Define and Call Methods 139
Lesson 4
Using Encapsulation To Ensure Consistency 150
Exercise 7: Use Private Attributes and Constructors 157
UNIT OBJECTIVES
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Define a local class inside a global class
Global classes are stored centrally and are contained in their own repository object called a
class pool. A global class can serve as main program. Global classes can also contain logic to
be reused by other ABAP programs, including other global classes.
Local classes are defined as part of an ABAP program, for example a global class. You can use
them only in the program or class in which they are defined. Local classes are useful for
entities or functions that you only need in a single program.
The ABAP syntax of both local and global classes is almost identical. In this course, you will be
working with local classes in your global class. The global class with method
if_oo_adt_classrun~main will only serve as a kind of main program.
ADT provides a source code template for local class. To use this template proceed as follows:
2. From the list that displays choose lcl - Local class and press Enter.
For the classes in this course you have to remove the create private addition from the CLASS
… DEFINITION statement.
Declare Attributes
Source Code of a Class in ABAP
In ABAP. the source code of a class has two parts - the definition and the implementation. The
definition part of a class is subdivided into up to three sections, called the visibility sections of
the class.
Animation
For more information on this topic please view the animation in the lesson
Defining a local class in your online course.
Definition
The definition part of a class contains the definition and declaration of all of the elements
in the class, that is, the types, the constants, the attributes, and the methods. It begins
with CLASS <class_name> DEFINITION. and ends with ENDCLASS.
Implementation
The implementation part of a class contains the executable code of the class, namely the
implementation of its methods. It begins with CLASS <class_name> IMPLEMENTATION.
and ends with ENDCLASS. The implementation part of a class is optional. It becomes
mandatory as soon as the class definition contains executable methods.
Visibility sections
Each visibility section of a class starts with one of the statements PUBLIC SECTION,
PROTECTED SECTION, PRIVATE SECTION and ends implicitly when the next section
begins. The last section ends with statement ENDCLASS. All declarations of a class have
to be inside one of the sections. In other words: No declarations are allowed between the
beginning of the class definition and the beginning of the first section.
The section in which a declaration is located defines the visibility of the declared element
of the class.
The three visibility sections of a class are optional; if you do not need a particular section,
you do not have to declare it. But if a class definition consists of more than one section,
they must follow the order PUBLIC SECTION - PROTECTED SECTION - PRIVATE
SECTION.
The figure shows the different kinds of components you can define in a class. You can define
any of these components in any visibility section of the class.
Animation
For more information on this topic please view the animation in the lesson
Defining a local class in your online course.
TYPES
The TYPES statement allows you to define types within your class. You do this in exactly
the same way as you would outside a class. If types are public, a program that uses the
class can use the type (or types) to declare ist own variables.
DATA and CLASS-DATA
You use DATA and CLASS-DATA to declare the attributes of the class. DATA declares an
instance attribute, while CLASS-DATA declares a static attribute.
CONSTANTS
Classes may also contain constants which are declared with CONSTANTS. Constants
and types are static components of the class.
METHODS and CLASS-METHODS
You use METHODS and CLASS-METHODS to define the methods of a class. METHODS
defines an instance method, while CLASS-METHODS defines a static method. The name
of the method is followed by the method's signature; that is, the set of values that the
method exchanges with its caller and the exceptions that may arise during the method.
Let us start implementing our UML model in ABAP, and take the attributes of the connection
class to begin with. To declare an attribute, use the DATA statement within the appropriate
visibility section.
The attribute conn_counter is underlined, which denotes a static attribute. You declare static
attributes using the CLASS-DATA statement. The syntax of CLASS-DATA is identical to that
of DATA.
Note:
We begin with public attributes at this point. Later in the course, when we discuss
encapsulation, we will turn them into private attributes.
1. Create a new ABAP class called ZCL_##_LOCAL_CLASS, where ## is your group number.
Ensure that the class implements the interface IF_OO_ADT_CLASSRUN.
2. Create a new local class lcl_connection inside the global class. Use code completion to
generate the code.
Table 1: Attributes
Attribute Name Scope Data Type
carrier_id instance /DMO/CARRIER_ID
conn_counter static I
Note:
Because the if_oo_adt_classrun~main( ) method does not contain
executable code, yet, there is nothing to test or debug at this step.
1. Create a new ABAP class called ZCL_##_LOCAL_CLASS, where ## is your group number.
Ensure that the class implements the interface IF_OO_ADT_CLASSRUN.
a) Choose File → New → ABAP Class.
b) Enter the name of your package in the Package field. In the Name field, enter the name
ZCL_##_LOCAL_CLASS, where ## is your group number. Enter a description.
d) Enter IF_OO_ADT_CLASSRUN. When the interface appears in the hit list, double-click it
to add it to the class definition.
e) Choose Next.
2. Create a new local class lcl_connection inside the global class. Use code completion to
generate the code.
a) Switch to the Local Types tab.
d) While lcl is still highlighted in the line class lcl definition create private.,
complete the name of the class to lcl_connection. Then delete the words create
private.
Table 1: Attributes
Attribute Name Scope Data Type
carrier_id instance /DMO/CARRIER_ID
conn_counter static I
a) After line PUBLIC SECTION. and before line PROTECTED SECTION., add the
following three statements:
Note:
Because the if_oo_adt_classrun~main( ) method does not contain
executable code, yet, there is nothing to test or debug at this step.
LESSON SUMMARY
You should now be able to:
● Define a local class inside a global class
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Create Instances of an ABAP Class
Create Instances
You work with attributes like normal variables of the same type. Outside the class, however,
the attribute name is not sufficient to identify the attribute unambiguously. To address a
static attribute outside the class, first type the class name, then the static component
selector (=>), and only then the attribute name. The static component selector is a double
arrow made up of an equals sign and the greater than sign.
Hint:
No blanks are allowed before or after the component selector.
For instance attributes the situation is even more complicated: In order to access an instance
component, you need a reference variable.
A reference variable is a special kind of variable that you use to create, address, and manage
an object. A reference variable is used to point at the instance of a class in the program
memory. You declare reference variables using the DATA statement with the addition TYPE
REF TO followed by the name of a class.
The initial value of a reference variable is called the NULL reference; the reference does not
yet point anywhere.
To create a new instance of a class, you use the NEW operator. The example above, uses a
NEW #( ) expression on the right hand side of a value assignment. The result of the
expression is the memory address of the newly created instance. This reference is then
stored in the reference variable on the left-hand side of the assignment.
You may have noticed that the name of the class that you want to instantiate does not appear
anywhere in the expression. However, from the location of the NEW #( ) expression, the
system already knows that the target variable connection has the type REF TO lcl_connection,
and consequently it knows that it should create an instance of the class lcl_connection. The
pound sign after the NEW operator means "use the type of the variable before the equals
sign". (In more advanced scenarios, you can actually specify the name of the class in place of
the pound sign).
Hint:
There must be at least one blank between the brackets.
When you address a class for the first time (which could be accessing a static component or
creating an instance of the class), the runtime system also loads the class definition into the
program memory. This class definition contains all of the static attributes, which only exist
once in the class instead of once for each instance.
You address static components using the class name and the static component selector. This
does not work for instance components because you have to specify the instance you want to
access.
To address an instance attribute outside the class, first type the reference variable, then the
instance component selector (->), and only then the attribute name. The instance component
selector is an arrow made up of a dash and the greater than sign.
Note:
Unlike many other programming languages, ABAP uses different characters for
instance component selector and static component selector.
One of the main characteristics of object oriented programming is the fact that you can create
multiple instances of the same class. Each instance is created in a different place in the
program memory and the values of instance attributes in one instance are independent from
the values in other instances. But as the graphic illustrates, instances of the same class share
the value for the static attributes.
If you assign one reference variable to another, the system copies the address of the object to
which the first variable is pointing into the second reference variable. The result of this is that
you have two reference variables that point to the same object.
You can use the same reference variable to create more than one instance of a class. Each
time you use the NEW #( ) expression, the system creates a new instance of the class and
places the address of the new instance into the reference variable. However, the address of
the new instance overwrites the address of the previous instance.
In the example above, the address of lcl_connection (2) overwrites the address of
lcl_connection (1). Consequently, there is no longer a reference variable in the program
pointing to lcl_connection (1). When this happens to an instance, it can no longer be
addressed from the program.
To prevent the program memory from becoming filled with objects that can no longer be
addressed and eventually overflowing, the runtime system has a component called the
garbage collector. The garbage collector is a program that runs periodically to look for and
destroy objects to which no more references point. If during a program you delete the last
reference to an object by overwriting it or using the CLEAR statement, the garbage collector
will destroy the object on its next pass.
Note:
At the end of a program, when all of the reference variables are freed, the garbage
collector will destroy all of the instances to which they had pointed. You do not
have to worry about resource management in the program yourself.
One way in which you can keep objects alive is to place the references into an internal table.
This is a technique that you may well want to use if you are creating a whole series of objects.
It enables you to use a single reference variable to create lots of objects. Although the
reference variable is overwritten with the address of the next object, the existing objects are
safe because the internal table contains a reference to them. You therefore never delete the
"last" reference to the objects.
Class zcl_s4d400_class
Task 1: Preparation
1. If you finished the previous exercise Define a Local Class, create a copy of your global class
ZCL_##_LOCAL_CLASS, and name the copy ZCL_##_INSTANCES, where ## is your group
number. Then skip the rest of this task.
2. If you did not finish the previous exercise, create a new global class ZCL_##_INSTANCES,
where ## is your group number. Ensure that the class implements the interface
IF_OO_ADT_CLASSRUN.
PUBLIC SECTION.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
ENDCLASS.
2. Create an instance of the class and set the attributes carrier_id and connection_id
(Suggested values: “LH” for attribute carrier_id and “0400”for attribute
connection_id.
3. Activate the class and use the Debugger to analyze step by step what happens.
2. After instantiating the class and setting the attributes, append the object reference to the
internal table.
3. Create two more instances of class lcl_connection, set their instance attributes to
different values as in the first instance and append the references to the new instances to
internal table connections. Use the same reference variable connection for all three
instances.
4. Activate the class and use the Debugger to analyze step by step what happens.
Task 1: Preparation
1. If you finished the previous exercise Define a Local Class, create a copy of your global class
ZCL_##_LOCAL_CLASS, and name the copy ZCL_##_INSTANCES, where ## is your group
number. Then skip the rest of this task.
a) In the Project Explorer view on the left, expand your package.
c) Right-click the name of the class you want to copy and choose Duplicate.
d) In the Name field, enter the name ZCL_##_INSTANCES, where ## is your group
number.
e) Choose Next.
2. If you did not finish the previous exercise, create a new global class ZCL_##_INSTANCES,
where ## is your group number. Ensure that the class implements the interface
IF_OO_ADT_CLASSRUN.
a) Choose File → New → ABAP Class.
b) Enter the name of your package in the Package field. In the Name field, enter the name
ZCL_##_INSTANCES, where ## is your group number. Enter a description.
d) Enter IF_OO_ADT_CLASSRUN. When the interface appears in the hit list, double-click it
to add it to the class definition.
e) Choose Next.
PUBLIC SECTION.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
ENDCLASS.
a) Navigate to the Local Types tab and insert the source code.
2. Create an instance of the class and set the attributes carrier_id and connection_id
(Suggested values: “LH” for attribute carrier_id and “0400”for attribute
connection_id.
a) Add the following code to your class after the DATA statement:
connection = new #( ).
connection->carrier_id = 'LH'.
connection->connection_id = '0400'.
Hint:
After typing the component selector (->), press Strg + Space to choose
the attribute names from a suggestion list.
3. Activate the class and use the Debugger to analyze step by step what happens.
a) Press Ctrl + F3 to activate the class.
b) Double-click the left-hand margin of the editor next to the line connection = new
#( ).to set a break point.
e) Press F5 to go one step further in the debugger. Check that the value of connection
has changed.
f) In the Variables view, expand the branch connection to display the initial attributes of
the class.
g) Press F5 to go one step further in the debugger. Check that the value of carrier_id
has changed.
2. After instantiating the class and setting the attributes, append the object reference to the
internal table.
a) Change your code so that it looks like this:
connection = NEW #( ).
connection->carrier_id = 'LH'.
connection->connection_id = '0400'.
3. Create two more instances of class lcl_connection, set their instance attributes to
different values as in the first instance and append the references to the new instances to
internal table connections. Use the same reference variable connection for all three
instances.
a) Before ENDMETHOD., add the following code:
connection->carrier_id = 'AA'.
connection->connection_id = '0017'.
connection = NEW #( ).
connection->carrier_id = 'SQ'.
connection->connection_id = '0001'.
4. Activate the class and use the Debugger to analyze step by step what happens.
a) Press Ctrl + F3 to activate the class.
b) Double-click the left-hand margin of the editor next to the first line connection =
new #( ).to remove the break point.
c) Double-click the left-hand margin of the editor next to the first line APPEND
connection TO connections.to set a new break point.
g) Press F5 to go one step further in the debugger. Check that the content of
connections has changed.
i) Press F5 several times until you reach the end of the program. While you do so, check
the content of the internal table.
LESSON SUMMARY
You should now be able to:
● Create Instances of an ABAP Class
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Define and call methods
Defining Methods
In the definition part of a class you use METHODS to define an instance method and CLASS-
METHODS to define a static method. The name of the method is followed by the method's
signature; that is, the set of values that the method exchanges with its caller and the
exceptions that may arise during the method.
The signature of a method consists of parameters and exceptions. Each parameter has a
name and a type.
ABAP knows the following kinds of parameters:
Importing Parameters
Importing parameters are values that the method receives from the caller. A method can
have any number of importing parameters.
By default, importing parameters are mandatory, but there are two ways to make them
optional:
● Using the OPTIONAL addition. The parameter is optional and its default value is the
initial value appropriate to the type of the parameter
● Using the DEFAULT <val> addition. The parameter is optional and its default value is
the value that you specified as <val>.
Inside of a method, you may not change importing parameters. If you attempt to do so,
you will cause a syntax error.
Exporting Parameters
Exporting parameters are results that are returned by the method. A method can have
any number of exporting parameters. All exporting parameters are optional - a calling
program only uses the values that it actually needs.
Changing Parameters
Changing parameters are values that the method receives from the caller. Unlike
importing parameters, the method can change the values of these parameters. They are
then returned to the caller under the same name. A method can have any number of
changing parameters. Changing parameters are mandatory by default; you can make
them optional in the same way as importing parameters.
Returning Parameters
A returning parameter is a method result that can be used directly in an expression. A
method can only have one returning parameter. Returning parameters have to use a
special form of parameter passing which is called pass-by-value. This form of parameter
passing is defined by surrounding the parameter name in brackets (no blanks!) and
preceding it with keyword VALUE.
Keyword RAISING is used to list the exceptions the method might raise to indicate an error
situation. The calling program can then react to the error.
Animation
For more information on this topic please view the animation in the lesson
Defining And Calling Methods in your online course.
Hint:
ADT offers a quickfix to add the missing implementation. To use this quick fix,
proceed as follows:
1. Position the cursor in a METHODS statement with an error and press Ctrl +
1.
2. From the list of possible quick fixes choose Add implementation for … and
press Enter . If the implementation is missing for several methods, the
quickfix title reads Add … unimplemented methods.
Implementing Methods
You must implement every method that you define. You do this in the implementation part of
the class, by adding a pair of statements METHOD <method_name> and ENDMETHOD.
The method implementation contains ABAP statements that can access the parameters of
the method (you are not allowed to change importing parameters) and all types, attributes,
and constants that you declared in the class, independent from their visibility. Instance
methods may access both instance attributes and static attributes. Static methods may only
access static components.
Inside the implementation part of a class, you can access the attributes of that class without a
reference variable and '->' (or the class name and '=>' , in case of static attributes).
Only in the implementation of instance methods, ABAP offers built-in variable ME. ME is a
reference variable, typed with the current class and filled at runtime with the address of the
current instance. The use of ME is optional and should be avoided, unless the name of an
attribute collides with the name of another data object, for example a parameter name.
In the example, the import parameters of method set_attributes( ) have the same names as
the corresponding attributes. In such a situation, the name by itself denotes the parameter.
Only by putting me-> in front it becomes clear that an attribute of the current instance is to be
accessed.
If an error occurs during the execution of a method, the method can trigger an exception
using statement RAISE EXCEPTION TYPE, followed by the name of the exception.
Note:
Technically, exception names are the names of special ABAP classes and
statement RAISE EXCEPTION TYPE creates an instance of the referenced class.
The instance is referred to as exception object and the special kind of ABAP
classes are called exception classes.
Note:
For some exceptions, the ABAP editor displays a syntax warning with a quick fix
when you raise an exception that is not yet declared in the RAISING clause of the
method.
Calling Methods
You call an instance method using a reference variable and the instance component selector
(->). The component selector is followed by the name of the method you want to call. For
static methods you use the class name and the static component selector (=>). On both
cases, the parameter passing takes place in a pair of brackets. The brackets are needed in any
case. They remain empty if no parameters are to be passed.
Note:
No blank is allowed between the method name and the opening bracket. On the
other hand you need at least one blank after the opening bracket and before the
closing bracket.
Importing parameters are listed after keyword EXPORTING because the values that are
imported by the called method are exported by the calling program. The parameter names
are listed on the left-hand side of an equals sign (=) and an expression on the right-hand side..
This expression can be as simple as a literal, constant, or variable but any other expressions
are also allowed as long as the type of the expression matches the type of the parameter.
Exporting parameters are listed after keyword IMPORTING. Note, that for exporting
parameter the parameter name is also listed on the left-hand side of the equals sign. The
variable on the right-hand side has to have the same type as the parameter.
Changing parameters are listed after keyword CHANGING. The type of the variable on the
right-hand side has to match the type of the parameter on the left hand side.
The example illustrates the call of instance methods set_attributes( ) and get_attributes( ).
The parameter names are always on the left-hand side of the equals sign (=).
Hint:
You can use code completion in ADT to generate the method call including the
parameter passing. To do so, proceed as follows:
1. Type in the reference variable (or class name) and the component selector.
2. Once you typed the component selector press Ctrl + Space to display a
list of components you can address.
3. If you choose a method, and press Shift + Enter to insert the name of the
method and its full signature into your code. Optional parameters are listed in
comment lines.
When calling a method, keyword IMPORTING is always needed to receive the value of
exporting parameters. But keyword EXPORTING becomes optional, if you only want to supply
importing parameters.
For methods with only one mandatory importing parameter, you can even omit the explicit
parameter assignment. In the example, parameter i_carrier_id is optional with default value
'LH'. Therefore the value between the brackets is assigned to the remaining mandatory
importing parameter i_connection_id.
Raising Exceptions
In a previous section of this class, you learned how to handle runtime errors with TRY …
ENDTRY control structures.
Exceptions declared in the RAISING clause of a method definition are handled in exactly the
same way:
● Place the method call in the TRY-block of the TRY … ENDTRY control structure.
● Define a CATCH block with the name of the exception you want to handle.
Note:
If there is code that you want to skip in case the method call failed, you can place
this code inside the try block, after the method call.
* Create Instance
**********************************************************************
connection = NEW #( ).
TRY.
connection->set_attributes(
EXPORTING
i_carrier_id = c_carrier_id
i_connection_id = c_connection_id
).
3. Navigate to tab Local Typesand insert the following code snippet there:
* Attributes
DATA carrier_id TYPE /dmo/carrier_id.
DATA connection_id TYPE /dmo/connection_id.
* Methods
METHODS set_attributes
IMPORTING
i_carrier_id TYPE /dmo/carrier_id DEFAULT 'LH'
i_Connection_id TYPE /dmo/connection_id
RAISING
cx_abap_invalid_value.
ENDCLASS.
METHOD set_attributes.
carrier_id = i_carrier_id.
connection_id = i_connection_id.
ENDMETHOD.
ENDCLASS.
5. Analyze the console output. Debug the program, play around with the source code to get
familiar with the concepts. In particular, change the values of the two constants to debug
the raising of the exception.
Functional Methods
Methods that have a returning parameter are called functional methods. It is not possible to
define more than one returning parameter for the same method. It is mandatory that you
define the returning parameter in the form VALUE(<parameter_name>). Besides the
returning parameter a functional method can have any combination of other parameters. It is
most common however, to add importing parameters only. The reason is, that with additional
exporting or changing parameters you loose the biggest advantage of functional methods,
namely, that you can use the result of a functional method directly in other ABAP expressions.
The figure shows two examples of how you can use functional methods in expressions; the
first is a simple example in which the returning parameter is assigned directly to a variable.
The second example shows how the call of the functional method is used as input for another
method. The system executes the functional method, and then uses the value of the returning
parameter as input for method write( ).
* Create Instance
**********************************************************************
connection = NEW #( ).
connection->set_attributes(
EXPORTING
i_carrier_id = 'LH'
i_connection_id = '0400'
).
ENDLOOP.
ENDIF.
3. Navigate to tab Local Typesand insert the following code snippet there:
* Attributes
DATA carrier_id TYPE /dmo/carrier_id.
DATA connection_id TYPE /dmo/connection_id.
* Methods
METHODS set_attributes
IMPORTING
i_carrier_id TYPE /dmo/carrier_id DEFAULT 'LH'
i_Connection_id TYPE /dmo/connection_id.
* PROTECTED SECTION.
* PRIVATE SECTION.
ENDCLASS.
METHOD set_attributes.
carrier_id = i_carrier_id.
connection_id = i_connection_id.
ENDMETHOD.
METHOD get_output.
ENDMETHOD.
ENDCLASS.
5. Analyze the console output. Debug the program, play around with the source code to get
familiar with the concepts. In particular, debug the different calls of functional method
get_output( )..
Task 1: Preparation
1. If you finished the previous exercise Create and Manage Instances, create a copy of your
global class ZCL_##_INSTANCES, and name the copy ZCL_##_METHODS, where ## is your
group number. Then skip the rest of this task.
2. If you did not finish the previous exercise, create a new global class ZCL_##_METHODS,
where ## is your group number. Ensure that the class implements the interface
IF_OO_ADT_CLASSRUN.
* First Instance
**********************************************************************
connection = NEW #( ).
connection->carrier_id = 'LH'.
connection->connection_id = '0400'.
* Second Instance
**********************************************************************
connection = NEW #( ).
connection->carrier_id = 'AA'.
connection->connection_id = '0017'.
* Third Instance
**********************************************************************
connection = NEW #( ).
connection->carrier_id = 'SQ'.
connection->connection_id = '0001'.
PUBLIC SECTION.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
ENDCLASS.
2. Add a method get_output to the public section of the class. It should have one returning
parameter r_outputof global table typeSTRING_TABLE.
Note:
Remember to surround the name of the returning parameter with VALUE( ).
3. Add a method set_attributes to the public section of the class. It should have one
importing parameter for each instance attribute of the class. Use the same types for the
parameters that you used to type the attributes. To distinguish the parameters from the
attributes, add prefix i_ to the parameter names. In addition, the method should raise
exception CX_ABAP_INVALID_VALUE.
2. At the end of the method, add a loop over internal table connectionswith reference
variable connection as work area.
3. In the loop, call functional method get_output( ) for each instance in turn and write the
result to the console.
Hint:
You can use the call of method get_output( ) directly as input for out-
>write( ).
4. Activate the class. Execute it as console app and analyze the console output.
1. For the first instance of class lcl_connection, replace the direct access to attributes
carrier_id and connection_idwith a call of instance method set_attributes( ).
Use code completion to insert the full signature of the method.
Hint:
Press Ctrl + Space after you typed the component select (->) to display a
list of available attributes and methods. Choose the method and then press
Shift + Enter to insert the full signature of the method.
2. Handle the exception. Surround the method call with TRY. and ENDTRY.. Add a CATCH-
Block to handle exception CX_ABAP_INVALID_VALUE. Make sure the new instance is only
added to internal table connections if the method call was successful.
3. Repeat the previous step for the other two instances of class lcl_connection.
Task 1: Preparation
1. If you finished the previous exercise Create and Manage Instances, create a copy of your
global class ZCL_##_INSTANCES, and name the copy ZCL_##_METHODS, where ## is your
group number. Then skip the rest of this task.
a) In the Project Explorer view on the left, expand your package.
c) Right-click the name of the class you want to copy and choose Duplicate.
d) In the Name field, enter the name ZCL_##_METHODS, where ## is your group number.
e) Choose Next.
2. If you did not finish the previous exercise, create a new global class ZCL_##_METHODS,
where ## is your group number. Ensure that the class implements the interface
IF_OO_ADT_CLASSRUN.
a) Choose File → New → ABAP Class.
b) Enter the name of your package in the Package field. In the Name field, enter the name
ZCL_##_METHODS, where ## is your group number. Enter a description.
d) Enter IF_OO_ADT_CLASSRUN. When the interface appears in the hit list, double-click it
to add it to the class definition.
e) Choose Next.
* First Instance
**********************************************************************
connection = NEW #( ).
connection->carrier_id = 'LH'.
connection->connection_id = '0400'.
* Second Instance
**********************************************************************
connection = NEW #( ).
connection->carrier_id = 'AA'.
connection->connection_id = '0017'.
* Third Instance
**********************************************************************
connection = NEW #( ).
connection->carrier_id = 'SQ'.
connection->connection_id = '0001'.
a) Navigate to the Global Class tab and insert the source code between METHOD
if_oo_adt_classrun~main. and ENDMETHOD..
PUBLIC SECTION.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
ENDCLASS.
a) Navigate to the Local Types tab and insert the source code.
2. Add a method get_output to the public section of the class. It should have one returning
parameter r_outputof global table typeSTRING_TABLE.
Note:
Remember to surround the name of the returning parameter with VALUE( ).
METHODS get_output
RETURNING
VALUE(r_output) TYPE STRING_TABLE.
Note:
For the moment, ignore syntax error Implementation missing for method
"GET_OUTPUT".
3. Add a method set_attributes to the public section of the class. It should have one
importing parameter for each instance attribute of the class. Use the same types for the
parameters that you used to type the attributes. To distinguish the parameters from the
attributes, add prefix i_ to the parameter names. In addition, the method should raise
exception CX_ABAP_INVALID_VALUE.
a) Enter the following code before the PROTECTED SECTION statement:
METHODS set_attributes
IMPORTING
i_carrier_id TYPE /dmo/carrier_id
i_connection_id TYPE /dmo/connection_id
RAISING
cx_abap_invalid_value.
Note:
For the moment, ignore syntax error Implementation missing for method
"SET_ATTRIBUTES".
carrier_id = i_carrier_id.
connection_id = i_connection_id.
2. At the end of the method, add a loop over internal table connectionswith reference
variable connection as work area.
a) Add the following code before ENDMETHOD..
ENDLOOP.
3. In the loop, call functional method get_output( ) for each instance in turn and write the
result to the console.
Hint:
You can use the call of method get_output( ) directly as input for out-
>write( ).
a) Add the following code between LOOP AT connections INTO connection and
ENDLOOP.
out->write( connection->get_output( ) ).
ENDLOOP.
4. Activate the class. Execute it as console app and analyze the console output.
a) Press Ctrl + F3 to activate the class.
1. For the first instance of class lcl_connection, replace the direct access to attributes
carrier_id and connection_idwith a call of instance method set_attributes( ).
Use code completion to insert the full signature of the method.
Hint:
Press Ctrl + Space after you typed the component select (->) to display a
list of available attributes and methods. Choose the method and then press
Shift + Enter to insert the full signature of the method.
a) Place the cursor in the next line after the first connection = NEW #( )..
d) Pass values to the importing parameters. Use the same literals that you previously
used to set the attributes of this instance.
e) Remove or comment the direct access to the instance attributes for this instance.
2. Handle the exception. Surround the method call with TRY. and ENDTRY.. Add a CATCH-
Block to handle exception CX_ABAP_INVALID_VALUE. Make sure the new instance is only
added to internal table connections if the method call was successful.
a) Uncomment the generated code line CATCH cx_abap_invalid_value. .
d) Between the CATCH statement and the ENDTRY statement add out-
>write( `Method call failed` ).
e) Move the APPEND statement up to between the method call and CATCH statement.
TRY.
connection->set_attributes(
EXPORTING
i_carrier_id = 'LH'
i_connection_id = '0400'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* connection->carrier_id = 'LH'.
* connection->connection_id = '0400'.
* APPEND connection TO connections.
3. Repeat the previous step for the other two instances of class lcl_connection.
* First Instance
**********************************************************************
connection = NEW #( ).
TRY.
connection->set_attributes(
EXPORTING
i_carrier_id = 'LH'
i_connection_id = '0400'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* connection->carrier_id = 'LH'.
* connection->connection_id = '0400'.
* APPEND connection TO connections.
* Second instance
**********************************************************************
connection = NEW #( ).
TRY.
connection->set_attributes(
EXPORTING
i_carrier_id = 'AA'
i_connection_id = '0017'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* connection->carrier_id = 'AA'.
* connection->connection_id = '0017'.
* APPEND connection TO connections.
* Third instance
**********************************************************************
connection = NEW #( ).
TRY.
connection->set_attributes(
EXPORTING
i_carrier_id = 'SQ'
i_connection_id = '0001'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* connection->carrier_id = 'SQ'.
* connection->connection_id = '0001'.
*
* APPEND connection TO connections.
* Output
**********************************************************************
out->write( connection->get_output( ) ).
ENDLOOP.
ENDMETHOD.
LESSON SUMMARY
You should now be able to:
● Define and call methods
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Explain Encapsulation
● Define and Use Constructors
Data Encapsulation
ensures that no other point in the program can bypass the check of the consistency - either
knowingly or unknowingly. This is one of the major advantages of object-orientation.
Note:
In object oriented programming, it is recommended to use data encapsulation as
much as possible!
With the public attributes we defined by now, it was possible to read and change the values of
the attributes anywhere inside and outside the class.
In order to restrict access to the attributes you have two options:
1. Keep the DATA statement or CLASS-DATA statement in the public section and add
addition READ-ONLY. In doing so, write access to the attribute is forbidden outside of the
class, but read access is still possible.
Note:
Addition READ-ONLY is only allowed in the public section of a class.
2. Move the DATA statement or CLASS-DATA statement from the public section to one of
the other visibility sections, for example the private section. In doing so, read access and
write access to the attribute is forbidden outside of the class.
Hint:
ADT offers a quick fix to change the visibility of an attribute. To use it place
the cursor in the attribute name, press Ctrl + 1, and choose Make
<attribute> private.
By making your attributes private - or read-only, at least - you can ensure that the client
program uses the available set_attributes( ) method to set the values for attributes carrier_id
and connection_id.
But there is still potential for inconsistencies:
● You cannot force the program to call set_attributes( ) for each new instance. As a result,
there can be instances with initial values for carrier_id and connection_id.
● The client program can call set_attributes( ) several times for the same instance. This
should also not be possible.
What you need is a technique to enforce non-initial values during instantiation and to prevent
later changes.
To solve these problems, object-oriented programming languages use constructor methods.
The runtime system calls the constructor automatically when you create a new instance of
the class, but you may not call it explicitly. Thus the constructor is guaranteed to run once and
once only for each instance that you create.
Note:
In ABAP, it is not possible to define more than one constructor method in the
same class.
Hint:
In ADT, you can use a quick fix to generate a constructor method. To use this
quick fix proceed as follows:
1. Either in the definition or implementation part, place the cursor in the class
name and press Ctrl + 1.
3. On the dialog window that appears, select the attributes you want to initialize
with the constructor and choose Finish
Once you generated the constructor you can adjust its definition and
implementation to your needs.
The example shows the constructor of class lcl_connection. The generated definition contains
an importing parameter for each of the attributes carrier_id and connection_id. The
generated parameters have the same names as the related attributes.
The generated part of the implementation contains a value assignment for each of the
attributes with the related importing parameter on the right. Self-reference me-> is needed to
distinguish the attributes from the parameters of identical name.
The example shows some manual additions to the generated constructor of class
lcl_connection.
In order to reject the creation of instances with initial values, a consistency check was added
to the implementation and a RAISING clause to the definition of the constructor.
Because the constructor is executed once and only once for each new instance of class
lcl_connection, the constructor implementation is the perfect place to increment static
attribute conn_counter.
When you instantiate a class that has a constructor, the system calls the constructor method
automatically. If the constructor has importing parameters, you pass them in the NEW
expression exactly as you would pass parameters to a normal method.
Note:
A constructor can have only importing parameters. Keyword EXPORTING is
neither needed nor allowed in the NEW expression.
Note:
If the constructor has exceptions, you must ensure that you catch them by
enclosing the instantiation in a TRY… CATCH...ENDTRY block. If an constructor
raises an exception, control returns immediately to the program containing the
NEW expression. In this case, you do not receive a new instance of the class.
While the instance constructor is called once when each instance of a class is created, you will
sometimes need to perform actions once only for the entire class. For this purpose ABAP
allows you to define a static constructor, also known as class constructor.
The runtime system calls the static constructor once only when the class is addressed for the
first time during the execution of a program.
The first addressing of a class could be one of the following:
● First instantiation of the class
● First call of a static method
● First access to a public static attribute
Note:
This list is not complete. There are other actions (related to inheritance) that can
cause a class to be addressed for the first time.
A typical use case for the static constructor is the dynamic initialization of static attributes
with non-initial values. Therefore it is important that the runtime calls the static constructor
before creating the instance, calling the static method, or addressing the static attribute.
From a syntax perspective a constructor method has following properties:
● A public static method with the reserved name class_constructor
● Without parameters or exceptions
Note:
A static constructor must not have a signature because it is impossible to tell
exactly when a class will be addressed for the first time.
Hint:
In ADT, you can use a quick fix to generate a class constructor method. To use
this quick fix proceed as follows:
1. Either in the definition or implementation part, place the cursor in the class
name and press Ctrl + 1.
2. From the list of available quick fixes, choose Generate class constructor
Task 1: Preparation
1. If you finished the previous exercise Define and Implement Methods, create a copy of your
global class ZCL_##_METHODS, and name the copy ZCL_##_CONSTRUCTORS, where ## is
your group number. Then skip the rest of this task.
2. If you did not finish the previous exercise, create a new global class
ZCL_##_CONSTRUCTORS, where ## is your group number. Ensure that the class
implements the interface IF_OO_ADT_CLASSRUN.
* First Instance
**********************************************************************
connection = NEW #( ).
TRY.
connection->set_attributes(
EXPORTING
i_carrier_id = 'LH'
i_connection_id = '0400'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* connection->carrier_id = 'LH'.
* connection->connection_id = '0400'.
* APPEND connection TO connections.
* Second instance
**********************************************************************
connection = NEW #( ).
TRY.
connection->set_attributes(
EXPORTING
i_carrier_id = 'AA'
i_connection_id = '0017'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* connection->carrier_id = 'AA'.
* connection->connection_id = '0017'.
* APPEND connection TO connections.
* Third instance
**********************************************************************
connection = NEW #( ).
TRY.
connection->set_attributes(
EXPORTING
i_carrier_id = 'SQ'
i_connection_id = '0001'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* connection->carrier_id = 'SQ'.
* connection->connection_id = '0001'.
*
* APPEND connection TO connections.
* Output
**********************************************************************
out->write( connection->get_output( ) ).
ENDLOOP.
ENDMETHOD.
PUBLIC SECTION.
METHODS set_attributes
IMPORTING
i_carrier_id TYPE /dmo/carrier_id
i_connection_id TYPE /dmo/connection_id
RAISING
cx_abap_invalid_value.
METHODS get_output
returning
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
METHOD get_output.
ENDMETHOD.
METHOD set_attributes.
IF i_carrier_id IS INITIAL OR i_connection_id IS INITIAL.
RAISE EXCEPTION TYPE cx_abap_invalid_value.
ENDIF.
carrier_id = i_carrier_id.
connection_id = i_connection_id.
ENDMETHOD.
ENDCLASS.
2. Add an instance constructor to the local class lcl_connection using a quick fix. Ensure
that the constructor has importing parameters corresponding to attributes carrier_id
and connection_id.
Hint:
You can copy the parameter passing from the call of method
set_attributes( ) for this instance.
4. Move the instance creation into the TRY block of the exception handling.
5. Repeat the previous steps for the other instances of your local class.
Task 1: Preparation
1. If you finished the previous exercise Define and Implement Methods, create a copy of your
global class ZCL_##_METHODS, and name the copy ZCL_##_CONSTRUCTORS, where ## is
your group number. Then skip the rest of this task.
a) In the Project Explorer view on the left, expand your package.
c) Right-click the name of the class you want to copy and choose Duplicate.
d) In the Name field, enter the name ZCL_##_CONSTRUCTORS, where ## is your group
number.
e) Choose Next.
2. If you did not finish the previous exercise, create a new global class
ZCL_##_CONSTRUCTORS, where ## is your group number. Ensure that the class
implements the interface IF_OO_ADT_CLASSRUN.
a) Choose File → New → ABAP Class.
b) Enter the name of your package in the Package field. In the Name field, enter the name
ZCL_##_CONSTRUCTORS, where ## is your group number. Enter a description.
d) Enter IF_OO_ADT_CLASSRUN. When the interface appears in the hit list, double-click it
to add it to the class definition.
e) Choose Next.
* First Instance
**********************************************************************
connection = NEW #( ).
TRY.
connection->set_attributes(
EXPORTING
i_carrier_id = 'LH'
i_connection_id = '0400'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* connection->carrier_id = 'LH'.
* connection->connection_id = '0400'.
* APPEND connection TO connections.
* Second instance
**********************************************************************
connection = NEW #( ).
TRY.
connection->set_attributes(
EXPORTING
i_carrier_id = 'AA'
i_connection_id = '0017'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* connection->carrier_id = 'AA'.
* connection->connection_id = '0017'.
* APPEND connection TO connections.
* Third instance
**********************************************************************
connection = NEW #( ).
TRY.
connection->set_attributes(
EXPORTING
i_carrier_id = 'SQ'
i_connection_id = '0001'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* connection->carrier_id = 'SQ'.
* connection->connection_id = '0001'.
*
* APPEND connection TO connections.
* Output
**********************************************************************
out->write( connection->get_output( ) ).
ENDLOOP.
ENDMETHOD.
a) Navigate to the Global Class tab and insert the source code between METHOD
if_oo_adt_classrun~main. and ENDMETHOD..
PUBLIC SECTION.
METHODS set_attributes
IMPORTING
i_carrier_id TYPE /dmo/carrier_id
i_connection_id TYPE /dmo/connection_id
RAISING
cx_abap_invalid_value.
METHODS get_output
returning
value(r_output) type string_table.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
METHOD get_output.
ENDMETHOD.
METHOD set_attributes.
IF i_carrier_id IS INITIAL OR i_connection_id IS INITIAL.
RAISE EXCEPTION TYPE cx_abap_invalid_value.
ENDIF.
carrier_id = i_carrier_id.
connection_id = i_connection_id.
ENDMETHOD.
ENDCLASS.
a) Navigate to the Local Types tab and insert the source code.
d) Check that the declaration of attribute carrier_id has moved to the private section.
d) Check that the declaration of attribute connection_id has moved to the private
section.
b) Select all lines that belong to the METHODS set_attributes statement, including the
line with the period sign (.).
c) Press Ctrl + < to add a star sign (*) in front of each selected line.
2. Add an instance constructor to the local class lcl_connection using a quick fix. Ensure
that the constructor has importing parameters corresponding to attributes carrier_id
and connection_id.
a) Place the cursor on the name of the class and press Ctrl + 1.
c) In the dialog box, ensure that carrier_id and connection_id are selected and
choose Finish.
b) At the end of the METHODS statement, before the period sign, add RAISING
CX_ABAP_INVALID_VALUE .
b) After METHOD constructor, before the generated value assignments, add the
following code.
Hint:
You can copy the parameter passing from the call of method
set_attributes( ) for this instance.
connection = NEW #(
i_carrier_id = 'LH'
i_connection_id = '0400'
).
b) Press Ctrl + < to add a star sign (*) in front of each selected line.
4. Move the instance creation into the TRY block of the exception handling.
a) Move the TRY. statement up, to before the instance creation.
TRY.
connection = NEW #(
i_carrier_id = 'LH'
i_connection_id = '0400'
).
* connection->set_attributes(
* EXPORTING
* i_carrier_id = 'LH'
* i_connection_id = '0400'
* ).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
5. Repeat the previous steps for the other instances of your local class.
a) The implementation of method if_oo_adt_classrun~main( ) should then look like
this:
METHOD if_oo_adt_classrun~main.
* First Instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'LH'
i_connection_id = '0400'
).
* connection->set_attributes(
* EXPORTING
* i_carrier_id = 'LH'
* i_connection_id = '0400'
* ).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Second instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'AA'
i_connection_id = '0017'
).
APPEND connection TO connections.
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Third instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'SQ'
i_connection_id = '0001'
).
APPEND connection TO connections.
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Output
**********************************************************************
LOOP AT connections INTO connection.
out->write( connection->get_output( ) ).
ENDLOOP.
ENDMETHOD.
LESSON SUMMARY
You should now be able to:
● Explain Encapsulation
● Define and Use Constructors
Learning Assessment
X C It doesn't matter.
2. You have defined a class containing instance attributes and static attributes. You have
also declared a reference variable but not yet created a new instance of the class. Which
components of the class can you access at this point, and how?
Choose the correct answers.
3. You have declared two reference variables, ref1 and ref2. ref1 points to an object. You now
execute the statement ref2 = ref1. What happens?
Choose the correct answer.
X A The ABAP system creates a copy of the object to which ref1 is pointing and assigns
its address to ref2. There are now two objects.
X B The ABAP system assigns the address of the object to which ref1is pointing to
reference variable ref2. There is only one object.
4. A class my_class contains the public static method my_method. What is the correct code
to call this method?
Choose the correct answer.
X A my_class->my_method().
X B my_class=>my_method().
X C my_class->my_method( ).
X D my_class=>my_method( ).
X C No changing parameters
6. Your class contains a public instance attribute attr. How could you ensure that its value
can only be changed within the class?
Choose the correct answers.
X A Importing parameters
X B Exporting parameters
X C Changing parameters
X D Exceptions
8. The static constructor is executed when the class is addressed for the first time. When
might this be?
Choose the correct answers.
Lesson 1
Investigating a Table Definition 175
Lesson 2
Implementing Basic SELECT Statements 179
Lesson 3
Working with CDS View 187
Exercise 8: Analyze and Use a CDS View Entity 193
UNIT OBJECTIVES
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Investigate a table definition
Note:
Newer ABAP releases only support SAP HANA as database. The ABAP Environment on SAP
BTP only runs on SAP HANA.
A sequence of columns at the beginning of each database table forms its key. The key is a
combination of values that ensures that each row in the table can be identified uniquely.
In SAP systems, database table definitions are development objects, and as such, are cross-
client. However, the vast majority of tables contain business data, which is client-specific. To
keep the data separate, client-specific tables have a client field (often named CLIENT or
MANDT) as their first key field. The database of SAP accesses statements using ABAP SQL to
ensure that a statement only manipulates data from the current client.
Animation
For more information on this topic please view the animation in the lesson
Investigating a Table Definition in your online course.
The main part of the definition consists of a DEFINE TABLE statement with the name of the
table. This is followed by the list of table fields in a pair of curly brackets ( { , } ); each field with
a type that is described by a data element. A subset of fields at the beginning of the list
defines the key, which identifies each record uniquely.
Several additional code lines before the DEFINE TABLE statement specify additional
properties of the database table, among them a label.
You can use the Data Preview tool to display and analyze the content of a database table. To
open the Data Preview for a given table, right-click anywhere in the table definition and choose
Open With → Data Preview. Alternatively, place the cursor anywhere in the database table
definition and press F8.
The tool displays the data stored in the database table. Some of the most important functions
are the following:
The tool displays the data stored in the database table. Some of the most important functions
are shown here.
Select Columns
For tables with many columns, you can adjust the columns you want to display.
Change Column Sequence
You can change the column sequence by dragging and dropping a column header to
another position.
Apply Sorting
You can apply a sorting by left-clicking on a column header.
Add Filter
For tables with many rows you can filter the data by choosing Add Filter.
Refresh Display
Choose the Refresh button to reload the data.
Number of Entries
Choose Number of Entries if you are only interested in the number of rows that meet your
filter conditions. Without filters, the total number of entries is displayed.
Animation
For more information on this topic please view the animation in the lesson
Investigating a Table Definition in your online course.
LESSON SUMMARY
You should now be able to:
● Investigate a table definition
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Describe basic features of ABAP SQL
● Read single values from the database
All relational database systems use a variant of Structured Query Language (SQL) to allow
you to work with them. Standard SQL consists of three main components:
Data Control Language is used in SQL to restrict access to data in the database for a
particular user. It is not used in its classic form in ABAP, since the users at database level
do not correspond one-to-one with the end users. Consequently, ABAP has its own
authorization concept.
Animation
For more information on this topic please view the animation in the lesson
Implementing Basic SELECT Statements in your online course.
In the past, SAP systems had to support a range of database platforms, and each platform
had a slightly different implementation of the SQL standard. This meant that each platform
needed slightly different commands to achieve a particular task. To avoid the ABAP code
being database-specific, SAP invented ABAP SQL - or Open SQL as it was called originally.
Note:
The name change from Open SQL and ABAP SQL also illustrates that as off
release 7.53 ABAP only supports SAP HANA as DBMS.
ABAP SQL is an abstract set of SQL commands implemented at ABAP level and integrated
into the ABAP language. At runtime, ABAP SQL is translated into a variant of SQL that the
database understands. This variant is called Native SQL to distinguish it from ABAP SQL, the
SQL variant that is integrated into ABAP. The translation from ABAP SQL to Native SQL takes
place in the database interface, a component of the ABAP system that consists of a general
part and a database-specific library.
Even though newer ABAP releases only support SAP HANA as DBMS, SAP has still retained
the concept of ABAP SQL and the database interface. This is because of the following
reasons:
Architecture compatibility
ABAP SQL and the database interface are an integral part of the system architecture.
Code compatibility
ABAP SQL coding from previous SAP products (including customer-specific
development) should run free of side-effects in the modern, SAP-HANA-only ABAP
environments.
Tasks of the Database Interface
The database interface does not just translate statements; it is also responsible for ABAP
specific tasks like, for example, automatic client-handling.
To read data from the database, you use the SELECT statement.
When you write a SELECT statement in ABAP SQL, the syntax check compares what you have
written with the definition of the tables and views. If you try to address tables, views, or fields
that do not exist, a syntax error occurs.
The basic syntax of the SELECT statement contains several sections, called clauses, and
always follows the pattern in the figure, The SELECT Statement in ABAP. The most important
clauses of a SELECT statement are as follows:
FROM
In the FROM clause of the SELECT statement, you specify the data source from which
you want to read. This can be either be a database table or a view. Special SQL
techniques allow you to combine data from multiple sources in the same SELECT
statement.
FIELDS
In the FIELDS clause of the SELECT statement, you list the columns of the database table
that you want to read. The columns in the list must be separated by commas. If you want
to read the entire table line, you can specify FIELDS * instead of a column list. Be aware,
however, that this can cause the database considerably more work than just reading the
columns you need.
WHERE
In the WHERE clause, you can specify a condition that describes which rows of the table
will be read. For example, the condition WHERE carrier_id = 'LH' means that only those
rows will be read (in which the column CARRIER_D contains the value LH).
The WHERE clause can contain multiple conditions linked with the AND and OR
operators. For example, WHERE carrier_id = 'LH' and connection_id = '0400' would
return the data of flight connection LH 0400. You can also negate conditions using NOT.
The WHERE clause is the only clause that is optional. Be aware, however, that without a
WHERE clause you read all data from the table, or if the table has a client field - all data
that belongs to the logon client of the user. SELECTs without a WHERE clause can cause
serious performance problems and should be avoided.
INTO
The INTO clause specifies the variable or variables in the ABAP program into which the
data should be placed. This is normally a structure, or an internal table, and should ideally
have the same sequence of components as the column list in the FIELDS clause.
Note:
You will see other forms of SQL syntax in ABAP. These are older and have been
retained to ensure compatibility. You should get used to using the modern syntax,
as it provides far more functions and features than the old form.
The syntax of the SELECT statement is explained here. You will also see some examples of
the SELECT statement.
Animation
For more information on this topic please view the animation in the lesson
Implementing Basic SELECT Statements in your online course.
The figure, Example 1: Reading Single Field of Single Record, illustrates a SELECT statement
that reads a single value from the database. The FROM clause tells us that the statement
reads from database table /DMO/CONNECTION. The option, SINGLE, after the keyword,
SELECT, indicates that only one row (a single record) is read. This row is identified in the
WHERE clause by providing key filter values for key fields carrier_id and connection_id. Keep
in mind, that the database interface will add a filter on the remaining key field client.
The FIELDS clause lists only one column of the table: column AIRPORT_FROM_ID.
The INTO clause has to match the rest of the statement. In our example, this specifies the
variable airport_from_id as the target object, a scalar data object of identical type as table
field, airport_from_id.
Note:
The at sign (@) identifies airport_from_id as the name of an ABAP data object. It is
mandatory for all variables and constants that you use in an ABAP SQL statement.
It is needed to avoid ambiguities if, for example, a data object and table field have
the same name.
The figure, Example 2: Reading Several Fields of Single Record, illustrates a SELECT
statement that reads two values from the same record of the database.
This time, the FIELDS clause lists two columns of the table; column AIRPORT_FROM_ID and
column AIRPORT_TO_ID.
To match this, the INTO clause specifies the variables airport_from_id and airport_to_id as
the target objects. They are separated by a comma and surrounded by pair of brackets to
make it clear that together they form the target of the SELECT statement.
When you implement a SELECT statement you always have to take into account that there
could be no result, either because the database table does not contain any data at all or
because it does not contain any rows that fulfill the conditions in the WHERE clause. In the
example from the figure above, the database table does not contain a row with carrier_id =
'XX' and connection_id = '1234'.
ABAP SQL use system field SY-SUBRC to indicate a successful or unsuccessful execution of a
statement. System field SY-SUBRC is of type integer. Initial value 0 always indicates a
successful execution. If, after a SELECT statement, SY-SUBRC contains the value 4, this
indicates that the database returned an empty result.
Note:
If the database returns an empty result, ABAP SQL does NOT touch the target
variable after INTO!
In particular, the target variable is not initialized in case of an error.
It is recommended that you evaluate the content of system field sy-subrc immediately after
each SELECT statement.
out->write( `----------` ).
out->write( `Example 1:` ).
out->write( `----------` ).
out->write( `Example 2:` ).
IF sy-subrc = 0.
out->write( `----------` ).
out->write( `Example 3:` ).
out->write( |Flight XX 1234 departs from
{ airport_from_id }.| ).
ELSE.
out->write( `----------` ).
out->write( `Example 3:` ).
ENDIF.
4. Analyze the console output. Debug the program, play around with the source code to get
familiar with the concepts.
LESSON SUMMARY
You should now be able to:
● Describe basic features of ABAP SQL
● Read single values from the database
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Analyze a CDS view definition
● Read data using a CDS view
Figure 108: View Definitions in ABAP Core Data Services (ABAP CDS)
The CDS View definition contains re-usable SQL logic; sometimes as simple as a projection of
table fields and sometimes more sophisticated with calculations, aggregations, joins, unions,
and so on.
A CDS View definition can contain associations to reflect the relations of the data model.
Consumers of the view can use the associations to retrieve related data.
Finally, annotations are used to semantically enrich the view definition. This metadata is
evaluated by frameworks that build on top of CDS View definitions. One such framework is the
ABAP RESTful Application programming model (ABAP RAP), for which we will discuss an
example later in this course.
Let's watch examples of CDS view definitions.
Animation
For more information on this topic please view the animation in the lesson
Working with CDS View in your online course.
CDS View definitions are contained in repository objects of type Data Definition. Let us now
have a look at the source code of data definition /DMO/I_CONNECTION.
The main part is the DEFINE VIEW ENTITY statement. It contains the name of the CDS View
entity and, after keyword FROM, the data source. In our example, the name of the view entity
is /DMO/I_Connection and the data source is database table /dmo/connection. Optional
addition AS, defines an alias name Connection to address the data source inside the View
definition.
Note:
The source of a CDS View entity could also be another CDS View.
A pair of curly brackets contains the list of view elements. In our example, the view elements
are fields of database table /dmo/connection. Keyword Key in front of the first two elements
defines them as key fields of the CDS View entity. Optional addition AS defines an alias name
for each view element.
Addition association defines a relation to another CDS view entity. In our example, the related
is CDS view entity /DMO/I_Carrier and the name of the association is _Airline.
This association becomes available for consumers of the view by adding it to the element list.
This is referred to as exposing the association.
Annotations start with the at sign (@) and they are used to semantically enrich the view
definition for consumers. Annotations before the view definition are called entity annotations.
Entity annotations are used to define metadata for the view entity as a whole. Annotations
between the curly brackets are called element annotations. Element annotations are used to
define metadata for the different elements of the view.
You already learned how to use the Data Preview tool to display and analyze the content of a
database table. This tool is also available for CDS view entities.
To open the Data Preview for a given CDS entity, right-click anywhere in the data definition
and choose Open With > Data Preview. Alternatively, place the cursor anywhere in the
database table definition and press Ctrl + F8.
The tool displays the data returned by the CDS entity. The same functions are available to
sort or filter the data and adjust the display.
If the view definition contains one or more associations, you can use them to display related
data. To do so, proceed in the following manner:
3. From the list of available associations, choose the one in which you are interested.
If you want to find all CDS Views with a certain database table as a source, you can utilize the
Where-used List tool of ADT. To use this tool, proceed as follows:
2. Right-click anywhere in the source code and choose Get Where-used List from the context
menu. Alternatively, you can press Ctrl + Shift + G, or choose the button from the toolbar
with the same symbol.
3. The Search view displays a list of all development objects that directly use the database
table.
You can apply filters to the Where-used List if, for example, you are only interested in objects
from certain packages or objects of the specific object type. The example illustrates how to
filter for CDS views that use the table.
When you implement a SELECT statement in ABAP, you can use a CDS view entity as the data
source instead of reading from the database table directly. This has several advantages.
● Re-use of the SQL logic contained in the CDS view
● Concise, easy-to-read reading of related data using the associations
● Sometimes the names of views and view elements are better readable than the more
technical names of database tables and table fields
The SELECT statement in the example uses CDS view entity /DMO/I_Connection as a data
source.
Note:
Note that the names used in the FIELDS clause and WHERE conditions are the
alias names of the view elements.
The third element in the FIELDS clause makes use of exposed association _Airline. It reads
element name from the associated CDS view entity /DMO/I_Airline. This kind of element is
called a path expression. The backslash (\) is a mandatory prefix for association names.
Task 1: Preparation
1. If you finished the previous exercise Use Private Attributes and Constructors, create a
copy of your global class ZCL_##_CONSTRUCTORS, and name the copy ZCL_##_CDS,
where ## is your group number. Then skip the rest of this task.
2. If you did not finish the previous exercise, create a new global class ZCL_##_CDS, where
## is your group number. Ensure that the class implements the interface
IF_OO_ADT_CLASSRUN.
* First Instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'LH'
i_connection_id = '0400'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Second instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'AA'
i_connection_id = '0017'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Third instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'SQ'
i_connection_id = '0001'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Output
**********************************************************************
out->write( connection->get_output( ) ).
ENDLOOP.
PUBLIC SECTION.
METHODS constructor
IMPORTING
i_connection_id TYPE /dmo/connection_id
i_carrier_id TYPE /dmo/carrier_id
RAISING
cx_ABAP_INVALID_VALUE .
METHODS get_output
RETURNING
VALUE(r_output) TYPE string_table.
PROTECTED SECTION.
PRIVATE SECTION.
DATA carrier_id TYPE /dmo/carrier_id.
DATA connection_id TYPE /dmo/connection_id.
ENDCLASS.
METHOD constructor.
IF carrier_id IS INITIAL OR connection_id IS INITIAL.
RAISE EXCEPTION TYPE cx_abap_invalid_value.
ENDIF.
me->connection_id = i_connection_id.
me->carrier_id = i_carrier_id.
conn_counter = conn_counter + 1.
ENDMETHOD.
METHOD get_output.
ENDMETHOD.
ENDCLASS.
Note:
The new attributes are not filled, yet. In order to fill them we will add a SELECT
statement to the constructor( ) in one of the next tasks of this exercise.
Table 2: Attributes
Attribute Name Scope Data Type
airport_from_id instance /DMO/AIRPORT_FROM_ID
1. Open the development object that contains the definition of CDS View Entity /DMO/
I_Connection.
2. Open the Tooltip Description for the data source of the CDS View entity. Find out the
names of the fields that are typed with /dmo/airport_from_id and /dmo/
airport_to_id.
3. Analyze the element list of the CDS view entity. Find out the alias names for fields
airport_from_id and airport_to_id.
4. Open the Tooltip Description for the target of association _Airline. Find out the alias
name of the field that is typed with /dmo/carrier_id.
3. After statement ENDIF. add a SELECT statement that reads a single record from CDS
view entity /DMO/I_Connection.
Hint:
Use auto-completion (Ctrl + Space) to enter the names.
5. Implement the WHERE clause. Restrict the key elements of CDS view entity with the
values of importing parameters i_carrier_id and i_connection_id. Do not forget to
escape the parameters with prefix @.
Hint:
Use auto-completion (Ctrl + Space) to enter the element names and
parameter names.
6. Implement the INTO clause. Store the SELECT result in attributes airport_from_id,
airport_to_id, and airline_name. Do not forget to escape the attributes with prefix
@.
Hint:
Use auto-completion (Ctrl + Space) to enter the attribute names.
7. Implement error handling after the SELECT statement. Check the content of system field
sy-subrc. If it does not equal Zero, raise exception CX_ABAP_INVALID_VALUE.
8. Activate the class. Execute it and analyze the console output. Check that the output for
the new attributes displays data.
Task 1: Preparation
1. If you finished the previous exercise Use Private Attributes and Constructors, create a
copy of your global class ZCL_##_CONSTRUCTORS, and name the copy ZCL_##_CDS,
where ## is your group number. Then skip the rest of this task.
a) In the Project Explorer view on the left, expand your package.
c) Right-click the name of the class you want to copy and choose Duplicate.
d) In the Name field, enter the name ZCL_##_CDS, where ## is your group number.
e) Choose Next.
2. If you did not finish the previous exercise, create a new global class ZCL_##_CDS, where
## is your group number. Ensure that the class implements the interface
IF_OO_ADT_CLASSRUN.
a) Choose File → New → ABAP Class.
b) Enter the name of your package in the Package field. In the Name field, enter the name
ZCL_##_CDS, where ## is your group number. Enter a description.
d) Enter IF_OO_ADT_CLASSRUN. When the interface appears in the hit list, double-click it
to add it to the class definition.
e) Choose Next.
* First Instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'LH'
i_connection_id = '0400'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Second instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'AA'
i_connection_id = '0017'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Third instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'SQ'
i_connection_id = '0001'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Output
**********************************************************************
out->write( connection->get_output( ) ).
ENDLOOP.
a) Navigate to the Global Class tab and insert the source code between METHOD
if_oo_adt_classrun~main. and ENDMETHOD..
PUBLIC SECTION.
METHODS constructor
IMPORTING
i_connection_id TYPE /dmo/connection_id
i_carrier_id TYPE /dmo/carrier_id
RAISING
cx_ABAP_INVALID_VALUE .
METHODS get_output
RETURNING
VALUE(r_output) TYPE string_table.
PROTECTED SECTION.
PRIVATE SECTION.
DATA carrier_id TYPE /dmo/carrier_id.
DATA connection_id TYPE /dmo/connection_id.
ENDCLASS.
METHOD constructor.
IF carrier_id IS INITIAL OR connection_id IS INITIAL.
RAISE EXCEPTION TYPE cx_abap_invalid_value.
ENDIF.
me->connection_id = i_connection_id.
me->carrier_id = i_carrier_id.
conn_counter = conn_counter + 1.
ENDMETHOD.
METHOD get_output.
ENDMETHOD.
ENDCLASS.
a) Navigate to the Local Types tab and insert the source code.
Note:
The new attributes are not filled, yet. In order to fill them we will add a SELECT
statement to the constructor( ) in one of the next tasks of this exercise.
Table 2: Attributes
Attribute Name Scope Data Type
airport_from_id instance /DMO/AIRPORT_FROM_ID
a) After line PRIVATE SECTION. and before line ENDCLASS., add the following three
statements:
1. Open the development object that contains the definition of CDS View Entity /DMO/
I_Connection.
a) From the toolbar of ADT, choose Open ABAP Development Object or press Ctrl +
Shift + A.
2. Open the Tooltip Description for the data source of the CDS View entity. Find out the
names of the fields that are typed with /dmo/airport_from_id and /dmo/
airport_to_id.
a) Click on /dmo/connection after keyword FROM and press F2 to show the tooltip
description.
3. Analyze the element list of the CDS view entity. Find out the alias names for fields
airport_from_id and airport_to_id.
a) Navigate to the comma-seperated list between the pair of curly brackets ( { } ).
4. Open the Tooltip Description for the target of association _Airline. Find out the alias
name of the field that is typed with /dmo/carrier_id.
a) Click on /DMO/I_Carrier after association [1..1] to and press F2 to show the
tooltip description.
3. After statement ENDIF. add a SELECT statement that reads a single record from CDS
view entity /DMO/I_Connection.
a) After ENDIF., add the following code:
SELECT SINGLE
FROM /DMO/I_Connection
Hint:
Use auto-completion (Ctrl + Space) to enter the names.
c) After a comma and a blank press Ctrl + Space again and choose
DestinationAirport.
d) After a comma and a blank, type in a backslash (\) , press Ctrl + Space and choose
_Airline.
e) Immediately after _Airline, type in a dash sign (-) , press Ctrl + Space and
choose Name.
5. Implement the WHERE clause. Restrict the key elements of CDS view entity with the
values of importing parameters i_carrier_id and i_connection_id. Do not forget to
escape the parameters with prefix @.
Hint:
Use auto-completion (Ctrl + Space) to enter the element names and
parameter names.
6. Implement the INTO clause. Store the SELECT result in attributes airport_from_id,
airport_to_id, and airline_name. Do not forget to escape the attributes with prefix
@.
Hint:
Use auto-completion (Ctrl + Space) to enter the attribute names.
SELECT SINGLE
FROM /DMO/I_Connection
FIELDS DepartureAirport, DestinationAirport, \_Airline-Name
WHERE AirlineID = @i_carrier_id
AND ConnectionID = @i_connection_id
INTO ( @airport_from_id, @airport_to_id, @carrier_name ).
7. Implement error handling after the SELECT statement. Check the content of system field
sy-subrc. If it does not equal Zero, raise exception CX_ABAP_INVALID_VALUE.
a) Add the following code after the SELECT statement:
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE cx_abap_invalid_value.
ENDIF.
8. Activate the class. Execute it and analyze the console output. Check that the output for
the new attributes displays data.
a) Press Ctrl + F3 to activate the class.
LESSON SUMMARY
You should now be able to:
● Analyze a CDS view definition
● Read data using a CDS view
Learning Assessment
3. Which of the following are valid data sources for a CDS view entity?
Choose the correct answers.
X A An internal table
X B A database table
X D A structure
4. You are writing a SELECT statement that reads data using a CDS view entity. In the field
list, you want to read a field from an associated table. What do you use?
Choose the correct answer.
X A A logical expression
X B A path expression
X C A regular expression
Lesson 1
Declaring a Structured Data Object 209
Lesson 2
Working with Structured Data Objects 216
Exercise 9: Use a Structured Data Object 231
UNIT OBJECTIVES
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Declare structured data object
Up until now, you have been using simple variables, each of which can store a single piece of
information. Here, for example, there are two variables, one for the departure airport, the
other for the arrival airport.
When you read a record from the database you need to hold all of this information together.
The two variables in the example are completely independent of one another, and are
therefore not suitable for storing different pieces of information that belong together.
In ABAP, the solution is to use a structured variable, or a structure, for short.
A structure is a variable ABAP data object with a name and a structured type.
In the example, data object connection_full is such a structure. It is subdivided into eight
components, each of which also has a name and a type. You can address both the structure
as a whole and the individual components. Importantly, you can use each component in
exactly the same way that you would use a standalone simple variable.
There are various possibilities to declare a structure. You can define structured types with
keyword TYPES or use a repository object of type Structure. The definitions of views and
database tables can also serve as structured types. The example uses CDS View /DMO/
I_Connection as a structured type.
In the debugger perspective, there are two ways to analyze the structure and content of a
structured variable:
Animation
For more information on this topic please view the animation in the lesson
Declaring a Structured Data Object in your online course.
A global structure type is a repository object that can be used as data type anywhere in the
system. In the example, structured type SYMSG is used to declare variable message.
When you press the F2 key to display the details of this data type you can see that this is a
structure type consisting of seven components. You can also see the names, technical types
and descriptions of the components.
When you press the F3 key to navigate to the definition of the type, a new view opens with the
ADT editor for global structured types.
The definition of a global structure type is very similar to the definition of a database table.
The main part of the definition consists of a DEFINE STRUCTURE statement with the name of
the structure type. This is followed by the list of structure components in a pair of curly
brackets ( { , } ); each component with a component type. Component types are often
described by data elements, but it is also possible to use structure types as component types.
Structures with structured components are referred to as Nested Structures.
Additional code lines before the DEFINE STRUCTURE statement specify additional properties
of the structure type, among them a label.
You can also define structured types in an ABAP program using the TYPES statement. The
structure definition begins with the statement TYPES BEGIN OF <structure type name> and
ends with TYPES END OF <structure type name>. In between, you name each component and
specify its type in an additional TYPES statement.
A compact form uses keyword TYPES only once, followed by a colon( : ). The BEGIN OF
addition, the END OF addition and the component definitions in between are then separated
by commas.
This is referred to as chain statement.
Note:
In the past, chain statements were used a lot in ABAP. Nowadays they are only
recommended to combine statements that belong closely together.
In this example, a chain statement TYPES: is used to define local structured type
st_connection which consists of the three components airport_from_id, airport_to_id, and
carrier_name. Each component is typed with a data element beginning with /dmo/.
Local structured type st_connection is then used in a DATA statement to type structured
variable connection.
In this example, variable connection has a nested structured type. Type st_nested defines 4
components. The first three are typed with data elements, therefore they are simple
components. The fourth component message, however, is typed with a structured type.
Therefore it is a structured component. This makes variable connection nested structure.
ABAP supports not only structured variables but also structured constants. To define a
structured constant, use BEGIN OF and END OF as part of a CONSTANTS statement. The
example shows a structured constant that is defined in the public section of global class
CL_ABAP_BEHV. All four components are typed with data element SECKEYNAME.
Remember that addition VALUE is mandatory when defining constants.
SELECT SINGLE
FROM /dmo/I_Connection
FIELDS AirlineID, ConnectionID, DepartureAirport,
DestinationAirport,
DepartureTime, ArrivalTime, Distance, DistanceUnit
WHERE AirlineId = 'LH'
AND ConnectionId = '0400'
INTO @connection_full.
out->write( `--------------------------------------` ).
out->write( `Example 1: CDS View as Structured Type` ).
out->write( connection_full ).
out->write( `---------------------------------` ).
out->write( `Example 2: Global Structured Type` ).
out->write( message ).
SELECT SINGLE
FROM /DMO/I_Connection
FIELDS DepartureAirport, DestinationAirport, \_Airline-Name
WHERE AirlineID = 'LH'
AND ConnectionID = '0400'
INTO @connection.
out->write( `---------------------------------------` ).
out->write( `Example 3: Global Local Structured Type` ).
out->write( connection ).
out->write( `---------------------------------` ).
out->write( `Example 4: Nested Structured Type` ).
out->write( connection_nested ).
3. Press CTRL + F3 to activate the class and F9 to execute the console app.
4. Analyze the console output. Debug the program, play around with the source code to get
familiar with the concepts.
LESSON SUMMARY
You should now be able to:
● Declare structured data object
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Work with structured data objects
● Use structured data objects in ABAP SQL
To access a component of a structure, you have to place a minus-sign (-) between the
structure name and the component name.
Note:
No blanks are allowed before or after the component selector.
Accessing a structure component that way, you can use it in any operand position in which
you can use a variable of the same type. Component airport_from_id of structure connection
in the example above is of type /DMO/AIRPORT_FROM_ID. In consequence, you can use this
component in any operand position in which you could use a simple variable of type /DMO/
AIRPORT_FROM_ID; not only on the left-hand side of a value assignment as in the example,
but also on the right-hand side, in the parameter passing of a method call, in the INTO clause
or WHERE clause of a SELECT statement, and so on.
If the component of a structure is itself a structure you access the sub-components by using
the component selector again after the name of the main component. The first value
assignment in the example accesses component MSGTY of MESSAGE, which itself is a
component of nested structure CONNECTION_NESTED.
Hint:
You can use code-completion to implement access to structure components.
Place the cursor immediately after the structure component selector and press
CTRL + SPACE to see a list of all available structure components.
The VALUE #( ) expression is an elegant way to assign values to a structured data object.
If you want to fill a whole structure, you can address each component individually as you saw
in the previous example.
However, you can also use a VALUE #( ) expression to fill the structure. The expression
constructs a structure, fills it with value and assigns the filled structure to a variable, in this
case connection. The pound sign (#) tells the ABAP runtime environment to construct a
structure with the same type as the target variable connection. In the brackets, you list the
components of the structure that you want to fill (it does not have to be all of them) and
assign a value to them. The value can be either a literal or the contents of a variable.
When you fill a structure in this way, the runtime system deletes all existing values from the
structure before refilling it with the values from your expression.
Note:
An assignment in the form connection = VALUE #( ). with just a blank between the
brackets, fills all components of the structure with the type-specific initial value.
This has the same effect as statement CLEAR connection.
In ABAP, you may only copy the contents of one structure directly into another structure
using the notation <target structure> = <source structure> if the two structure types are
compatible. This is generally only the case if both structures have the same type. If the
structures have different types, two things can happen:
● If one of the structures has a non-char-like component at a position where the other
structure has a char-like component, direct assignment leads to a syntax error.
● If both structures are char-like, or, in other words, both structures consist of char-like
components, only, direct assignment is technically possible. But usually, the result will be
wrong.
In the example, source structure and target structure are char-like. Therefore, direct
assignment is technically possible. But because they are not compatible the result is wrong:
The content of component carrier_name is copied to component message in the target
structure.
Note:
Because there is no syntax error, you have to be extra careful when working with
non-compatible char-like structures.
When you copy data between structures, you usually want to copy information from one field
into the corresponding field of the target structure - airport_from_id to airport_from_id,
airport_to_id to airport_to_id, and so on. To achieve this in ABAP, use the CORRESPONDING
expression. This assigns values from <source_structure> to the corresponding, that is,
identically-named components of . <target_structure>. You must remember the following
points:
● The fields must have identical names.
● The components do not have to be in the same position or sequence in the two structures.
● If the fields have different types, ABAP attempts a type conversion according to the
predefined set of rules.
Note:
The target structure is initialized before being re-filled with the result of the
expression.
connection-airport_from_id = 'ABC'.
connection-airport_to_id = 'XYZ'.
connection-carrier_name = 'My Airline'.
CLEAR connection.
connection_nested = connection.
out-
>write( `-------------------------------------------------------------
` ).
out->write( `Example 3: Wrong Result after direct assignment` ).
out-
>write( `-------------------------------------------------------------
` ).
out->write( `Example 4: Correct Result after assignment with
CORRESPONDING` ).
3. Press CTRL + F3 to activate the class and F9 to execute the console app.
4. Analyze the console output. Debug the program, play around with the source code to get
familiar with the concepts.
The INTO clause of the SELECT statement will only work correctly if the number and types of
the components of the structure correspond to the number and types of the columns
specified in the FIELDS clause. In the above example, the statement can only work if the
target structure connection has three components with the same type and length as the
columns DepartureAirport, DestinationAirport, and \_Airline-Name listed in the FIELDS
clause. Note that, in this case, the names do not have to be identical - the system fills the
target structure from left to right.
Note:
If the field list in the FIELDS clause does not match the structure or table line
type in the INTO clause, a runtime error will occur.
The example shows an easy technique to ensure that the target structure matches the field
selection:
● The target structure is typed with CDS view /DMO/I_Connection, which is the data source
in the FROM clause.
● The asterisk sign (*) after keyword FIELDS is a short notation to makes sure, that all fields
of the view are part of the field selection. Exposed associations are ignored.
Note:
This technique is also available when you read directly from a database table. Just
like CDS view definitions, database table definitions can also serve as global
structure types in ABAP.
The main advantage of this technique is, that the SELECT statement stays syntactically intact
even if you or someone else makes changes to CDS view or database table. The most
important drawback is, that you always read all fields from the database, whether you actually
need them or not.
Note:
Only use this technique for views and tables with a small number of fields and if
you actually need all the fields. Unnecessary reading of data from the database is
a major cause for performance problems.
Figure 131: INTO CORRESPONDING FIELDS OF to Read Only Fields with Corresponding Names
Another way to avoid syntax errors is variant INTO CORRESPONDING FIELDS. This variant
has the same effect as the CORRESPONDING #( ) operator that you learned about earlier. It
ensures that data is copied between identically-named components. By defining the structure
type according to your needs you can ensure that only the required data is read.
Once again, only the names must be identical. But to avoid problems you should make sure
that identically-named components have compatible types. Otherwise the system attempts
to convert the contents of the source field into the type of the target field. This can lead to
data loss or (catchable) runtime errors.
If the field names in the data source and the component names in the target structure do not
match, the combination of FIELDS * and INTO CORRESPONDING FIELDS OF does not work.
If you want to keep variant INTO CORRESPONDING FIELDS OF, you can define alias names
for the selected fields in the field list. For this, add addition AS after the field name, followed
by the alias name. In the example, the alias name for view field DepartureAirport is
airport_from_id and the alias name for path expression \_Airline-Name is carrier_name.
Based on this alias names, INTO CORRESPONDING FIELDS OF correctly identifies the
structure component in which to store the retrieved data.
The simplest technique to avoid conflicts between the field selection and the target structure
is an inline declaration in the INTO clause. The sequence, type and name of the inline declared
structure is derived from the FIELD clause. Therefore the target structure always fits the field
selection.
Note:
Inline declarations are only supported after INTO. You cannot use inline
declarations after INTO CORRESPONDING FIELDS OF.
If you use an inline declaration in the INTO clause, you have to provide a name for each
element in the FIELDS clause. For fields of the data source, this can be the field name itself or,
optionally an alias name. For expressions, the alias name becomes mandatory.
In the example, there is no alias for field DepartureAirport. The name of the field is used as
component name in structure connection_inline. Field DestinationAirport has an optional alias
ArrivalAirport. In this case the alias is used as component name. The alias for path expression
\_Airline-Name is mandatory.
When working with a relational database you often face the problem that you have to read
related data from different database tables. We already learned that associations in CDS
views are an elegant way to perform this task.
If no CDS View with suitable associations exist you can implement SQL joins, instead. The
example above illustrates the principle of joins:
We are interested in flight connections and the airports they connect with each other. We find
the 3-letter IDs of the airports in database table /DMO/CONNECTION. The full names of the
airports are stored in database table /DMO/AIRPORT.
To retrieve a connection with the departure airport name, in one SELECT statement, we read
connection data from DB table /DMO/CONNECTION and join it with DB table /DMO/
AIRPORT.
A join consists of the following building blocks:
Data Sources
The Database tables and views to join with each other. A single join always combines a
left-hand data source with a right-hand data source. In the example above, table /DMO/
CONNECTION is the left-hand data source and table /DMO/AIRPORT the right-hand
data source. ABAP SQL also supports joins of joins (nested joins)
Join Condition
The join condition specifies which records of the right-hand data source belong to a
record from the left-hand data source. In the example above, the related departure
airport is identified by the value in columns CLIENT and AIRPORT_ID. The join condition
reads:
/DMO/CONNECTION~CLIENT = /DMO/AIRPORT~CLIENT
AND
/DMO/CONNECTION~AIRPORT_FROM_ID = /DMO/AIRPORT~AIRPORT_ID
Join Type
The join type has an influence on the result if one of the data sources does not contain a
matching records. ABAP SQL currently supports INNER JOIN, LEFT OUTER JOIN, and
RIGHT OUTER JOIN. The most common join type is a LEFT OUTER JOIN.
The figure shows the ABAP SQL syntax for a join. In the FROM clause, the join type is specified
by keywords LEFT OUTER JOIN between the left-hand data source /dmo/connection and the
right-hand data source /dmo/airport. The syntax introduces alias names c and f for the data
sources. Alias names for data sources are optional, unless a data source appears more than
once in the join.
The join condition follows keyword ON. The separator between the data source or its alias and
the field is the tilde sign (~).
Note:
In ABAP SQL, it is not necessary to mention the client fields. They are added to the
join condition by the database interface before the statement is sent to the
database. If the FROM clause defines a join, you can use fields from both data
sources in the FIELDS and WHERE clauses.
In this example, the SELECT statement not only read the departure airport name but also the
destination airport name. To do so, the FROM clause defines a nested join:
The first join is a left outer join of tables /dmo/connection and /dmo/airport, introducing
alias "f" (like "from") for the right-hand data source. This first join is then used as left-hand
data source for a second left outer join that has table /dmo/airport as right-hand data source.
Note that in this case alias "t" (for "to") is crucial to distinguish this appearance of
table /dmo/airport from the previous one.
The FIELDS clause, lists the airport names from both data sources, introducing aliases
airport_from_name and airport_to_name to distinguish them from each other.
Hint:
The brackets around the first join are optional. If they are omitted, the joins in the
from clause are evaluated from left to right.
SELECT SINGLE
FROM /DMO/I_Connection
FIELDS DepartureAirport, DestinationAirport, \_Airline-Name
WHERE AirlineID = 'LH'
AND ConnectionID = '0400'
INTO @connection.
out->write( `------------------------------` ).
out->write( `Example 1: Field List and INTO` ).
out->write( connection ).
* Example 2: FIELDS *
**********************************************************************
SELECT SINGLE
FROM /DMO/I_Connection
FIELDS *
WHERE AirlineID = 'LH'
AND ConnectionID = '0400'
INTO @connection_full.
out->write( `----------------------------` ).
out->write( `Example 2: FIELDS * and INTO` ).
out->write( connection_full ).
SELECT SINGLE
FROM /DMO/I_Connection
FIELDS *
WHERE AirlineID = 'LH'
AND ConnectionID = '0400'
INTO CORRESPONDING FIELDS OF @connection_short.
out->write( `----------------------------------------------------
` ).
out->write( `Example 3: FIELDS * and INTO CORRESPONDING FIELDS
OF` ).
out->write( connection_short ).
CLEAR connection.
SELECT SINGLE
FROM /DMO/I_Connection
FIELDS DepartureAirport AS airport_from_id,
\_Airline-Name AS carrier_name
WHERE AirlineID = 'LH'
AND ConnectionID = '0400'
INTO CORRESPONDING FIELDS OF @connection.
out->write( `---------------------------------------------------
` ).
out->write( `Example 4: Aliases and INTO CORRESPONDING FIELDS
OF` ).
out->write( connection ).
SELECT SINGLE
FROM /DMO/I_Connection
FIELDS DepartureAirport,
DestinationAirport AS ArrivalAirport,
\_Airline-Name AS carrier_name
WHERE AirlineID = 'LH'
AND ConnectionID = '0400'
INTO @DATA(connection_inline).
out->write( `-----------------------------------------` ).
out->write( `Example 5: Aliases and Inline Declaration` ).
out->write( connection_inline ).
* Example 6: Joins
**********************************************************************
SELECT SINGLE
FROM ( /dmo/connection AS c
LEFT OUTER JOIN /dmo/airport AS f
ON c~airport_from_id = f~airport_id )
LEFT OUTER JOIN /dmo/airport AS t
ON c~airport_to_id = t~airport_id
FIELDS c~airport_from_id, c~airport_to_id,
f~name AS airport_from_name, t~name AS airport_to_name
WHERE c~carrier_id = 'LH'
AND c~connection_id = '0400'
INTO @DATA(connection_join).
out->write( `------------------------------------------` ).
out->write( `Example 6: Join of Connection and Airports` ).
out->write( connection_join ).
3. Press CTRL + F3 to activate the class and F9 to execute the console app.
4. Analyze the console output. Debug the program, play around with the source code to get
familiar with the concepts.
Task 1: Preparation
1. If you finished the previous exercise Analyze and Use a CDS View Entity, create a copy of
your global class ZCL_##_CDS, and name the copy ZCL_##_STRUCTURE, where ## is your
group number. Then skip the rest of this task.
2. If you did not finish the previous exercise, create a new global class ZCL_##_STRUCTURE,
where ## is your group number. Ensure that the class implements the interface
IF_OO_ADT_CLASSRUN.
* First Instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'LH'
i_connection_id = '0400'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Second instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'AA'
i_connection_id = '0017'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Third instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'SQ'
i_connection_id = '0001'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Output
**********************************************************************
out->write( connection->get_output( ) ).
ENDLOOP.
PUBLIC SECTION.
METHODS constructor
IMPORTING
i_connection_id TYPE /dmo/connection_id
i_carrier_id TYPE /dmo/carrier_id
RAISING
cx_ABAP_INVALID_VALUE .
METHODS get_output
RETURNING
VALUE(r_output) TYPE string_table.
PROTECTED SECTION.
PRIVATE SECTION.
DATA carrier_id TYPE /dmo/carrier_id.
DATA connection_id TYPE /dmo/connection_id.
ENDCLASS.
METHOD constructor.
FROM /DMO/I_Connection
FIELDS DepartureAirport, DestinationAirport, \_Airline-Name
WHERE AirlineID = @i_carrier_id
AND ConnectionID = @i_connection_id
INTO ( @airport_from_id, @airport_to_id, @carrier_name ).
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE cx_abap_invalid_value.
ENDIF.
me->connection_id = i_connection_id.
me->carrier_id = i_carrier_id.
conn_counter = conn_counter + 1.
ENDMETHOD.
METHOD get_output.
ENDMETHOD.
ENDCLASS.
DestinationAirport /DMO/AIRPORT_TO_ID
AirlineName /DMO/CARRIER_NAME
3. Declare a new private instance attribute details and type it with structure type
st_details..
Hint:
Do not type in the component names manually. After typing the structure
component selector (-), press Ctrl + Space to get a list of all components.
3. Activate the class. Execute it and analyze the console output. Check that the output
displays data for all attributes.
Task 1: Preparation
1. If you finished the previous exercise Analyze and Use a CDS View Entity, create a copy of
your global class ZCL_##_CDS, and name the copy ZCL_##_STRUCTURE, where ## is your
group number. Then skip the rest of this task.
a) In the Project Explorer view on the left, expand your package.
c) Right-click the name of the class you want to copy and choose Duplicate.
d) In the Name field, enter the name ZCL_##_STRUCTURE, where ## is your group
number.
e) Choose Next.
2. If you did not finish the previous exercise, create a new global class ZCL_##_STRUCTURE,
where ## is your group number. Ensure that the class implements the interface
IF_OO_ADT_CLASSRUN.
a) Choose File → New → ABAP Class.
b) Enter the name of your package in the Package field. In the Name field, enter the name
ZCL_##_STRUCTURE, where ## is your group number. Enter a description.
d) Enter IF_OO_ADT_CLASSRUN. When the interface appears in the hit list, double-click it
to add it to the class definition.
e) Choose Next.
* First Instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'LH'
i_connection_id = '0400'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Second instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'AA'
i_connection_id = '0017'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Third instance
**********************************************************************
TRY.
connection = NEW #(
i_carrier_id = 'SQ'
i_connection_id = '0001'
).
CATCH cx_abap_invalid_value.
out->write( `Method call failed` ).
ENDTRY.
* Output
**********************************************************************
out->write( connection->get_output( ) ).
ENDLOOP.
a) Navigate to the Global Class tab and insert the source code between METHOD
if_oo_adt_classrun~main. and ENDMETHOD..
PUBLIC SECTION.
METHODS constructor
IMPORTING
i_connection_id TYPE /dmo/connection_id
i_carrier_id TYPE /dmo/carrier_id
RAISING
cx_ABAP_INVALID_VALUE .
METHODS get_output
RETURNING
VALUE(r_output) TYPE string_table.
PROTECTED SECTION.
PRIVATE SECTION.
DATA carrier_id TYPE /dmo/carrier_id.
DATA connection_id TYPE /dmo/connection_id.
ENDCLASS.
METHOD constructor.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE cx_abap_invalid_value.
ENDIF.
me->connection_id = i_connection_id.
me->carrier_id = i_carrier_id.
conn_counter = conn_counter + 1.
ENDMETHOD.
METHOD get_output.
ENDMETHOD.
ENDCLASS.
a) Navigate to the Local Types tab and insert the source code.
DestinationAirport /DMO/AIRPORT_TO_ID
AirlineName /DMO/CARRIER_NAME
3. Declare a new private instance attribute details and type it with structure type
st_details..
a) Insert the following code after DATA carrier_name TYPE /dmo/
carrier_name..
Hint:
Do not type in the component names manually. After typing the structure
component selector (-), press Ctrl + Space to get a list of all components.
APPEND |--------------------------------| TO
r_output.
APPEND |Carrier: { carrier_id } { details-airlinename }| TO
r_output.
APPEND |Connection: { connection_id }| TO
r_output.
APPEND |Departure: { details-departureairport }| TO
r_output.
APPEND |Destination: { details-destinationairport }| TO
r_output.
SELECT SINGLE
FROM /DMO/I_Connection
FIELDS DepartureAirport, DestinationAirport, \_Airline-Name
WHERE AirlineID = @i_carrier_id
AND ConnectionID = @i_connection_id
* INTO ( @airport_from_id, @airport_to_id, @carrier_name ).
INTO @details.
SELECT SINGLE
FROM /DMO/I_Connection
FIELDS DepartureAirport, DestinationAirport, \_Airline-Name as
AirlineName
WHERE AirlineID = @i_carrier_id
AND ConnectionID = @i_connection_id
INTO CORRESPONDING FIELDS OF @details.
3. Activate the class. Execute it and analyze the console output. Check that the output
displays data for all attributes.
a) Press Ctrl + F3 to activate the class.
LESSON SUMMARY
You should now be able to:
● Work with structured data objects
● Use structured data objects in ABAP SQL
Learning Assessment
1. You declare a variable using the statement DATA struct TYPE <type>. Which of the
following can you use to declare a structure?
Choose the correct answers.
X A A database table
X B A data element
X C A CDS view
2. A structure struct contains a component comp. How do you address the component?
Choose the correct answer.
X A struct.comp
X B struct-comp
X C struct->comp
X D struct=>comp
Lesson 1
Declaring a Complex Internal Table 245
Lesson 2
Working with Complex Internal Tables 253
UNIT OBJECTIVES
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Declare a complex internal table
The internal tables we have used so far had a scalar data type as their row type. In the
example shown in the figures here, the row type of internal table numbers is the ABAP built-in
type I.
We refer to these kinds of internal tables as simple internal tables.
We speak of a complex internal table if the row type is a structured data type.
While a simple internal table has only one nameless column, a complex internal table consists
of several columns, each of them with the name and type of the corresponding component of
the structured row type. In the example, the row type of internal table connection is a
structured type with five components: carrier_id, connection_id, airport_from_id,
airport_to_id, and carrier_name. Consequently, internal table connections has five columns
with those names.
Note:
The columns in the examples in the figures, Reminder: Simple Internal Tables and
Internal Tables with Structured Row Type, all have scalar types. More generally, a
column of an internal table could also be of structured type or even have a table
type. In the latter case, we talk of a nested internal table.
Figure 139: Addressing Internal Tables - Index Access and Key Access
Up to now, we have addressed the rows of an internal table by their position. This is called an
index access.
With the named columns of a complex internal table, key access becomes more important.
Key access means addressing a row of the internal table by looking for particular values in
particular columns. The columns in which you search can be any columns of the internal
table.
Index access to an internal table is always very fast, even if the internal table contains many
rows. Key access, however, can become very slow if the table contains a lot of rows. Choosing
the right access type for the internal table can improve the performance of a key access.
Every internal table has one of three access types. The access type determines how data is
stored in the table and, based on that, how the system reads the table to retrieve the data.
The different types of tables are as follows:
Animation
For more information on this topic please view the animation in the lesson
Declaring a Complex Internal Table in your online course.
Standard Table
In a standard table, the contents are not stored in a particular sort order. By default, new
records are appended to the end of the table. In order to retrieve data by key, the system
must read it sequentially, which can lead to long retrieval times if the table is very large.
The simple internal tables we used so far were standard tables.
Sorted Table
In a sorted table, the contents of the table are always sorted according to the key fields in
ascending order. When you insert a new record into the table, the system ensures that it
is placed at the correct position. Since the data is always sorted, the system can retrieve
records more efficiently than from a standard table (as long as you follow particular
rules).
Hashed Table
Hashed tables are managed using a special hash algorithm. This ensures that the system
can retrieve records very quickly even if the table is extremely large. However, this
performance gain only works in very particular cases.
Note:
In this lesson, we will concentrate on standard tables. More information on sorted
and hashed tables can be found in the ABAP documentation.
Every internal table has a key. In standard tables, the key does not play a particularly
significant role. For sorted and hashed tables, the key is very important as it determines the
way in which the data will be managed in the table. Crucially, sorted and hashed tables are
only faster for key access that addresses all or at least a subset of the key fields.
A further attribute of the table key is its uniqueness. You will sometimes want to allow
duplicate entries in an internal table, and sometimes you will want to ensure that the key is
unique. Here, the following rules apply:
● Duplicates are always allowed in standard tables
● Duplicates are never allowed in hashed tables
● For a sorted table, you choose in the definition whether the key is to be unique or non-
unique.
Note:
Internal tables may also have secondary keys. Secondary keys are a way of
improving the performance of key accesses to internal tables that use different
combinations of fields. You will find more information about secondary keys in the
ABAP syntax documentation.
Here you will see some examples for the declaration of complex internal tables.
Animation
For more information on this topic please view the animation in the lesson
Declaring a Complex Internal Table in your online course.
The figure, Examples - Complex Internal Tables, shows some examples for the declaration of
complex internal tables.
The first example specifies neither the access type nor the key attributes. The ABAP compiler
implicitly declares a standard table with a non-unique standard key. The standard key, or
default key, consists of all non-numeric columns of the table line type.
For simple internal tables, where the access type and key definition is not important, the short
syntax is acceptable. For complex internal tables, you should clearly specify the access type
and key. The second example defines an internal table of exactly the same type as the first
example, but instead of using the short form it explicitly specifies the access type and the key.
The third and forth internal tables define internal tables of access type sorted and hashed. For
these access types the definition of the key is mandatory.
Furthermore, it is good programming style to define the data type first and then to create a
variable that refers to the type.
Instead of specifying the access type and key of an internal table in the DATA statement, you
should use a named table type. If you need the table type only locally in a method or in
connection with a given class, you can define it using the TYPES statement.
The example defines a structured type st_connection, first. With this structured type as row
type it then defines table type tt_connections. Finally, the declaration of internal table
connections_5 refers to the table type.
If you need the table type globally, you can use a global table type.
A global table type is a repository object that can be used as data type anywhere in the
system. ADT provides a dedicated editor for this kind of repository object. The tool consists of
the following frames:
Animation
For more information on this topic please view the animation in the lesson
Declaring a Complex Internal Table in your online course.
Row Type
This frame contains the origin and name of the row type. In the example, global
type /DMO/FLIGHT is used.
Initialization and Access
Among other things, this frame contains the definition of the access type. In the example,
the access type - Standard Table - is selected.
Key Overview
This frame provides an overview of the keys. Choose the key for which you want to see
details on the right.
Note:
Every table type contains the definition of a primary key. Secondary keys are
optional. The table type in the example does not contain any secondary
keys.
Key Details
This frame displays details for the selected key: the key category, that is, whether the key
is unique or non-unique, and the list of key components. In the example, the primary key
is non-unique and consists of components CLIENT, CARRIER_ID, CONNECTION_ID, and
FLIGHT_DATE.
out->write( `--------------------------------------------` ).
out->write( `Example 1: Simple and Complex Internal Table` ).
out->write( `------------------------------------------` ).
out->write( `Example 4: Global Table TYpe /DMO/T_FLIGHT` ).
out->write( data = flights
name = `Internal Table FLIGHTS:` ).
3. Press CTRL + F3 on your keyboard to activate the class and F9 to execute the console
app.
4. Analyze the console output. Debug the program, play around with the source code to get
familiar with the concepts.
LESSON SUMMARY
You should now be able to:
● Declare a complex internal table
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Fill complex internal tables with data
● Access the content of complex internal tables
● Use complex internal tables in ABAP SQL
Figure 145: APPEND for Complex Internal Tables - Declaration of a Work Area
As you already learned, the simplest way to add a new row to an internal table is the APPEND
statement with a data object whose type corresponds to the row type of the internal table.
This data object is sometimes referred to as work area.
For simple internal tables the work area used in APPEND can be a scalar variable, constant, or
a literal. For complex internal tables, the work area has to be structured.
In the example, structured variable connection is used to fill internal table connections.
In principle, there are two ways to declare work area connection:
● Reference the row type st_connection directly
● Reference the row type indirectly using LIKE LINE OF <internal_table>.
● It reveals the purpose of the structured variable as work area for the internal table
● It ensures that the work area fits to the internal table, even if the definition of the internal
table changes
Figure 146: APPEND for Complex Internal Tables - Filling the Work Area
If you do not fill the work area before the APPEND statement, the new row of the internal table
will be filled with type-specific initial values.
Hint:
You get the same result with the special variant APPEND INITIAL LINE TO
<internal_table>. This variant does not even require a work area.
To fill the structured work area, you can either fill the individual components or, as you can
see in the example, use a VALUE #( ) expression.
Figure 147: APPEND for Complex Internal Tables - direct use of VALUE #( )
As you can see in the example, you can also use a VALUE #( ) expression directly in the
APPEND statement. In this case, you do not need a work area.
Note:
This can have a positive effect on the overall memory consumption of your
program.
There is a variant of the VALUE #( ) expression that you can assign directly to an internal
table. In this variant of VALUE #( ) additional pairs of brackets are used to separate the table
rows from each other.
The code example fills internal table carriers with three rows, each with a different value for
carrier_id and carrier_name. As a result of this, column currency_code is not mentioned, it is
filled with the type specific initial value.
Note:
With the assignment above, all existing table rows are removed before the table is
filled with the new rows.
To copy data between identically-named fields of two internal tables, use the
CORRESPONDING operator. This works similarly to CORRESPONDING for structures: for
each row of the source internal table, the system creates a new row in the target internal table
and copies data between identically-named fields. Source fields for which there is no
identically named field in the target are not copied. Target fields for which there is no
identically named field in the source are filled with type-specific initial values.
In the example, the source internal table carriers contains three rows. Therefore, after the
value assignment, the target internal table connections also contains three rows.
Fields carrier_id and carrier_name exist in both internal tables. They are copied from source
to target. Field currency_code only exists in the source. It is not copied. Fields connection_id,
airport_from_id, and airport_to_id exist only in the target. They are filled with initial values.
Note:
If the target internal table contains data before the assignment, the system
deletes it.
* connection-carrier_id = 'NN'.
* connection-connection_id = '1234'.
* connection-airport_from_id = 'ABC'.
* connection-airport_to_id = 'XYZ'.
* connection-carrier_name = 'My Airline'.
out->write( `--------------------------------` ).
out->write( `Example 1: APPEND with Work Area` ).
out->write( connections ).
out->write( `----------------------------` ).
out->write( `Example 2: Append with VALUE` ).
out->write( connections ).
out->write( `-----------------------------------------` ).
out->write( `Example 3: Fill Internal Table with VALUE` ).
out->write( carriers ).
out->write( `--------------------------------------------` ).
out->write( `Example 4: CORRESPONDING for Internal Tables` ).
out->write( data = carriers
name = `Source Table CARRIERS:`).
out->write( data = connections
name = `Target Table CONNECTIONS:`).
3. Press CTRL + F3 on your keyboard to activate the class and F9 to execute the console
app.
4. Analyze the console output. Debug the program, play around with the source code to get
familiar with the concepts.
Earlier in this course, you learned how to retrieve a single row from a simple internal table
using an internal table expression. Back then we used an index access, that is, we identified
the row through its position in the internal table. This index access works for complex internal
tables in just the same way. For complex internal tables, however, internal table expressions
with key access become important, where you identify the row through its content.
Note:
Even though this is called a key access, you can use any fields for the selection,
not only key fields of the internal table. If more than one row fulfills the
requirement, the first row is returned, that is, the row with the lowest index.
The example reads a single row from internal table connections. The key of this internal table
consists of fields, carrier_id and connection_id, but the key access uses airport_from_id and
airport_to_id to identify the row. The Internal table contains two connections from airport SFO
to SIN, so the first of them is returned.
Note:
Remember that the ABAP runtime raises exception
CX_SY_ITAB_LINE_NOT_FOUND if none of the rows fulfills the requirement.
Handle this exception in a TRY … CATCH … ENDTRY structure to avoid runtime
errors
To process multiple lines of an internal table by specifying fields, you use LOOP AT <internal
table> INTO <target> WHERE <condition>. The WHERE condition can contain any number of
constituent expressions joined using AND and OR. Within the expressions, you can use not
just the equals operator (=) but also operators >, >=, <, <=, <> and BETWEEN.
After reading the content of a table row into a work area, you sometimes want to write
changes from the work area back into the internal table. One way to do this is the MODIFY
TABLE statement.
This statement is a key access because the system uses the content of the key fields in the
work area to identify the table row that needs to be modified. It then overwrites this table row
with the contents of the work area.
In the example, the work area carrier contains value 'JL' in key field, carrier_id. Based on this
value, the system identifies the second row to be updated. This row is then updated with the
values from the work area.
Note:
You can only change non-key fields with MODIFY TABLE. The statement does not
support changes to key fields.
The MODIFY statement (without keyword TABLE!) does not distinguish between key fields
and non-key fields. It overwrites the entire table row with new values from the work area. This
statement is considered an index access because the row to be updated is identified by its
position in the internal table. Usually, the index is specified explicitly using addition INDEX
followed by an integer argument (literal, constant, variable, expression).
Note:
There is also a special variant without addition INDEX. We will discuss this variant
next.
In the example, the MODIFY statement uses the INDEX addition to address the first table row.
In this row, all fields are overwritten with the values from the work area, even key field
carrier_id.
There will often be times when you need to modify the contents of multiple rows of an internal
table, or maybe even all of them. To do this, you implement a loop over the table, which places
each row you need to change successively into a work area. Within the loop, you first change
the contents of the work area and then write the changes back into the internal table using the
MODIFY statement.
Note:
If you do not write your changes back into the table, the changes will be lost when
the work area is filled with the data from next row.
In the example, the loop reads all rows of internal table carriers for which field currency_code
is not yet filled. This is the case for the last two rows. For each of these rows the program
replaces the initial value in field currency_code with the new value 'USD'. Finally, it uses the
MODIFY statement to overwrite the current row with the updated values.
Instead of specifying the index explicitly, the code example uses a short form of the MODIFY
statement where the INDEX addition is missing. This short form is only allowed between
LOOP … ENDLOOP. Only there the system can implicitly update the row it is currently working
on.
Note:
If you use MODIFY without INDEX outside of LOOP…ENDLOOP, the system does
not know which row to modify and triggers a non-catchable runtime error. To
avoid such runtime errors, make sure not to ignore the related warning from the
syntax check!
airport_to_id = 'SIN'
carrier_name = 'Singapore Airlines'
)
( carrier_id = 'UA'
connection_id = '0078'
airport_from_id = 'SFO'
airport_to_id = 'SIN'
carrier_name = 'United Airlines'
)
).
out->write( `-------------------------------` ).
out->write( `Example 2: LOOP with Key Access` ).
ENDLOOP.
**********************************************************************
out->write( `-----------------------------------` ).
out->write( `Example 3: MODIFY TABLE (key access` ).
carrier-carrier_id = 'LH'.
carrier-currency_code = 'EUR'.
MODIFY carriers FROM carrier INDEX 1.
carrier-currency_code = 'USD'.
MODIFY carriers FROM carrier.
ENDLOOP.
3. Press CTRL + F3 on your keyboard to activate the class and F9 to execute the console
app.
4. Analyze the console output. Debug the program, play around with the source code to get
familiar with the concepts.
The ABAP SQL statement, SELECT, reads data from a database table or a CDS View. When
you use the SINGLE option, exactly one record is read from the database, even if more data
exist that meets the conditions in the WHERE clause.
As you learned earlier, one way to receive this single record result is structured variable after
keyword INTO.
If you use SELECT without SINGLE, you indicate that you are interested in all records that
match the conditions in the WHERE clause. You then have to make sure that you can actually
receive and store multiple records. The obvious way to do this is the usage of a complex
internal table as target of the SELECT statement. This is possible but it requires addition
TABLE between keyword INTO and the name of the internal table.
In the example, we want to read all three airports related to London and not just a single one
of them. Therefore, we leave out the keyword SINGLE after SELECT, add keyword TABLE
after INTO and use internal table airports_full as target of the SELECT statement.
The example uses an explicit field list after FIELDS that matches the columns of internal table
airports_full. Of course, you can also use FIELDS *, INTO CORRESPONDING FIELDS OF
TABLE, and alias names in the field list.
This example uses FIELDS * instead of an explicit field list and INTO CORRESPONDING
FIELDS OF TABLE instead of INTO TABLE.
As the row type of internal table airports contains only two components AirportID and Name,
only the fields with the same name are read from the database.
If you use DATA( ) in a SELECT statement after addition INTO TABLE, you inline declare an
internal table. The row type of this internal table is derived from the FIELDS clause. For table
fields and view elements an alias name is optional. For expressions in the FIELDS clause, an
alias name is mandatory if the INTO clause contains an inline declaration.
Note:
Inline declarations of internal tables are only supported after INTO TABLE. You
cannot use inline declarations after INTO CORRESPONDING FIELDS OF TABLE.
Note:
Inline-declared internal tables are always standard tables without a key. You
cannot declare sorted or hashed tables using inline declarations. This can cause
performance problems if you fill the internal table with many rows and use key
access a lot.
When you are reading multiple records from the database, some special SQL techniques
become particularly interesting. One of these techniques is the UNION directive to combine
the results of several SELECT statements.
The figure illustrates the combination of two SELECT results:
The first SELECT result reads ID and NAME of all carriers with CURRENCY_CODE = 'GBP'. The
second SELECT reads ID and NAME of all airports with CITY = 'London'. The first SELECT
returns one record, the second SELECT returns three records. Instead of retrieving these
results separately, they are combined into one result with four records. It is important to point
out that this happens inside the database.
A prerequisite for this technique is, of course, that the two results are compatible with each
other, that is, that they have the same number of fields, the same field names. It is beneficial,
though not necessary, that the types of the fields are also the same.
The ABAP SQL syntax for this example consists of two SELECT statements. Each SELECT
statement has its own FROM clause, FIELDS clause, and WHERE clause, but there is only one
INTO clause at the very end. The two SELECT statements are connected by keywords UNION
ALL.
Note:
With UNION instead of UNION ALL, the database would look for and eliminate
duplicates before returning the result. We use UNION ALL to avoid this
unnecessary additional load on the database.
Both field lists consist of three elements, the first and second element have identical alias
names in both FIELDS clauses. The third field does not need an alias because the field name is
the same in both CDS Views.
Note:
The first element in FIELDS is a literal text that allows us to distinguish between
Airlines and Airports in the combined result.
SELECT SINGLE
FROM /DMO/I_Airport
FIELDS AirportID, Name, City, CountryCode
WHERE City = 'Zurich'
INTO @airport_full.
out->write( `-------------------------------------` ).
out->write( `Example 1: SELECT SINGLE ... INTO ...` ).
out->write( data = airport_full
name = `One of the airports in Zurich (Structure):` ).
SELECT
FROM /DMO/I_Airport
FIELDS airportid, Name, City, CountryCode
WHERE City = 'London'
INTO TABLE @airports_full.
out->write( `------------------------------------` ).
out->write( `Example 2: SELECT ... INTO TABLE ...` ).
out->write( data = airports_full
name = `All airports in London (Internal Table):` ).
SELECT
FROM /DMO/I_Airport
FIELDS *
WHERE City = 'London'
INTO CORRESPONDING FIELDS OF TABLE @airports.
out-
>write( `----------------------------------------------------------
` ).
out->write( `Example 3: FIELDS * and INTO CORRESPONDING FIELDS OF
TABLE` ).
out->write( data = airports
name = `Internal Table AIRPORTS:` ).
SELECT
FROM /DMO/I_airport
FIELDS AirportID, Name AS AirportName
WHERE City = 'London'
INTO TABLE @DATA(airports_inline).
out-
>write( `----------------------------------------------------------
` ).
out->write( `Example 4: Inline Declaration after INTO TABLE` ).
out->write( data = airports_inline
name = `Internal Table AIRPORTS_INLINE:` ).
UNION ALL
out->write( `----------------------------------------------` ).
out->write( `Example 5: UNION ALL of Airlines and Airports ` ).
out->write( data = names
name = `ID and Name of Airlines and Airports:` ).
3. Press CTRL + F3 on your keyboard to activate the class and F9 to execute the console
app.
4. Analyze the console output. Debug the program, play around with the source code to get
familiar with the concepts.
LESSON SUMMARY
You should now be able to:
● Fill complex internal tables with data
● Access the content of complex internal tables
● Use complex internal tables in ABAP SQL
Learning Assessment
2. The type tt_table is defined as follows: TYPES tt_table TYPE STANDARD TABLE OF
st_connection WITH NON-UNIQUE KEY carrier_id connection_id. Which DATA statement
would you use to create an internal table with this type?
Choose the correct answer.
X A READ
X B VALUE #( )
X C LOOP...ENDLOOP
X D APPEND
4. You want to read data from two database tables so that the SELECT statement returns a
single result set that contains no duplicate entries. Which of the following techniques
would you use?
Choose the correct answer.
X B UNION
X C INNER JOIN
X D UNION ALL
Lesson 1
Analyzing a Business Object 275
Lesson 2
Using the Entity Manipulation Language 280
Exercise 10: Modify Data Using EML 287
UNIT OBJECTIVES
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Analyze a Business Object
In the ABAP RESTful Application Programming model (RAP), a business object defines a
particular entity, such as a travel agency. Its definition has two parts; a CDS view, which
defines the structure of the object or, in other words, the fields that it contains, and a behavior
definition, which describes what you can do with the business object.
The behavior definition specifies which of the standard operations, create, update, delete are
allowed. It can also contain the definition of validations, determinations, and actions.
Validations check that the data is correct when you create or update a record. Determinations
modify instances of business objects based on trigger conditions. Actions are non-standard
operations which you use to provide customized, business-logic-specific behavior. Approving
a purchase order or canceling a flight are activities that you would implement as an action.
The behavior implementation consists of one or more ABAP classes. Here the validations,
determinations, and actions are implemented. When it comes to the standard operations,
RAP distinguishes two implementation scenarios: In the unmanaged implementation
scenario, create, update, and delete are implemented in the behavior implementation. In the
managed implementation scenario the RAP runtime takes care of them.
RAP business objects are commonly used in generated Fiori Elements apps or Web APIs.
However, you can also access them from ABAP coding using Entity Manipulation Language
(EML). This is a set of ABAP statements that allows you to create, read, update, and delete
data using business objects. When you create business objects using RAP, you will have to
implement its application-specific behavior. You use EML in this context to access the
application data.
In this unit, you will create a class that modifies travel agency data. The view entity contains
the key field AgencyID and various other fields containing information about the travel
agency.
There are two parts to the behavior of a business object: the behavior definition and the
behavior implementation. The behavior definition contains information about what the
business object can do, while the behavior implementation contains the actual coding that the
system executes.
managed implementation in class <class> unique
The behavior implementation is an ABAP class. You declare the class in the behavior
definition in the statement managed implementation in class <class> unique. The actual
coding of the behavior implementation is contained in a local class within the global class that
you specify.
Animation
For more information on this topic please view the animation in the lesson
Analyzing a Business Object in your online course.
The global class of the behavior implementation (also known as a behavior pool) is just an
empty class definition with the special addition FOR BEHAVIOR OF followed by the name of
the behavior definition. The actual implementation of the behavior definition is a local class
within the global class definition. You access the class by clicking the Local Types tab.
The behavior implementation contains code that is specific to the business object, for
example, the implementation for validations, determinations, and actions. Whether it also
contains code for the standard operations (create, update, delete, and lock) depends on the
details of the behavior definition. The behavior implementation for our business object does
not contain code for the standard operations. This is because the business object uses the
managed implementation type in which the RAP runtime deals with the standard operations.
A validation is a check that the RAP runtime performs when data is changed. Here, the
validation is always performed when a new record is created (trigger create;) If an existing
record is changed, the validation is only performed if the Name field has been changed
(trigger field Name;).
Validations are defined in the behavior definition. For each validation, there is a corresponding
method in the behavior implementation.
LESSON SUMMARY
You should now be able to:
● Analyze a Business Object
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Implement an EML Statement
EML consists of statements that you can use to manipulate the data of a business object. You
use the READ ENTITIES statement to read data; for all other operations, you use the MODIFY
ENTITIES statement with the corresponding addition UPDATE, CREATE, or DELETE.
Note:
You can only use the CREATE, UPDATE, and DELETE operations if the behavior
definition of the business object contains the corresponding use create, use
update, or use delete directive. Trying to use a prohibited operation causes a
syntax error.
To read data from a business object, you use the READ ENTITIES statement. The statement
has two important parameters: one internal table containing the keys of the data that you
want to read and another containing the results of the query.
These internal tables have special data types called derived behavior definition types. The
system creates them automatically when a developer creates a behavior definition and they
contain some or all of the fields of the business object along with further fields that control
how the system processes a particular request. You declare the internal tables using the new
addition TYPE TABLE FOR <operation> in the DATA statement.
The type TABLE FOR READ IMPORT contains the key field or fields of the business object. The
%control structure is a generated structure that indicates which fields of the business object
are actually used in the current operation. In our example, the system fills the structure
automatically and you do not have to worry about it.
The type TABLE FOR READ RESULT contains all of the fields of the business object. This table
contains the result set after the read statement has been executed.
The read import table contains a column for each key field of the business object, in this case,
agencyID. To read a particular agency, you add a row to the internal table containing its key.
As well as the key field or fields, the table contains the columns %is_draft and %control. With
%is_draft you can specify whether you want to read draft data or active data. The %control
structure is used to specify which fields are to be read.
Note:
In our example, we only read active data and the %control structure is filled by the
framework, based on the field list after addition FIELDS.
When you process a business object, using the READ ENTITIES OF or MODIFY ENTITIES OF
statement, you must first specify the name of the behavior definition. This is followed by
keyword ENTITY and the name of the entity with which you want to work. If the entity has a
alias name, you should use it, here.
Note:
You cannot use the alias name after addition OF. This is because the technically
speaking, you specify the name of the behavior definition at this point, not the
name of the entity.
The READ ENTITIES statement reads business object data according to the keys that you
pass in the WITH addition. It returns the result in the internal table in the RESULT addition. In
the statement, you can also specify which fields of the business object you need. In this
example, we have used the ALL FIELDS addition to return all of the fields. However, if you only
require a subset of the fields, you can use the variant FIELDS ( field1 field2 … ) to restrict the
amount of data that is read. Note that, unlike in a SELECT statement, the field list is not
comma-separated.
The result table contains all of the fields of the business object, along with the control field
%is_draft. If your READ ENTITIES statement contains the FIELDS ( f1 … fn ) variant, only the
fields that you requested will be filled. If you use the ALL FIELDS variant, the system provides
the values of all of the fields.
If you want to update data, you declare an internal table with TYPE TABLE FOR UPDATE. This
contains all of the fields of the business object and also the %control structure. In our variant
of the MODIFY ENTITIES statement, the system fills the %control structure automatically.
The MODIFY ENTITIES statement updates data in the RAP transactional buffer. In the
UPDATE FIELDS addition, you specify which fields should be changed. In the WITH addition,
you pass the internal table containing the data that you want to update.
When you use EML outside the business object, you must use the COMMIT ENTITIES
statement to trigger the RAP save sequence and persist the data in the database.
Note:
Later in this course we will use EML inside the behavior implementation. Inside the
behavior implementation it is neither necessary nor allowed to trigger the commit
with COMMIT ENTITIES.
1. Open the data preview of table /DMO/AGENCY and note any value of AGENCY_ID. This is
the record that you will change using EML.
2. Create a new ABAP class called ZCL_##_EML, where ## is your group number. Ensure that
the class implements the interface IF_OO_ADT_CLASSRUN.
4. Modify the data, ensuring that only the field NAME is changed. Commit the changes.
5. Check whether your changes have been applied in table /DMO/AGENCY, yet.
7. Check that after the execution of the COMMIT ENTITIES statement your changes have
been applied in table /DMO/AGENCY.
1. Open the data preview of table /DMO/AGENCY and note any value of AGENCY_ID. This is
the record that you will change using EML.
a) Press Ctrl + Shift + A to open the Open ABAP Development Object dialog box.
b) Enter /DMO/AGENCY. There are several hits, to open the object with the Database Table
type, double-clicking it.
2. Create a new ABAP class called ZCL_##_EML, where ## is your group number. Ensure that
the class implements the interface IF_OO_ADT_CLASSRUN.
a) Choose File → New → ABAP Class.
b) Enter the name of your package in the Package field. In the Name field, enter the name
ZCL_##_EML, where ## is your group number. Enter a description.
d) Enter IF_OO_ADT_CLASSRUN. When the interface appears in the hit list, double-click it
to add it to the class definition.
e) Choose Next.
4. Modify the data, ensuring that only the field NAME is changed. Commit the changes.
a) Enter the following code in the method implementation.
5. Check whether your changes have been applied in table /DMO/AGENCY, yet.
a) Proceed as described in step 1. You should find that your change has not been applied
in the column NAME because you have not committed the changes, yet.
COMMIT ENTITIES.
7. Check that after the execution of the COMMIT ENTITIES statement your changes have
been applied in table /DMO/AGENCY.
a) Proceed as described in step 1. Ensure that your change has been applied in the
column NAME.
LESSON SUMMARY
You should now be able to:
● Implement an EML Statement
Learning Assessment
X A Validations
X B Determinations
X C Update
2. In the READ ENTITIES statement, you use an internal table with a special type (TYPE
TABLE FOR READ RESULT). How is this type created?
Choose the correct answer.
Lesson 1
Introducing the ABAP RESTful Application Programming Model (RAP) 295
Lesson 2
Exploring the Architecture of RAP 296
Exercise 11: Create Data Elements 303
Exercise 12: Create a Database Table 305
Exercise 13: Generate the Development Objects for an OData UI Service 313
Lesson 3
Adding ABAP logic 318
Exercise 14: Validate the Semantic Key 329
Exercise 15: Validate the Airline 333
Exercise 16: Validate the Airport Codes 337
Exercise 17: Determine the Cities and Countries 345
Lesson 4
Improving the User Experience 349
Exercise 18: Adjust the UI of your app 353
Exercise 19: Provide Input Help 361
UNIT OBJECTIVES
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Describe the process to develop an OData Service with RAP
Overview
Watch this video to get an overview of what you will learn in this unit.
LESSON SUMMARY
You should now be able to:
● Describe the process to develop an OData Service with RAP
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Create a database table
● Generate the RAP Objects for an OData UI service
The data model in this example is very simple - there is a single database table that contains
details of flight connections - an airline, flight number, details of the departure airport and the
destination.
Note:
Not included in the figure are additional fields that the RAP runtime needs in order
to manage concurrency control. We will look at these a little later on.
Animation
For more information on this topic please view the animation in the lesson
Exploring the Architecture of RAP in your online course.
To create a database table, choose File → New → Other. A dialog box appears.
Type Database table into the filter box. ADT narrows down the list of objects that you can
select.
Double-click the Database Table entry to create the table.
When you create a table, you must give it a name and assign it to a package.
In the Basic Table Attributes figure, the prefix Z stands for the customer namespace and A for
active table. This is the table in which saved data is stored. Later on, there will be a similar
table ZDCONNECTION, in which D stands for draft data. This will allow users to store
incomplete data and to resume processing it at a later date.
Choose Next. ADT prompts you for a transport request. Choose a suitable request and
choose Finish.
In the table definition, you specify the names and data types of each of the columns of the
table. To do this, you use the notation field_name : data_type ;.
The data type can be a built-in ABAP Dictionary type - for this, the notation is abap.data_type.
If the data type is incomplete, you provide the length in parentheses after the data type - for
example, abap.char(10). For numeric types with a fixed number of decimal places you would
write, for example, abap.dec(15,2). This defines a column of type DEC with 15 digits including
two decimal places.
If you do not use a built-in ABAP Dictionary type to specify the type of a table column, use a
data element instead. Data elements are ABAP Dictionary objects that define the type of a
single field such as UUID, or airline code. Data elements contain not only a technical type
description but also semantic information such as field labels. If you use data elements to
define the types of the table columns, the system can use the field labels automatically on the
generated user interface.
The data element in the Data Element figure describes the airline field. It is a character field
with length 3; the system will use the field labels automatically on the generated UI.
The technical type of a data element is usually specified by a domain. Here you can see that
the domain for an airline code defines a character field with length 3 characters.
The data element /DMO/CITY describes the data type for the columns city_from and city_to
of the database table. However, its field label - the text that will appear in the user interface - is
just the word "city". This will make it impossible to distinguish which city is the departure city
of the flight, and which is the destination city.
To improve the user experience, it is better not to use the data element /DMO/CITY in this
case. Instead, you create a new data element for the departure city and another for the
destination and define the corresponding field labels in each case. As well as the semantic
information, you must specify a data type for the new data elements. You do this by assigning
a domain. Here, you can re-use the domain /DMO/CITY, since departure city and destination
city should have the same technical attributes.
To create a new data element, choose File → New → Other, then type data element into the
Filter Text field. Enter a package name and the name of the data element, then choose Next.
Select a transport request and choose Finish.
You must then specify a data type for the data element. This should be a domain - the
example re-uses the existing domain /DMO/CITY. Note that both data elements use the same
domain. This means that if someone subsequently changes the length of the domain, the
change applies to all data elements that use it.
Finally, you enter short, medium, and long field labels, and a label that is used for column
headings. Later on, when you generate the Fiori Elements app, the system will use these texts
automatically.
Each table that you define must have a primary key. This is a sequence of fields at the
beginning of the table description that identifies each entry in the table uniquely.
The first field in the table (and therefore also the first field in the key definition) must be the
client field, and this must have the data type abap.clnt. Furthermore, it is convenient for our
example to use a UUID for the unique key. If you use the data element sysuuid_x16 to specify
the type of this field, you can let the RAP runtime assign the UUID automatically when you
create a new record.
For traceability reasons it is recommended to store some administrative information with the
data, like, for example, the user that created or changed the data and timestamps for the
creation and the last change. The database table that you create should contain the fields
displayed in the figure.
Note:
These fields are mandatory for the generator we are going to use later. For that
generator to work properly the fields also have be defined with the data types
listed here.
Especially fields local_last_changed_at and last_changed_at are not only administrative fields.
In RAP they are also used for concurrency control.
Because RESTful applications are stateless, data consistency cannot be ensured by exclusive
locks alone. RAP uses a mixture of exclusive locks and ETags to avoid data inconsistencies.
An ETag is a field that changes its value whenever a data set is updated. By comparing the
value of the ETag field, the framework can ensure that a data set has not been changed since
it was read the last time. Timestamps of the last change make perfect ETag fields.
When you activate a database table in ADT, the system creates the corresponding physical
table in the database. Once you have done this, you will be able to generate the additional
objects that you need for your app automatically.
2. Use the domain /dmo/city to specify the type of the data element.
3. For each of the texts (short, medium, long, heading), enter a suitable text to describe the
departure city.
5. Create another data element called Z##_CITY_TO. For the data type, use the
domain /dmo/city again. For the field labels, use the word To.
b) Type Data e into the filter field. The system reduces the hit list to Data Element.
d) Ensure that your package is set to ZS4D400_##. Enter the name Z##_CITY_FROM and
a description. Choose Next.
2. Use the domain /dmo/city to specify the type of the data element.
a) In the Type Name field, enter /dmo/city.
3. For each of the texts (short, medium, long, heading), enter a suitable text to describe the
departure city.
a) Enter Departure in the relevant fields.
5. Create another data element called Z##_CITY_TO. For the data type, use the
domain /dmo/city again. For the field labels, use the word To.
a) Repeat the exercise from step 1 to 4 using the data element name Z##_CITY_TO and
the field label Destination instead of Departure.
In this series of exercises, you will create a Fiori Elements app for managing flight
connections. The first step is to create the database table that will store the data.
3. For the field city_from, replace the data element /dmo/city with your own data
element z##_city_from. For the city_to field, replace the data element /dmo/city
with your own data element z##_city_to.
In this series of exercises, you will create a Fiori Elements app for managing flight
connections. The first step is to create the database table that will store the data.
b) In the Filter field, enter Database. The system filters the list of object types so that
only object types containing the word Database are displayed.
d) Ensure that the correct package (ZS4D400_##) is set. Enter the name Z##ACONN and a
description for your table (suggestion: Group ## Active Connections). Choose
Next.
3. For the field city_from, replace the data element /dmo/city with your own data
element z##_city_from. For the city_to field, replace the data element /dmo/city
with your own data element z##_city_to.
a) The definition of the city_from and city_to fields should now look like the following:
city_from : z##_city_from;andcity_to : z##_city_to;.
The generated objects contain all of the information that is necessary to provide a working
app with create, read, update, and delete capability. Later on, we will also adjust and extend
some of these objects to change the appearance of the user interface and to implement some
checks and calculations.
Watch this video to learn how to generate additional objects.
To start the object generator, right-click the table name in the Project Explorer and choose
Generate ABAP Repository Objects. The wizard starts and you must enter a package to which
all of the new objects will be assigned. You then select the generator; for this example we are
using the ABAP RESTful Application Programming Model UI Service.
In the RESTful Application Programming Model, you do not access database tables directly.
Instead, you use a CDS view entity to define the data model. At this stage, in the generator,
you enter the name of a data definition. Since you are working in the customer namespace,
the name must begin with Z or Y or a reserved namespace. A further convention is that the
letter Z or Y is followed by an underscore, then the letter R for restricted, and then another
underscore.
You also have to enter an alias name, which is used inside the generated application to
identify the entity that the data definition represents.
With the data mode view that you create, you can read data from the database. However, our
app should also be able to create, modify, and delete data. In order to make this possible, you
must define a behavior definition. This is linked to the data mode view and specifies which of
the create, update, and delete actions are allowed. For example, some kinds of documents
must not be altered once they have been created. In this case, you would allow the create
action, but not update or delete.
As well as specifying which of the create, update, and delete actions should be available, the
behavior definition can also contain the following kinds of definitions:
● Draft enabling
● Numbering: Automatic numbering is enabled for the UUID field
● Validations: These are checks that are carried out when the user enters data in the app
● Determinations: A determination performs a calculation to fill fields in the data record.
The generator creates the behavior definition and switches on automatic numbering for the
UUID field. If you need to use validations and determinations, you must add them by hand.
The behavior definition declares which validations and determinations exist. However, they
also need an ABAP implementation. The implementations are methods, so you therefore
need an implementation class. The naming convention for this class is to use the prefix Z or Y
for the customer namespace, followed by BP_. BP stands for behavior pool.
The behavior definition also defines the draft enabling for the entity. Based on the definition of
the data model view, the generator creates a corresponding table that will contain the draft
data. You therefore need to specify the name of the draft table at this point. The naming
convention is that for a basic table ZA<table>, the draft table should be called ZD<table>.
The data model and its behavior definition are a reusable implementation of a particular
business entity. The next step in the object generation is to specify the name of the service
projection. The projection contains a view with precisely the fields that are required for a
particular app, a behavior definition that specifies which of the defined behaviors should
actually be available in the app, and a metadata extension. The metadata extension contains
CDS annotations that define how the UI of the app should look.
The naming convention for the projection layer is Z_C_<name>. The projection view, behavior
projection, and metadata extension will all have the same name.
In order to expose the app, you need to create a service definition and a service binding. the
service definition specifies the projection view to be exposed in this service, the service
binding specifies the protocol to be used. In our example define an OData UI service based on
version 4 of the OData protocol.
The naming convention for the service definition is Z<name>. The naming convention for the
service binding is ZUI_<name>_O4. This indicates that the binding is for a Fiori Elements app
and that it uses version 4 of the OData protocol.
At the end of the wizard, the system displays a list of all of the objects that it is going to
generate. You can check your entries against the naming conventions and look out for typing
errors and, if necessary, go back and change any that are incorrect.
1. Start the object generator in ABAP Development Tools. Ensure that the package is set to
your own package, enter a description for the generated objects, and set the generator to
generate a UI service.
1. Start the object generator in ABAP Development Tools. Ensure that the package is set to
your own package, enter a description for the generated objects, and set the generator to
generate a UI service.
a) Right-click the table name in the Project Explorer and choose Generate ABAP
Repository Objects.
c) In the Generator field, open the dropdown list box and select ABAP RESTful Application
Programming Model: UI Service.
d) Choose Next.
a) On the left-hand side of the dialog box, click Data Model. In the right-hand pane, enter
the Data Definition Name Z##_R_Connection and Alias Name Connection.
b) On the left-hand side of the dialog box, click Behavior. In the right-hand pane, enter the
Implementation Class ZBP_##_CONNECTION and the Draft Table Name Z##DCONN.
c) On the left-hand side of the dialog box, click Service Projection. In the right-hand pane,
enter the Name Z##_C_Connection.
d) On the left-hand side of the dialog box, click Service Definition. In the right-hand pane,
enter the Name Z##_CONNECTION.
e) On the left-hand side of the dialog box, click Service Binding. In the right-hand pane,
enter the Name Z##_UI_Connection_O4 and select the Binding Type OData V4 -
UI.
You must publish the service before you can test the app. To do so, open the service binding
and choose Publish. Once you have done this, the entity that you created (in this case,
Connection) appears in the list of entities. Mark it and choose Preview to test the app.
When you choose Preview, a browser window opens and you can create a new flight
connection. However, the app does not implement any checks other than type checks (for
example, only digits are allowed in the Flight Number field).
LESSON SUMMARY
You should now be able to:
● Create a database table
● Generate the RAP Objects for an OData UI service
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Implement the behavior of a RAP Business Object
Validations
As shown in the figure, in an RAP data model, the key of a database table is often made up of
the client field and a UUID field, whose value is assigned automatically by the RAP runtime
when you create a new instance of the business object. This field combination is sufficient to
ensure that the system can identify each record in the table uniquely. However, as well as this
technical key, our object also has a semantic key - in this case the combination of airline and
flight number, which must also be unique according to the business logic. In order to ensure
the uniqueness of this field combination, you must implement your own check in the form of a
validation.
You declare validations in the behavior definition of the CDS view entity, and implement them
in the behavior implementation class.
To prevent this from happening, you define further validations in the behavior definition, and
implement them in the behavior implementation class.
1. Choose File → New → Other… and type message into the filter field.
2. Double-click the Message Class entry in the hit list, then enter a package, name, and
description for the new message class. Choose Next.
Messages can also contain placeholders, which are replaced with concrete values when the
message is displayed. Placeholders are denoted by the ampersand symbol followed by a
number. You can use up to four placeholders in each message.
When you define the validation in the behavior definition, a warning tells you that the
corresponding method does not exist. Use a quick fix (key combination CTRL + 1) to add the
method to the behavior implementation. The behavior implementation is a local class within
your behavior pool. The method definition contains the addition FOR VALIDATE ON SAVE,
which identifies it as the implementation of the validation. It has an importing parameter
KEYS. This is an internal table containing the keys of the created or changed objects. You use
these to read the actual data that the user has entered.
The addition FOR Connection~CheckSemanticKey links the method with the validation
CheckSemanticKey from the behavior definition. Here, Connection is the alias name of the
view entity Z_R_CONNECTION.
When you define a validation, you must also create its implementation. This is a method in the
behavior pool. The easiest way to do this is to use a quick fix. Position the cursor on the name
of the validation and press CTRL + 1. ADT proposes to create the method. Double-click the
proposal to create the method.
Animation
For more information on this topic please view the animation in the lesson
Adding ABAP logic in your online course.
Note:
Some code examples in this section use SELECT statements inside of loops. This
has been done to keep the examples simple. Note that SELECTs in loops can
cause performance problems and should be avoided.
When the system triggers a validation, it calls the corresponding implementation. The
importing parameter KEYS contains the keys of the data records that have been changed. You
use the keys to read the fields of the records that you need using Entity Manipulation
Language (EML). EML is a special set of statements in ABAP that allows you to address
business objects.
Once you have read the data, you can perform the checks that you need. If the check fails, you
will need to issue an appropriate error message and, importantly, tell the RAP framework not
to write the changes to the database.
The first task in a validation is to read the user input. You do this using the Entity Manipulation
Language (EML) statement READ ENTITIES. The keys of the new data records are passed to
the validation using the importing parameter keys.
The fields that you need to validate the semantic key are CarrierID for the airline and
ConnectionID for the flight number. You also need the UUID field.
The code snippet uses the corresponding operator and an inline declaration for the result set.
Below, you see the equivalent code using explicitly-defined variables, which makes it easier to
understand the types that are used.
Once you have read the user input, you can use the values of CarrierID and ConnectionID to
see if this semantic key has already been used in another data set than the one you are
processing just now. Since the key combination could be in either the active table or the draft
table, you need to look in both, and the most efficient way to do this is with a union.
The result set of this query should always be empty. If not, there are more records with the
same combination of CarrierID and ConnectionID, this means that the record that the user is
currently trying to create is a duplicate, and must be rejected.
If the combination of carrier ID and connection ID already exists, there will be an entry in the
table check_result. In this case, you must issue a message.
The first step is to create a message object. You do this using the self-reference me and
calling the method new_message( ). The parameters ID, number, and severity are mandatory.
ID is the name of the message class that contains the message; number is the message
number. Severity classifies the message as a success, information, warning, or error
message. The behavior implementation class contains a structured constant mswhose
components represent the different severity levels. In this case, you need the severity level
ms-error.
The method also has optional importing parameters v1, v2, v3, and v4. You use these to
replace placeholders with concrete values. In this example, the placeholder &1 is replaced
with the airline code, placeholder &2 is replaced with the flight number.
The result of the method call is an object reference. In the next step, you will pass the object
to the RAP runtime so that the error message is returned the OData service and displayed in
the app preview.
To make the RAP runtime display a message, you must report it using the reported structure.
This is an implicit changing parameter of all validation methods and is a deep structure. It
contains a component with the alias name of the business object. This component is an
internal table.
To report the message, you must do three things:
1. Add the key of the affected record to the internal table. You can do this using the field
group %tky. When you group fields like this, you can address the name of the group
instead of having to address each field individually.
2. Attach the message object to the table. You do this by assigning the object reference of
the message object to the %msg component of the internal table.
3. Bind the message to the affected field. This ensures that the field is emphasized in the
app. This, in turn, helps the user to navigate the app better. You do this using the
%element component of the internal table.
In this example, reported_record is a structure with the line type of the internal table reported-
connection. You fill the %tky component with the contents of the %tky field group in the data
record. This is the structure line that we used as a work area for the internal table containing
the data that the user entered. Next, you assign the message object that you created using
the new_message( ) method to the %msg component. Finally, to bind the message to the field
CarrierID, you use the structure %element. %element is a structure, and contains a
component for each element in the view entity. If you set a component to "true", the
corresponding input field will be highlighted in the app. You do this using the structured
constant if_abap_behv=>mk. This has the component on for checked/true and off for
unchecked/false.
Note:
You cannot use the global constants abap_true and abap_false at this point, as their data
types are not compatible.
As well as issuing the message, you must also tell the RAP runtime not to save the incorrect
data. To do this, you use the failed structure of the validation method. Failed is an implicit
changing parameter that is present in all validation methods.
To report a record as failed, add its field group %tky to the field group %tky of the internal
table failed-Connection.
The next validation checks that the airline that the user has entered actually exists. The first
step is to read the user input using the EML statement READ ENTITIES. This time, you only
need to read the field CarrierID.
The SELECT SINGLE statement reads data using the CDS view entity /dmo/i_carrier and
checks whether the given airline exists. If it does, the value of the global constant abap_true
('X') is placed in the field exists. If exists is initial following the SELECT statement, you must
issue a message, report it, and add the record to the failed structure as you did in the previous
example.
If the departure and arrival airports are the same, you must issue the corresponding message
and fill the reported and failed structures. The code extract shows the relevant coding to
create the message. The coding to fill the reported and failed structures is the same as in the
previous examples.
1. Define a validation CheckSemanticKey to check that a particular flight number has not
already been used.
3. Use the EML READ ENTITIES statement to read the data that the user entered. Ensure
that the fields CarrierID, and ConnectionID are read. Use an inline declaration for the
result set.
4. In a loop over the data that you just read, select the UUIDs of all other data sets with the
same combination of airline ID and flight number. Use a union to address the active and
the draft tables at the same time.
Note:
Make sure you only read other data sets, that is, exclude data sets with the
same UUID from the selection.
5. If the internal table check_result contains any records create a new message with
message classZS4D400, message number 001 and severity ms-error. Pass
connection-CarrierID to parameter v1 and connection-ConnectionID to
parameter v2.
6. Declare the structure reported_record with the type LIKE LINE OF reported-
connection. Add the record to the reported structure.
7. Declare the structure failed_record with the type LIKE LINE OF failed-
connection. Add the record to the failed structure and close the open IF and LOOP
control structures.
1. Define a validation CheckSemanticKey to check that a particular flight number has not
already been used.
a) In the Project Explorer, navigate to the behavior definition Z##_R_CONNECTION and
add the following line: validation CheckSemanticKey on save { create;
update; }.
3. Use the EML READ ENTITIES statement to read the data that the user entered. Ensure
that the fields CarrierID, and ConnectionID are read. Use an inline declaration for the
result set.
a) Enter the following code in the method implementation. Replace ## with your group
number:
4. In a loop over the data that you just read, select the UUIDs of all other data sets with the
same combination of airline ID and flight number. Use a union to address the active and
the draft tables at the same time.
Note:
Make sure you only read other data sets, that is, exclude data sets with the
same UUID from the selection.
a) Enter the following code in the method implementation. Replace ## with your group
number:
FIELDS uuid
WHERE carrierid = @connection-CarrierID
AND connectionid = @connection-ConnectionID
and uuid <> @connection-uuid
5. If the internal table check_result contains any records create a new message with
message classZS4D400, message number 001 and severity ms-error. Pass
connection-CarrierID to parameter v1 and connection-ConnectionID to
parameter v2.
a) Enter the following code in the method implementation:
DATA(message) = me->new_message(
id = 'ZS4D400'
number = '001'
severity = ms-error
v1 = connection-CarrierID
v2 = connection-ConnectionID
).
6. Declare the structure reported_record with the type LIKE LINE OF reported-
connection. Add the record to the reported structure.
a) Enter the following code in the method implementation:
reported_record-%tky = connection-%tky.
reported_record-%msg = message.
reported_record-%element-CarrierID = if_abap_behv=>mk-on.
reported_record-%element-ConnectionID = if_abap_behv=>mk-on.
7. Declare the structure failed_record with the type LIKE LINE OF failed-
connection. Add the record to the failed structure and close the open IF and LOOP
control structures.
a) Enter the following code in the method implementation:
failed_record-%tky = connection-%tky.
APPEND failed_record TO failed-connection.
ENDIF.
ENDLOOP.
1. Define a validation CheckCarrierID to check that the airline that the user entered exists.
3. Use the EML READ ENTITIES statement to read the data that the user entered. Ensure
that the field CARRID is read. Use an inline declaration for the result set.
4. In a loop over the data that you just read, check that the airline exists. Use a SELECT
statement that returns the literal abap_true if the airline exists. Use the CDS View /dmo/
i_carrier as the data source of the statement.
6. Declare the structure reported_record with the type LIKE LINE OF reported-
connection. Fill the fields in fields group %tky with the corresponding values of the
current data set, store the reference to the message object in field %msg, and link the
message to view element CarrierID. Finally, add the record to the reported structure.
7. Declare the structure failed_record with the type LIKE LINE OF failed-
connection. Fill the fields in fields group %tky with the corresponding values of the
current data set. Add the record to the failed structure and close the open IF and LOOP
control structures.
1. Define a validation CheckCarrierID to check that the airline that the user entered exists.
a) In the Project Explorer, navigate to the behavior definition Z##_R_CONNECTION and
add the following line: validation CheckCerrierID on save { create; field
CarrierID; }.
3. Use the EML READ ENTITIES statement to read the data that the user entered. Ensure
that the field CARRID is read. Use an inline declaration for the result set.
a) Enter the following code in the method implementation. Replace ## with your group
number:
4. In a loop over the data that you just read, check that the airline exists. Use a SELECT
statement that returns the literal abap_true if the airline exists. Use the CDS View /dmo/
i_carrier as the data source of the statement.
a) Enter the following code in the method implementation. Replace ## with your group
number:
SELECT SINGLE
FROM /DMO/I_Carrier
FIELDS @abap_true
WHERE airlineid = @connection-CarrierID
INTO @DATA(exists).
IF exists = abap_false.
DATA(message) = me->new_message(
id = 'ZS4D400'
number = '002'
severity = ms-error
v1 = connection-CarrierID
) .
6. Declare the structure reported_record with the type LIKE LINE OF reported-
connection. Fill the fields in fields group %tky with the corresponding values of the
current data set, store the reference to the message object in field %msg, and link the
message to view element CarrierID. Finally, add the record to the reported structure.
a) Enter the following code in the method implementation:
reported_record-%tky = connection-%tky.
reported_record-%msg = message.
reported_record-%element-carrierid = if_abap_behv=>mk-on.
7. Declare the structure failed_record with the type LIKE LINE OF failed-
connection. Fill the fields in fields group %tky with the corresponding values of the
current data set. Add the record to the failed structure and close the open IF and LOOP
control structures.
a) Enter the following code in the method implementation:
failed_record-%tky = connection-%tky.
APPEND failed_Record TO failed-connection.
ENDIF.
ENDLOOP.
3. Use the EML READ ENTITIES statement to read the data that the user entered. Ensure
that the fields AirportFromID and AirportToID are read. Use an inline declaration for
the result set.
4. In a loop over the data that you just read, check whether AirportFromID and
AirportToID are the same. If they are, create a message with id ZSD4D00, number 003,
and severity ms-error.
5. Declare the structure reported_record with the type LIKE LINE OF reported-
connection. Fill the fields in fields group %tky with the corresponding values of the
current data set, store the reference to the message object in field %msg, and link the
message to both airport fields. Finally, add the record to the reported structure.
6. Declare the structure failed_record with the type LIKE LINE OF failed-
connection. Fill the fields in fields group %tky with the corresponding values of the
current data set. Add the record to the failed structure and close the open IF and LOOP
control structures.
3. Use the EML READ ENTITIES statement to read the data that the user entered. Ensure
that the fields AirportFromID and AirportToID are read. Use an inline declaration for
the result set.
a) Enter the following code in the method implementation. Replace ## with your group
number:
4. In a loop over the data that you just read, check whether AirportFromID and
AirportToID are the same. If they are, create a message with id ZSD4D00, number 003,
and severity ms-error.
a) Enter the following code in the method implementation.
5. Declare the structure reported_record with the type LIKE LINE OF reported-
connection. Fill the fields in fields group %tky with the corresponding values of the
current data set, store the reference to the message object in field %msg, and link the
message to both airport fields. Finally, add the record to the reported structure.
reported_record-%tky = connection-%tky.
reported_record-%msg = message.
reported_record-%element-AirportFromID = if_abap_behv=>mk-on.
reported_record-%element-AirportToID = if_abap_behv=>mk-on.
6. Declare the structure failed_record with the type LIKE LINE OF failed-
connection. Fill the fields in fields group %tky with the corresponding values of the
current data set. Add the record to the failed structure and close the open IF and LOOP
control structures.
a) Enter the following code in the method implementation:
failed_record-%tky = connection-%tky.
APPEND failed_record TO failed-connection.
ENDIF.
ENDLOOP.
Determinations
In this section, you will learn how to complete the data using determinations.
You will first implement the determination. Afterwards, you will learn how to deactivate input
for the fields that will be filled automatically.
When the system triggers a determination, it calls the corresponding implementation. The
importing parameter KEYS contains the keys of the data records that have been changed. In
the determination method, you use EML to read the data based on the keys in exactly the
same way that you did in the validations. However, in a determination, you also manipulate
the data in the method and you must consequently update the data held by the RAP
framework using the EML statement UPDATE.
Animation
For more information on this topic please view the animation in the lesson
Adding ABAP logic in your online course.
Note:
Some code examples in this section use SELECT statements inside of loops. This
has been done to keep the examples simple. Note that SELECTs in loops can
cause performance problems and should be avoided.
At the beginning of the determination you read the user input using EML. You need the
AirportFromID and AirportToID fields and will use these to complete the city and ciountry
information.
The demonstration data model provides a CDS view entity /dmo/i_airport that you can use to
read the city and country in which a particular airport is located. The example uses the variant
of the INTO clause in which you explicitly specify the fields of the structure that you want to
fill. Remember that the changes to the data are in the work area of the internal table and that
you must return them to the table itself using the MODIFY statement.
Figure 223: Copying the Data to the Internal Table for Update
The READ ENTITIES statement returns an internal table with the derived type FOR READ
RESULT. To change the data in the transactional buffer, you need a MODIFY ENTITIES
statement. You pass the data that you want to change to this statement using an internal
table with the derived type FOR UPDATE. The data fields are identical in both types, however
the FOR UPDATE table has an additional structure called %control that contains
administrative information.
You cannot pass the connections table to the MODIFY ENTITIES statement. You therefore
need to copy your data into an appropriately-typed internal table (connections_upd) before
you perform the actual modification.
To update the data with the fields that you filled in the determination, you use the MODIFY
ENTITIES statement. In it, you specify which fields should be updated in the FIELDS clause,
and pass the data in an internal table using the WITH addition. This table must have the
correct derived data type, which in this case would be TYPE TABLE FOR UPDATE
zsd4d400_r_connection.
The MODIFY ENTITIES statement can return messages, which you receive using the
REPORTED clause. You then propagate these messages to your own business object by
copying the contents of the internal table to the REPORTED structure of the determination
method.
1. Define a determination getCities to fill the city and country fields automatically based
on the airport code that the user entered.
3. Read the user input using an EML READ ENTITIES statement. Read the fields
AirportFromID and AirportToID. Use an inline declaration for the result set.
4. In a loop over the data, use two SELECT statements to read the city and country data for
the two airports the user entered. Use the CDS view /DMO/I_Airport as the data source
and read the fields City and CountryCode. For AirportFromID, fill the fields
CityFrom and CountryFrom. For AirportToID, fill the fields CityTo and CountryTo.
Remember that you need the MODIFY statement to write the changes back to the internal
table.
5. Declare an internal table connections_upd with the type TABLE FOR UPDATE
z##_r_connections where ## is your group number. Copy the data from the internal
table connections to the new table connections_upd.
6. Use an EML MODIFY ENTITIES statement to update the data in the transactional buffer.
Restrict the update to the fields that you modified (CityFrom CityTo CountryFrom
CountryTo). Use the REPORTED addition to receive any messages from the statement.
Transfer any messages to the reported structure of your method.
1. Define a determination getCities to fill the city and country fields automatically based
on the airport code that the user entered.
a) In the Project Explorer, navigate to the behavior definition Z##_R_CONNECTION and
add the following line: determination GetCities on save { field
airportFromID, AirportToID; }.
3. Read the user input using an EML READ ENTITIES statement. Read the fields
AirportFromID and AirportToID. Use an inline declaration for the result set.
a) Enter the following coding in the method implementation. Replace ## with your group
number.
4. In a loop over the data, use two SELECT statements to read the city and country data for
the two airports the user entered. Use the CDS view /DMO/I_Airport as the data source
and read the fields City and CountryCode. For AirportFromID, fill the fields
CityFrom and CountryFrom. For AirportToID, fill the fields CityTo and CountryTo.
Remember that you need the MODIFY statement to write the changes back to the internal
table.
a) Enter the following coding in the method implementation:
SELECT SINGLE
FROM /DMO/I_Airport
FIELDS city, CountryCode
WHERE AirportID = @connection-AirportFromID
INTO ( @connection-CityFrom, @connection-CountryTo ).
SELECT SINGLE
FROM /DMO/I_Airport
FIELDS city, CountryCode
WHERE AirportID = @connection-AirportToID
INTO ( @connection-CityTo, @connection-CountryTo ).
ENDLOOP.
5. Declare an internal table connections_upd with the type TABLE FOR UPDATE
z##_r_connections where ## is your group number. Copy the data from the internal
table connections to the new table connections_upd.
a) Enter the following coding in the method implementation:
6. Use an EML MODIFY ENTITIES statement to update the data in the transactional buffer.
Restrict the update to the fields that you modified (CityFrom CityTo CountryFrom
CountryTo). Use the REPORTED addition to receive any messages from the statement.
Transfer any messages to the reported structure of your method.
a) Enter the following coding in the method implementation:
LESSON SUMMARY
You should now be able to:
● Implement the behavior of a RAP Business Object
LESSON OBJECTIVES
After completing this lesson, you will be able to:
● Arrange Fields in the App
● Provide input help
Adjusting the UI
In this section, you will learn how to control field properties in the behavior definition and how
to adjust the UI in the metadata extension.
Animation
For more information on this topic please view the animation in the lesson
Improving the User Experience in your online course.
When you generate a Fiori Elements app, the system creates a default user interface.
Technical fields such as the UUID, and administrative fields such as the timestamps and user
IDs are read-only and hidden by default. All other fields are displayed input-ready in the
sequence in which they appear in the CDS entity.
In the flight connection example, you can make the information easier to read by changing the
order of the fields. You can also make the fields that are filled automatically by the
determination read-only.
You make fields read-only in the behavior definition or the behavior projection. It depends o
whether you want them to be read-only in general or only for your particular service.
In our example, we make the city and country fields read-only on the data model level because
their values should always be provided by the determination. If we kept them editable we
would have to implement a validation to make sure the cities and countries match the airport
IDs.
To define a list of fields as read-only, use the field ( readonly ) statement with a comma-
separated list of fields. You end the list with a semicolon after the last field.
Metadata extensions are standalone repository objects that contain the annotations
belonging to a particular CDS entity. The idea behind them is to make it easier to understand
both the definition of the CDS entity and the annotations by separating them. Otherwise, the
CDS definition would become very long and difficult to read.
If you want to use a metadata extension in conjunction with a CDS view, the CDS view must
contain the annotation @Metadata.allowExtensions: true. The RAP object generator
takes care of this for you and automatically generates both a metadata extension for the
projection view and the corresponding annotation in the projection view itself.
There are two important UI annotations that you might need to change in the metadata
extension. The first is @UI.LineItem. This is an array in which you set the attribute position.
This determines the position of the column in the report list page of the app.
The second annotation is @UI.identification. This is, again, an array in which you also set
the attribute position. This determines the position of the field in the single object page. that is
displayed during the create or update operation.
To change the position of the fields, you assign a new value to the attribute
@UI.identification: [ { position: } ]. To rearrange the fields so that they are
displayed as in the "After" figure, you must change the position of the AirportToID field from
60 to 40, the position of the CityFrom field from 40 to 50, the position of the CountryFrom
field from 50 to 70, and so on.
1. In the behavior definition of your projection set the fields CityFrom, CityTo,
CountryFrom, and CountryTo to read-only.
2. Rearrange the fields in the app so that they appear in the following order:
10 CarrierID
20 ConnectionID
30 AirportFromID
40 AirportToID
50 CityFrom
60 CityTo
70 CountryFrom
80 CountryTo
1. In the behavior definition of your projection set the fields CityFrom, CityTo,
CountryFrom, and CountryTo to read-only.
a) Open the behavior definition Z##_R_CONNECTION (where ## is your group number.
b) Enter the following code in the behavior definition. The precise position is not
important. We suggest that you place it immediately after the existing field
statement:
field ( readonly )
CityFrom,
CountryFrom,
CityTo,
CountryTo;
2. Rearrange the fields in the app so that they appear in the following order:
10 CarrierID
20 ConnectionID
30 AirportFromID
40 AirportToID
50 CityFrom
60 CityTo
70 CountryFrom
80 CountryTo
@Metadata.layer: #CUSTOMER
@UI: {
headerInfo: {
typeName: 'Connection',
typeNamePlural: 'Connections'
}
}
annotate view ZS4D400_C_CONNECTION with
{
@UI.facet: [ {
id: 'idIdentification',
type: #IDENTIFICATION_REFERENCE,
label: 'Connection',
position: 10
} ]
@UI.hidden: true
UUID;
@UI.lineItem: [ {
position: 10 ,
importance: #MEDIUM,
label: ''
} ]
@UI.identification: [ {
position: 10 ,
label: ''
} ]
CarrierID;
@UI.lineItem: [ {
position: 20 ,
importance: #MEDIUM,
label: ''
} ]
@UI.identification: [ {
position: 20 ,
label: ''
} ]
ConnectionID;
@UI.lineItem: [ {
position: 30 ,
importance: #MEDIUM,
label: ''
} ]
@UI.identification: [ {
position: 30 ,
label: ''
} ]
AirportFromID;
@UI.lineItem: [ {
position: 40 ,
importance: #MEDIUM,
label: ''
} ]
@UI.identification: [ {
position: 50 ,
label: ''
} ]
CityFrom;
@UI.lineItem: [ {
position: 50 ,
importance: #MEDIUM,
label: ''
} ]
@UI.identification: [ {
position: 70 ,
label: ''
} ]
CountryFrom;
@UI.lineItem: [ {
position: 60 ,
importance: #MEDIUM,
label: ''
} ]
@UI.identification: [ {
position: 40 ,
label: ''
} ]
AirportToID;
@UI.lineItem: [ {
position: 70 ,
importance: #MEDIUM,
label: ''
} ]
@UI.identification: [ {
position: 60 ,
label: ''
} ]
CityTo;
@UI.lineItem: [ {
position: 80 ,
importance: #MEDIUM,
label: ''
} ]
@UI.identification: [ {
position: 80 ,
label: ''
} ]
CountryTo;
@UI.hidden: true
LocalLastChangedAt;
}
In this section, we will learn about providing value help to the users. Watch this video to learn
more.
A feature of all of SAP's user interface solutions over the years has been value help, which
gives the user the possibility to display a list of possible values that can be entered in a field.
Typically, this is achieved by reading data from the database. In Fiori Elements, you can
provide a value help by defining a CDS View that displays the possible values and attaching it
to a field in the app.
This CDS view selects data from the database table /DMO/CARRIER, which contains a list of
airlines. It returns two columns - the airline code and its name. These columns will form the hit
list of the value help.
The view also contains @UI.lineItem annotations. These define how the hit list will look on
the screen - in this case with the airline code first and its name second.
To provide a value help, you attach the view that defines the hit list to a field of your projection
view using an annotation. The system then displays the field with a value help button. When
the user clicks the button, the system reads the possible values using the view and displays
the hit list accordingly.
When the user chooses an entry from the hit list, the system must return a single value to the
field. In the annotation, you must also specify which field of the value help hit list should be
placed in the input field.
3. Ensure that the view contains only the fields carrier_id (with alias CarrierID) and
name (with alias Name).
4. Add @UI.lineItem annotations so that the system displays the data when the user
starts the value help.
6. In the projection view Z##_C_CONNECTION (where ## is your group number) attach the
value help to the field CarrierID. Use the annotation
@Consumption.valueHelpDefinition. For the attribute name use the CDS View Entity
Z##_I_CarrierVH, and for the attribute element, use the field CarrierID.
b) Type data into the filter field to restrict the number of objects offered.
d) Ensure that the package is correct, enter the name Z##_I_CarrierVH where ## is
your group number. Enter a description and the referenced object /DMO/AIRLINE.
e) Choose Next.
3. Ensure that the view contains only the fields carrier_id (with alias CarrierID) and
name (with alias Name).
a) Delete the fields currency_code to last_changed_at. The view definition should
now look like this:
4. Add @UI.lineItem annotations so that the system displays the data when the user
starts the value help.
a) Add the annotations so that your source code looks like this:
@UI.lineItem: [{position: 10 }]
key carrier_id as CarrierID,
@UI.lineItem: [{position: 20 }]
name as Name
6. In the projection view Z##_C_CONNECTION (where ## is your group number) attach the
value help to the field CarrierID. Use the annotation
@Consumption.valueHelpDefinition. For the attribute name use the CDS View Entity
Z##_I_CarrierVH, and for the attribute element, use the field CarrierID.
a) Open the data definition Z##_C_CONNECTION and add the following annotation before
the element CarrierID. Replace ## with your own group:
@Consumption.valueHelpDefinition:
[{ entity: { name: 'ZS4D400_I_CarrierVH',
element: 'CarrierID'
}
}]
CarrierID,
LESSON SUMMARY
You should now be able to:
● Arrange Fields in the App
● Provide input help
Learning Assessment
1. Which of the following can you use to specify the data type of a column in a database
table?
Choose the correct answer.
X A Domain
X B Data element
X C Local type
2. When you create a database table to generate a RAP application, you must create a client
field. Which data type must it have?
Choose the correct answer.
X A Draft enabling
X B Validations
X C Determinations
X A In a text pool.
X B In a global class.
X C In a message class.
5. When you create a validation, what does the system generate automatically?
Choose the correct answer.
6. In the validation, you use the READ ENTITIES statement to read the data entered by the
user. Which parameter of the validation method do you use to ensure that the correct data
is retrieved?
Choose the correct answer.
X A REPORTED
X B FAILED
X C KEYS