Are you tired of taking the secondary road to your destination? You might feel like this if you're writing
DO
or
WHILE
statements with a
READ ... INDEX
to loop backwards across the lines of an internal table in ABAP. And if your internal table is a hashed table... you're almost good to go to turn around. The good thing in brief: You've been heard! With SAP BTP ABAP Environment 2202 the new addition
STEP
is introduced.
STEP
combines the loop order with the step size – you're getting two in one. If you want to take the main road to your destination, follow this blog to get most out of the new addition.
Basics
While the main use case for
STEP
might be the
LOOP
statement, there're more statements, expressions, and operators to use the addition with. But first, we'll look at the usage of
STEP
itself: The loop order and step size are defined by
STEP n
where
n
has a positive or negative sign for the loop order and where
n
is a numeric expression position of operand type
i
for the step size. For example, if you wanted to loop across every second table line in a backward order, you'd simply write
STEP -2
. Other statements, expressions, and operators to use
STEP
with are:
FOR ... IN
,
DELETE
,
INSERT
,
APPEND
,
NEW
, and
VALUE
. We'll go through them step by step. (Want a quick overview? Scroll down.)
Looping across an internal table
The
LOOP
statement and the
FOR ... IN
expression can be used with the keywords
USING KEY
,
FROM
,
TO
, and
WHERE
. Together with
USING KEY
, the processing order of the internal table can be defined. Together with
FROM
,
TO
, or
WHERE
, the subset of lines can be specified. Combining the additions
STEP
and
WHERE
is possible with the
LOOP
statement and the
FOR ... IN
expression. In this case, the value of
n
can either be
1
or
-1
. A reverse processing order can be achieved by the
LOOP
statement and the
FOR ... IN
expression as well. When combining the additions
FROM
and
TO
with
STEP
, both row numbers need to be adjusted according to the value of
n
: If the value of
n
is less than 0, the row number of
FROM
needs to be greater than the row number of
TO
or equal to
TO
, for example,
FROM 3 TO 1 STEP -1
.
Deleting lines of an internal table
The
DELETE
statement can be used with the same keywords as mentioned above:
USING KEY
,
FROM
,
TO
, and
WHERE
. The difference is that it's possible to define the step size but it's not possible to reverse the processing order for the
DELETE
statement using
STEP
. Combining the additions
STEP
and
WHERE
is not possible because at runtime it wouldn't be clear which condition should be evaluated first.
Adding lines of an internal table to a target table
The
APPEND
and
INSERT
statements can be combined with the additions
FROM
,
TO
, and
USING KEY
. Adding multiple lines of an internal table to a target table, the same syntax and effect applies as for
LOOP
except that the value of
n
cannot be negative and no reverse processing order is possible with
STEP
.
Adding lines of an internal table to construct a table
The
VALUE
and
NEW
operators can be used with the same keywords as mentioned above:
FROM
,
TO
, and
USING KEY
. Adding multiple lines of an internal table when constructing an internal table, the same syntax and effect applies as for
LOOP
except that the value of
n
cannot be negative and no reverse processing order is possible with
STEP
.
Use case
Let's look at some small examples regarding purchase orders and customer benefits and see how
STEP
can be used with all the statements, expressions, and operators mentioned above.
Purchase order
Imagine that one part of your work concerns customers and their purchase orders. For inquiries, you've prepared a joined table with customer data and purchased item data. This internal table has two customer-related columns that are connected to a customer table and three purchase-related columns that are connected to an order table. The internal table could look as follows.
Customer ID |
Customer Name |
Item ID |
Purchase Date |
Processing Date |
00000001 |
Customer A |
781029348 |
08.11.2021 |
09.11.2021 |
00000001 |
Customer A |
781028275 |
17.11.2021 |
18.11.2021 |
00000001 |
Customer A |
781029350 |
03.12.2021 |
06.12.2021 |
00000002 |
Customer B |
781029348 |
07.12.2021 |
08.12.2021 |
00000003 |
Customer C |
781029353 |
15.12.2021 |
16.12.2021 |
00000004 |
Customer D |
781029321 |
15.12.2021 |
16.12.2021 |
00000005 |
Customer E |
781029342 |
16.12.2021 |
17.12.2021 |
In the first code section below, we define our playground for the use case. Each example of the use case is represented by a method. The
customer_purchase
type definition is used for the internal table of customer data and the constant
co_example_customer
is used to represent a specific customer.
CLASS purchase_orders DEFINITION.
PUBLIC SECTION.
TYPES:
BEGIN OF customer_purchase,
cust_id TYPE c LENGTH 8,
cust_name TYPE string,
item_id TYPE c LENGTH 9,
purch_date TYPE datn,
proc_date TYPE datn,
END OF customer_purchase,
customer_purchases TYPE STANDARD TABLE OF customer_purchase
WITH EMPTY KEY.
METHODS constructor.
METHODS purchase_order.
METHODS quarterly_order.
METHODS discount.
METHODS tombola.
PRIVATE SECTION.
CONSTANTS co_example_customer TYPE c LENGTH 8 VALUE '00000001'.
DATA m_customer_purchases TYPE purchase_orders=>customer_purchases.
ENDCLASS.
CLASS purchase_orders IMPLEMENTATION.
"...
ENDCLASS.
START-OF-SELECTION.
DATA(inquiry) = NEW purchase_orders( ).
inquiry->purchase_order( ).
inquiry->quarterly_order( ).
inquiry->discount( ).
inquiry->tombola( ).
cl_demo_output=>display( ).
In the following sample code, we create an internal table and fill it with values to mimic the example scenario. The table and its values are needed for all examples.
CLASS purchase_orders IMPLEMENTATION.
METHOD constructor.
m_customer_purchases = VALUE customer_purchases(
( cust_id = '00000001' cust_name = `Customer A` item_id = '781029348'
purch_date = `20211108` proc_date = `20211109` )
( cust_id = '00000001' cust_name = `Customer A` item_id = '781028275'
purch_date = `20211117` proc_date = `20211118` )
( cust_id = '00000001' cust_name = `Customer A` item_id = '781029350'
purch_date = `20211203` proc_date = `20211206` )
( cust_id = '00000002' cust_name = `Customer B` item_id = '781029348'
purch_date = `20211207` proc_date = `20211208` )
( cust_id = '00000003' cust_name = `Customer C` item_id = '781029353'
purch_date = `20211215` proc_date = `20211216` )
( cust_id = '00000004' cust_name = `Customer D` item_id = '781029321'
purch_date = `20211215` proc_date = `20211216` )
( cust_id = '00000005' cust_name = `Customer E` item_id = '781029342'
purch_date = `20211216` proc_date = `20211217` ) ).
ENDMETHOD.
"...
ENDCLASS.
Imagine now that you receive an inquiry and need to extract all entries of the customer with the ID 00000001, listed from the newest date to the oldest date. For this scenario, you can simply loop backwards with the
STEP
addition and a
WHERE
condition. The syntax may look like this:
LOOP AT itab ASSIGNING FIELD-SYMBOL(<fs>) STEP -1 WHERE cust_id = '00000001'
.
CLASS purchase_orders IMPLEMENTATION.
"...
METHOD purchase_order.
TYPES:
BEGIN OF order,
tabix TYPE sy-tabix,
item_id TYPE c LENGTH 9,
purch_date TYPE datn,
proc_date TYPE datn,
END OF order.
DATA orders TYPE TABLE OF order.
LOOP AT m_customer_purchases REFERENCE INTO DATA(customer_purchase)
STEP -1 WHERE cust_id = co_example_customer.
orders = VALUE #( BASE orders
( tabix = sy-tabix item_id = customer_purchase->item_id
purch_date = customer_purchase->purch_date
proc_date = customer_purchase->proc_date ) ).
ENDLOOP.
cl_demo_output=>write( |Orders of { m_customer_purchases[ cust_id =
co_example_customer ]-cust_name } ({ co_example_customer })| ).
cl_demo_output=>write( orders ).
ENDMETHOD.
"...
ENDCLASS.
Of course, this would work in other ways too, for example, with a
SORT
statement. But look at the results, if you don't use the addition
STEP
.
Orders of Customer A (00000001)
TABIX |
ITEM_ID |
PURCH_DATE |
PROC_DATE |
5 |
781029350 |
2021-12-03 |
2021-12-06 |
6 |
781028275 |
2021-11-17 |
2021-11-18 |
7 |
781029348 |
2021-11-08 |
2021-11-09 |
You'll get the above result when applying the
SORT
statement and the one below when using the
STEP
addition. The important difference is evident in the column
TABIX
which represents the
sy-tabix
. Above, the
sy-tabix
starts with 5 and ends with 7. Below, the
sy-tabix
starts with 3 and ends with 1. Meaning that the addition
STEP
doesn't change the actual sort order of the internal table, but the processing order of the current loop (with its sort order).
Looking at the result of the example, you'll get information about the customer and their orders sorted from last to first. The
sy-tabix
is added here to emphasize the processing order.
Orders of Customer A (00000001)
TABIX |
ITEM_ID |
PURCH_DATE |
PROC_DATE |
3 |
781029350 |
2021-12-03 |
2021-12-06 |
2 |
781028275 |
2021-11-17 |
2021-11-18 |
1 |
781029348 |
2021-11-08 |
2021-11-09 |
The result lists all entries for the customer with the ID 00000001 from the newest to the oldest date. A precondition is that the table is filled in an appending logic; the most recent purchase order is the latest entry. As mentioned above, bear in mind that
STEP
can only have the value of
1
or
-1
when used together with
WHERE
. If you want to test the result using
STEP 1
, you don't have to write it because it's evaluated implicitly.
Quarterly orders
For a similar scenario, you want to list all purchase orders of all customers in a specific time period from the newest to the oldest date to store statistical data. This can be achieved simply by using a
FOR
expression together with the
STEP
addition. To make the quarterly selection as smooth as possible, additional code is added here.
CLASS purchase_orders IMPLEMENTATION.
"...
METHOD quarterly_order.
TYPES:
BEGIN OF order,
tabix TYPE sy-tabix,
item_id TYPE c LENGTH 9,
purch_date TYPE datn,
proc_date TYPE datn,
END OF order,
orders TYPE TABLE OF order WITH EMPTY KEY,
BEGIN OF quarter,
quarter_start TYPE datn,
quarter_end TYPE datn,
END OF quarter,
quarters TYPE TABLE OF quarter WITH EMPTY KEY.
DATA quarter_name TYPE string.
DATA(quarters) = VALUE quarters( ( quarter_start = `20211001`
quarter_end = `20211231` ) ).
DATA(quarter) = REF #( quarters[ lines( quarters ) ] ).
DATA(q_start) = quarter->quarter_start.
DATA(q_end) = quarter->quarter_end.
DATA(orders) = VALUE orders( FOR <fs> IN m_customer_purchases
INDEX INTO idx STEP -1 WHERE ( purch_date >= q_start AND purch_date <= q_end )
( tabix = idx item_id = <fs>-item_id
purch_date = <fs>-purch_date
proc_date = <fs>-proc_date ) ).
DATA(quarter_start_month) = substring( val = q_start off = 4 len = 2 ).
CASE quarter_start_month.
WHEN `01`.
quarter_name = `Quarter 1`.
WHEN `04`.
quarter_name = `Quarter 2`.
WHEN `07`.
quarter_name = `Quarter 3`.
WHEN `10`.
quarter_name = `Quarter 4`.
ENDCASE.
DATA(quarter_year) = substring( val = q_start len = 4 ).
cl_demo_output=>write(
|Orders of all customers in { quarter_name } { quarter_year }:| ).
IF orders IS NOT INITIAL.
cl_demo_output=>write( orders ).
ELSE.
cl_demo_output=>write( |No orders found.| ).
ENDIF.
ENDMETHOD.
"...
ENDCLASS.
This time, the
FOR
expression is used instead of the
LOOP
statement to identify all purchase orders of the last quarter and to see which orders were recently purchased and processed. Both options work the same way.
Executing the method returns a table with all orders from all customers of the fourth quarter of 2021. No customer-specific data is displayed here because the result might be processed for further calculations.
Orders of all customers in Quarter 4 2021:
TABIX |
ITEM_ID |
PURCH_DATE |
PROC_DATE |
7 |
781029342 |
2021-12-16 |
2021-12-17 |
6 |
781029321 |
2021-12-15 |
2021-12-16 |
5 |
781029353 |
2021-12-15 |
2021-12-16 |
4 |
781029348 |
2021-12-07 |
2021-12-08 |
3 |
781029350 |
2021-12-03 |
2021-12-06 |
2 |
781029350 |
2021-11-17 |
2021-11-18 |
1 |
781029350 |
2021-11-08 |
2021-11-09 |
Did you notice that the result table looks the same as the internal table
itab
with only one difference?
Discounts
Imagine now that at some point you want to give regular customers a discount for every third order. It's almost the year's end and you want to know how many discounts you've given to every customer because there'll be a special give-away for all customers who received at least one discount in the last quarter of the year. If we wanted to get a complete list of granted discounts, we'd simply apply a
LOOP AT ... GROUP BY
statement without needing the
STEP
addition. That's why we'll only take a look at a specific customer, like the one from the previous example with the ID 00000001.
CLASS purchase_orders IMPLEMENTATION.
"...
METHOD discount.
TYPES:
BEGIN OF order,
tabix TYPE sy-tabix,
item_id TYPE c LENGTH 9,
purch_date TYPE datn,
proc_date TYPE datn,
END OF order,
orders TYPE TABLE OF order WITH EMPTY KEY,
BEGIN OF discount_candidate,
tabix TYPE sy-tabix,
item_id TYPE c LENGTH 9,
purch_date TYPE datn,
proc_date TYPE datn,
END OF discount_candidate,
discount_candidates TYPE TABLE OF discount_candidate WITH EMPTY KEY.
DATA(orders) = VALUE orders( FOR <fs> IN m_customer_purchases
INDEX INTO idx STEP -1 WHERE ( cust_id = co_example_customer )
( tabix = idx item_id = <fs>-item_id
purch_date = <fs>-purch_date
proc_date = <fs>-proc_date ) ).
DELETE orders WHERE purch_date <= `20211001` OR purch_date >= `20211231`.
DATA(discounts) = VALUE discount_candidates( ( LINES OF orders FROM 3 STEP 3 ) ).
FINAL(is_discount_granted) = xsdbool( discounts IS NOT INITIAL ).
cl_demo_output=>write( |Discount of { m_customer_purchases[ cust_id =
co_example_customer ]-cust_name } ({ co_example_customer
}) granted: { is_discount_granted }| ).
ENDMETHOD.
"...
ENDCLASS.
The result is presented in a single sentence. A table-like result is applied in the next example of the use case. If you'd generate an output with the
sy-tabix
of the
discounts
table, the value
1
of the purchase date
2021-11-09
would be returned which again emphasizes that the table keeps the actual order despite being processed backwards.
Discount of Customer A (00000001) granted: X
These examples show use cases of the addition
STEP
for looping in reverse order and for looping with a step size greater than
1
, as well as combined. The next and last example for the use case is another usage of
STEP
for adding lines of an internal table.
Surprise tombola
After the year's end, you plan to give every customer who didn't get a special discount at the year's end the chance to win a prize at a surprise tombola. Each order represents a lottery ticket. To avoid that customers can win more than once, a
LOOP AT ... GROUP BY
statement is used. At this point,
STEP
is already used to reduce the number of hits. To ensure that several tombolas can be performed with the same result set, you first list the result in a separate internal table
win_candidates
. Afterwards, you determine the winning customers by inserting the rows into the
customer_benefit
table and using
STEP
again to finalize the result.
CLASS purchase_orders IMPLEMENTATION.
"...
METHOD tombola.
TYPES:
BEGIN OF customer_benefit,
cust_id TYPE c LENGTH 8,
discount_granted TYPE abap_bool,
tombola_won TYPE abap_bool,
END OF customer_benefit,
customer_benefits TYPE STANDARD TABLE OF customer_benefit WITH EMPTY KEY.
DATA(customer_benefit) = VALUE customer_benefits( ).
DATA(win_candidates) = VALUE customer_benefits( ).
DATA(customers_with_discounts) = VALUE customer_benefits(
( cust_id = co_example_customer
discount_granted = abap_true ) ).
LOOP AT m_customer_purchases REFERENCE INTO DATA(tombola) STEP 2
GROUP BY ( cust_id = tombola->cust_id ) REFERENCE INTO DATA(customer).
IF NOT line_exists( customers_with_discounts[ cust_id = customer->cust_id ] ).
INSERT VALUE #( cust_id = customer->cust_id tombola_won = abap_true )
INTO TABLE win_candidates.
ENDIF.
ENDLOOP.
INSERT LINES OF customers_with_discounts INTO TABLE customer_benefit.
INSERT LINES OF win_candidates STEP 2 INTO TABLE customer_benefit.
cl_demo_output=>write( customer_benefit ).
ENDMETHOD.
ENDCLASS.
The
INSERT ... LINES OF
statement could be replaced by the
VALUE ... LINES OF
statement as well, but it's shown here to demonstrate the alternative usage.
Data from customers who received a discount is first stored in the internal table
customer_benefit
. Afterwards, data from those who didn't receive a discount is added to the same
customer_benefit
table. The number of customers from the second selection is reduced by using
STEP
. These second selection represents the winners of the tombola.
CUST_ID |
DISCOUNT_GRANTED |
TOMBOLA_WON |
00000001 |
X |
|
00000003 |
|
X |
Quick Check
Get a quick overview of how to apply the keywords with
STEP
where
o stands for
it is possible and
x stands for
it is not possible.
Keyword |
n > 1 |
-n |
Syntax |
Reference |
LOOP |
o* |
o |
LOOP AT itab ... STEP -1 |
LOOP AT itab |
DELETE |
o |
x |
DELETE itab ... STEP 2 |
DELETE itab |
INSERT |
o |
x |
INSERT LINES OF jtab ... STEP 2 |
INSERT itab |
APPEND |
o |
x |
APPEND LINES OF jtab ... STEP 2 |
APPEND |
FOR ... IN |
o* |
o |
FOR ... IN itab STEP -1 |
FOR ... IN itab |
NEW |
o |
x |
NEW ... LINES OF jtab ... STEP 2 |
NEW |
VALUE |
o |
x |
VALUE ... LINES OF jtab ... STEP 2 |
VALUE |
*only without
WHERE
condition
Further information
You should now know how to use the new addition
STEP
in your projects. Use
STEP
for looping backward across the lines of an internal table and for defining a step size. The examples given in this blog are intended for demonstration purposes only. From now on, you may take the short and direct route to your destination instead of taking detours. Go and explore your new shortcut. Are you excited to use the new addition
STEP
? Write your thoughts in the comment section. Don't miss out on new language elements and follow my profile (
lenapadeken) for similar blog posts.