Very slow itab loop with nested SELECTs - abap

I have declare an internal table like:
DATA: wa_collectoraction TYPE zcollectoraction,
it_collectoraction LIKE STANDARD TABLE OF zcollectoraction.
Then I fill the table with:
SELECT bukrs kunnr yearmonth MAX( dat ) AS dat
FROM zcollectoraction
INTO CORRESPONDING FIELDS OF TABLE it_collectoraction
WHERE bukrs IN so_bukrs AND
kunnr IN so_kunnr AND
dat IN so_date
GROUP BY bukrs kunnr yearmonth.
and finally I have the following loop
LOOP AT it_collectoraction INTO wa_collectoraction.
PERFORM progress_bar USING 'Retrieving data...'(035)
sy-tabix
i_tab_lines.
"Get the MAX TIME for all lines in order to cover the case we have more than 1 line."
SELECT SINGLE * FROM zcollectoraction
INTO CORRESPONDING FIELDS OF wa_collectoraction
WHERE bukrs = wa_collectoraction-bukrs AND
kunnr = wa_collectoraction-kunnr AND
dat = wa_collectoraction-dat AND
time = ( SELECT MAX( time ) AS time
FROM zcollectoraction
WHERE bukrs = wa_collectoraction-bukrs AND
kunnr = wa_collectoraction-kunnr AND
dat = wa_collectoraction-dat ).
MODIFY it_collectoraction FROM wa_collectoraction.
ENDLOOP.
This loop is doing 5 minutes for 3000 records.
Can someone tell me what to do in order to be faster?
Thanks in advance

The best tool to analyze a standalone report's performance is ST12, so if you have the chance, trace it.
Without a trace, we have to guess, the biggest problem is either the SELECT with the subSELECT, or the MODIFY.
1) SELECTs in a LOOP are always slow
Here you actually make two for every line in it_collectoraction.
Try reducing the number of SELECTs
Depending on the number of lines with the same dat, it might be much faster to replace the SELECT in the LOOP with a SELECT with FOR ALL ENTRIES from zcollectoraction outside the LOOP, and find the MAX(time) on ABAP side.
Index coverage
Seems to be fine.
2) MODIFY is slow on STANDARD tables
You have to sieve through the whole table just to find the relevant line. If you define it_collectoraction as SORTED, this will be much faster. If you use a field symbol in the LOOP, it can be avoided altogether.
Coding
Replace your LOOP with this:
TYPES: BEGIN OF tty_coll_act,
bukrs TYPE burks,
kunnr TYPE kunnr,
dat TYPE dat,
time TYPE time,
END OF tty_coll_act.
DATA: lt_coll_act TYPE TABLE OF tty_coll_act,
ls_coll_act LIKE LINE OF lt_coll_act.
FIELD-SYMBOLS: <fs_collectoraction> LIKE LINE OF it_collectoraction.
SELECT bukrs kunnr dat time
INTO TABLE lt_coll_act
FROM zcollectoraction
FOR ALL ENTRIES IN it_collectoraction
WHERE bukrs = wa_collectoraction-bukrs AND
kunnr = wa_collectoraction-kunnr AND
dat = wa_collectoraction-dat.
SORT lt_coll_act BY bukrs kunnr dat time DESCENDING.
LOOP AT it_collectoraction ASSIGNING <fs_collectoraction>.
" the READ TABLE finds the first matching row,
" it will be MAX(TIME) as TIME is sorted descending
READ TABLE lt_coll_act INTO ls_coll_act
WITH KEY bukrs = <fs_collectoraction>-bukrs
kunnr = <fs_collectoraction>-kunnr
dat = <fs_collectoraction>-dat BINARY SEARCH.
<fs_collectoraction> = ls_coll_act.
ENDLOOP.

Instead of adding a selection query inside a loop, get all data into an internal table and handle it with read statements inside the loop.
Adding select queries inside a loop will always slow down the execution of an application as the application has to execute database queries for each loop. Loading all required information into an internal table and then handling data within the application is much more faster.
Let me know if you require any further details on this.

First of all I want to thank all of you for your help.
I change the logic of select by using an internal table with all records from dbtab according to the selection data of the user.
So the code became as follow:
DATA: wa_collectoraction TYPE zcollectoraction,
it_collectoraction TYPE TABLE OF zcollectoraction,
itsort_collectoraction TYPE HASHED TABLE OF zcollectoraction
WITH UNIQUE KEY mandt bukrs kunnr yearmonth dat time.
FIELD-SYMBOLS: <fs_collectoraction> LIKE LINE OF it_collectoraction.
SELECT bukrs kunnr yearmonth MAX( dat ) AS dat
FROM zcollectoraction
INTO CORRESPONDING FIELDS OF TABLE it_collectoraction
WHERE bukrs IN so_bukrs AND
kunnr IN so_kunnr AND
dat IN so_date
GROUP BY bukrs kunnr yearmonth.
" Keep the total records which will be inserted.
i_tab_lines = sy-dbcnt.
SELECT * INTO TABLE itsort_collectoraction
FROM zcollectoraction
WHERE bukrs IN so_bukrs AND
kunnr IN so_kunnr AND
dat IN so_date.
SORT itsort_collectoraction
BY mandt bukrs kunnr yearmonth dat time DESCENDING.
LOOP AT it_collectoraction ASSIGNING <fs_collectoraction>.
PERFORM progress_bar USING 'Retrieving data...'(035)
sy-tabix
i_tab_lines.
READ TABLE itsort_collectoraction INTO wa_collectoraction
WITH KEY bukrs = <fs_collectoraction>-bukrs
kunnr = <fs_collectoraction>-kunnr
yearmonth = <fs_collectoraction>-yearmonth
dat = <fs_collectoraction>-dat.
<fs_collectoraction> = wa_collectoraction.
ENDLOOP.
This code run 43000 records in 1 minute.
The only problem is that after the first 10000 to 15000 records the process is slowing down. I don't know if there is any command to clear sth. I don't know what to clear.
Again thanks a lot all of you.
Regards
Elias
PS. In the 1st 10 sec it process 14.000 records.
In 1 minute process 38.500 and
In 1 minute & 50 seconds finished the 54.500 records.
It gives me the impression that it fulfills sth which slow-down the process. ANY IDEA?

I am a little late to the party, but what I see in your first post is that you just want to read the latest (max(date) and max(time)) entries from one table per bukrs and kunnr?
Use one select to get the table's content. Select only by keyfields or indexes:
I assume that date is not a key field, but bukrs and kunnr:
SELECT bukrs kunnr yearmonth dat time
FROM zcollectoraction
INTO CORRESPONDING FIELDS OF TABLE it_collectoraction
WHERE bukrs IN so_bukrs AND
kunnr IN so_kunnr
.
Delete non key fields from itab:
DELETE it_collectoraction WHERE dat NOT IN so_date.
Sort itab by date and time descending so that the latest entry is the first combination of bukrs and kunnr
SORT it_collectoraction BY bukrs kunnr date DESCENDING time DESCENDING.
Delete all adjacent (=all with same compare key after the first) entries per bukrs and kunnr
DELETE ADJACENT DUPLICATES FROM it_collectoraction COMPARING bukrs kunnr.

Related

How to replace specific rows in a table with rows form another table, other than using 2 loops?

I have 2 internal tables, A and B, of the same type. A has many records, while B has some of the records in Table A (that is with equal key fields) but with different values on non-key fields.How can I replace those rows in A with their respective rows at B without using 2 different LOOPs (that is LOOP AT A and for each iteration at A, LOOP AT B to find the respective row and replace it)?Below is the structure of these tables.
TYPES: BEGIN OF tab1,
bukrs TYPE bukrs,
belnr TYPE belnr,
gjahr TYPE gjahr,
buzei TYPE buzei,
"above are the key fields
"below are the non-key fields
blart TYPE blart,
bldat TYPE bldat,
bschl TYPE bschl,
menge TYPE menge,
meins TYPE meins,
dmbtr TYPE dmbtr,
waers TYPE waers,
zstatus TYPE c LENGTH 1,
END OF tab1.
You can avoid the loop through table B by finding the corresponding line with the READ TABLE command.
LOOP AT gt_tab1 ASSIGNING FIELD-SYMBOL(<line1>).
READ TABLE gt_tab2 ASSIGNING FIELD-SYMBOL(<line2>) WITH KEY
bukrs = <line1>-bukrs
belnr = <line1>-belnr
gjahr = <line1>-gjahr
buzei = <line1>-buzei.
IF sy-subrc = 0.
MOVE-CORRESPONDING <line2> TO <line1>.
ENDIF.
ENDLOOP.
Now what READ TABLE usually does is perform a loop over the table until it found the first matching record. So you didn't actually gain anything except making your code a bit shorter and more readable.
However, there are ways to speed up the performance of READ TABLE. The first is to declare the table you read from with a primary or a secondary key and then use that key in the READ TABLE. Here is an example with the hashed key variant:
DATA gt_tab2 TYPE TABLE OF tab1
WITH UNIQUE HASHED KEY key1 COMPONENTS bukrs belnr gjahr buzei.
"...
READ TABLE gt_tab2 ASSIGNING FIELD-SYMBOL(<line2>)
USING KEY key1 COMPONENTS
bukrs = <line1>-bukrs
belnr = <line1>-belnr
gjahr = <line1>-gjahr
buzei = <line1>-buzei.
The result is that you speed up the linear search time to logarithmic time (with a NON-UNIQUE SORTED KEY) or to constant time (with a UNIQUE HASHED KEY).
This of course requires that you have control over the declaration of the second table. This is not always the case, for example when you implement an interface or event function module. But in that case there is still one thing you can do:
SORT the table by the fields you are going to search it with later (or a copy of the table, if you are in a context where you can't change the order)
Use READ TABLE with the BINARY SEARCH addition
Using binary search reduces the runtime of READ TABLE from linear to logarithmical. But note that when the table is not correctly sorted, it will fail to find rows even though they exist.
SORT gt_tab2 BY bukrs belnr gjahr buzei.
LOOP AT gt_tab1 ASSIGNING FIELD-SYMBOL(<line1>).
READ TABLE gt_tab2 ASSIGNING FIELD-SYMBOL(<line2>)
WITH KEY
bukrs = <line1>-bukrs
belnr = <line1>-belnr
gjahr = <line1>-gjahr
buzei = <line1>-buzei
BINARY SEARCH.
IF sy-subrc = 0.
MOVE-CORRESPONDING <line2> TO <line1>.
ENDIF.
ENDLOOP.

Which has better performance: SELECT...ENDSELECT (1 by 1) or SELECT...INTO TABLE / LOOP AT

I have to read 10.000.000 records from a table.
Is it better:
to read these records one by one using SELECT ... ENDSELECT (without internal table)
or to read all of them at once using SELECT ... INTO TABLE itab and then LOOP through this internal table?
If all 10,000,000 entries fit into ABAP's main memory, you should select all of them with a single SELECT ... INTO TABLE ..., followed by a LOOP.
This reduces expensive database interaction to a minimum and will be fastest.
If the records don't fit into main memory, you need to retrieve them in packages. Check out the PACKAGE SIZE addition of the SELECT statement.
They have about the same speed
Contrary to popular belief, SELECT ... ENDSELECT does not fetch the rows one-by-one, so its performance is not much worse than SELECT ... INTO TABLE. See the explanation here.
The big problem with SELECT ... ENDSELECT is that it prevents other performance improvements
Consider this coding:
SELECT matnr FROM mara
INTO lv_matnr WHERE (...).
SELECT SINGLE ebeln
FROM ekpo
INTO lv_ebeln
WHERE matnr = lv_matnr.
SELECT SINGLE zfield
FROM ztable
INTO lv_zfield
WHERE matnr = lv_matnr.
...
ENDSELECT.
Most of the time will be spent with the SELECT SINGLEs on table ekpo and ztable, and often the solution for this is using FOR ALL ENTRIES1, however you need an internal table for that.
So it has to be converted into a SELECT ... INTO TABLE anyway:
SELECT matnr FROM mara
INTO TABLE lt_matnr WHERE (...).
IF lt_mara IS NOT INITIAL.
SELECT matnr, ebeln FROM ekpo
INTO TABLE #lt_ekpo "make it SORTED by matnr"
FOR ALL ENTRIES IN #lt_matnr
WHERE matnr = #table_line.
SELECT matnr, zfield FROM ztable
INTO TABLE #lt_ztable "make it SORTED by matnr"
FOR ALL ENTRIES IN #lt_matnr
WHERE matnr = #table_line.
ENDIF.
LOOP AT lt_matnr ASSIGNING <fs_mara>.
READ TABLE lt_ekpo ASSIGNING <fs_ekpo>
WITH KEY matnr = <fs_matnr>.
READ TABLE lt_ztable ASSIGNING <fs_ztable>
WITH KEY matnr = <fs_matnr>.
...
ENDLOOP.
You should avoid SELECT ... ENDSELECT, not because of its own performance, but to make other improvements easier.
You should use JOINs whenever you can instead of FOR ALL ENTRIES

How to make massive selection SAP ABAP

I am doing a massive selection from database with the intention of saving it on application server or local directory.
Since the db has loads of entries I first tried this way:
SELECT * FROM db PACKAGE SIZE iv_package
INTO CORRESPONDING FIELDS OF TABLE rt_data
WHERE cond IN so_cond
AND cond1 IN so_cond1.
SAVE(rt_data).
ENDSELECT.
This resulted in a dump, with the following message:
Runtime Errors: DBIF_RSQL_INVALID_CURSOR
Exeption : CX_SY_OPEN_SQL_DB
I tried doing an alternative way as well:
OPEN CURSOR WITH HOLD s_cursor FOR
SELECT * FROM db
WHERE cond IN so_cond
AND cond1 IN so_cond1.
DO.
FETCH NEXT CURSOR s_cursor INTO TABLE rt_data PACKAGE SIZE iv_package.
SAVE(rt_data).
ENDDO.
This also resulted in a dump with the same message.
What is the best approach to this scenario?
TYPES:
BEGIN OF key_package_type,
from TYPE primary_key_type,
to TYPE primary_key_type,
END OF key_package_type.
TYPES key_packages_type TYPE STANDARD TABLE OF key_package WITH EMPTY KEY.
DATA key_packages TYPE key_packages_type.
* select only the primary keys, in packages
SELECT primary_key_column FROM db
INTO TABLE #DATA(key_package) PACKAGE SIZE package_size
WHERE cond IN #condition AND cond1 IN other_condition
ORDER BY primary_key_column.
INSERT VALUE #( from = key_package[ 1 ]-primary_key_column
to = key_package[ lines( key_package ) ]-primary_key_column )
INTO TABLE key_packages.
ENDSELECT.
* select the actual data by the primary key packages
LOOP AT key_packages INTO key_package.
SELECT * FROM db INTO TABLE #DATA(result_package)
WHERE primary_key_column >= key_package-from
AND primary_key_column <= key_package-to.
save_to_file( result_package ).
ENDLOOP.
If your table has a compound primary key, i.e. multiple columns such as {MANDT, GJAHR, BELNR}, simply replace the types of the from and to fields with structures and adjust the column list in the first SELECT and the WHERE condition in the second SELECT appropriately.
If you have a range containing only option = 'EQ' records or one of the conditions has a foreign key you can simply start looping before you do the select to reduce the size of the resulting table and move the method call out of the open cursor.
OPTION = 'EQ'
Here you just loop over the range:
LOOP AT so_cond ASSIGNING FIELD-SYMBOL(<cond>).
SELECT * FROM db
INTO CORRESPONDING FIELDS OF TABLE rt_data
WHERE cond = <cond>-low.
AND cond1 IN so_cond1.
save(rt_data).
ENDLOOP.
Foreign Key
Looping over the range is not possible in this case since you cannot easily resolve the other options like CP. But you can get each value the range selects from the foreign keytab of cond. Then you loop over the resulting table and do the SELECT statement inside like above.
SELECT cond FROM cond_foreign_keytab
WHERE cond IN #so_cond
INTO TABLE #DATA(cond_values).
LOOP AT cond_values ASSIGNING FIELD-SYMBOL(<cond>).
SELECT * FROM db
INTO CORRESPONDING FIELDS OF TABLE rt_data
WHERE cond = <cond>.
AND cond1 IN so_cond1.
save(rt_data).
ENDLOOP.

Modify certain BSEG fields from customary structured table

I'm trying to use following:
update bseg from zbseg
where tables are not from same length (ZBSEG is reduced version of BSEG).
Whole idea is that BSEG is just an example, I have a loop where all cluster tables will be iterated, so everything should be dynamically.
Table data from cluster is reduced to only several fields and copied to transparent table (data dictionary in new transparent table has primary keys + only few of the field of cluster) and afterwards data in DB will be modified and copied back via UPDATE to the cluster.
update bseg from zbseg
this statement updates the field values from ZBSEG but for the rest will not keep old values but rather puts initial values.
I've tried even that:
SELECT *
FROM bseg
INTO TABLE gt_bseg.
SELECT mandt bukrs belnr gjahr buzei buzid augdt
FROM zbseg
INTO CORRESPONDING FIELDS OF TABLE gt_bseg.
but it still overlaps those fields that are not considered in zbseg.
Any statement that will update only certain range of fields extracted from ZBSEG not touching other BSEG fields?
I think you need get records from zbseg with limit because of there will be exists million records then get them from bseg one by one and update it, then remove or update flags of it from zbseg for performance.
tables: BSEG, ZBSEG.
data: GT_ZBSEG like ZBSEG occurs 1 with header line,
GS_BSEG type BSEG.
select *
into table GT_ZBSEG up to 1000 rows
from ZBSEG.
check SY-SUBRC is initial.
check SY-DBCNT is not initial.
loop at GT_ZBSEG.
select single * from BSEG into GS_BSEG
where BSEG~MANDT = GT_ZBSEG-MANDT
and BSEG~BUKRS = GT_ZBSEG-BUKRS
and BSEG~BELNR = GT_ZBSEG-BELNR
and BSEG~GJAHR = GT_ZBSEG-GJAHR
and BSEG~BUZEI = GT_ZBSEG-BUZEI.
if SY-SUBRC ne 0.
message E208(00) with 'Record not found!'.
endif.
if GS_BSEG-BUZID ne GT_ZBSEG-BUZID
or GS_BSEG-AUGDT ne GT_ZBSEG-AUGDT.
move-corresponding GT_ZBSEG to GS_BSEG.
update BSEG from GS_BSEG.
endif.
" delete same records and transfered
delete ZBSEG from GT_ZBSEG.
endloop.
Here is piece of code you can use for your task. It is based on dynamic UPDATE statement which allows updating only certain fields:
DATA: handle TYPE REF TO data,
lref_struct TYPE REF TO cl_abap_structdescr,
source TYPE string,
columns TYPE string,
keys TYPE string,
cond TYPE string,
sets TYPE string.
SELECT tabname FROM dd02l INTO TABLE #DATA(clusters) WHERE tabclass = 'CLUSTER'.
LOOP AT clusters ASSIGNING FIELD-SYMBOL(<cluster>).
lref_struct ?= cl_abap_structdescr=>describe_by_name( <cluster>-tabname ).
source = 'Z' && <cluster>-tabname. " name of your ZBSEG-like table
* get key fields
DATA(key_fields) = VALUE ddfields( FOR line IN lref_struct->get_ddic_field_list( )
WHERE ( keyflag NE space ) ( line ) ).
lref_struct ?= cl_abap_structdescr=>describe_by_name( source ).
* get all fields from source reduced table
DATA(fields) = VALUE ddfields( FOR line IN lref_struct->get_ddic_field_list( ) ( line ) ).
* filling SELECT fields and SET clause
LOOP AT fields ASSIGNING FIELD-SYMBOL(<field>).
AT FIRST.
columns = <field>-fieldname.
CONTINUE.
ENDAT.
CONCATENATE columns <field>-fieldname INTO columns SEPARATED BY `, `.
IF NOT line_exists( key_fields[ fieldname = <field>-fieldname ] ).
IF sets IS INITIAL.
sets = <field>-fieldname && ` = #<fsym_wa>-` && <field>-fieldname.
ELSE.
sets = sets && `, ` && <field>-fieldname && ` = #<fsym_wa>-` && <field>-fieldname.
ENDIF.
ENDIF.
ENDLOOP.
* filling key fields and conditions
LOOP AT key_fields ASSIGNING <field>.
AT FIRST.
keys = <field>-fieldname.
CONTINUE.
ENDAT.
CONCATENATE keys <field>-fieldname INTO keys SEPARATED BY `, `.
IF cond IS INITIAL.
cond = <field>-fieldname && ` = #<fsym_wa>-` && <field>-fieldname.
ELSE.
cond = cond && ` AND ` && <field>-fieldname && ` = #<fsym_wa>-` && <field>-fieldname.
ENDIF.
ENDLOOP.
* constructing reduced table type
lref_struct ?= cl_abap_typedescr=>describe_by_name( source ).
CREATE DATA handle TYPE HANDLE lref_struct.
ASSIGN handle->* TO FIELD-SYMBOL(<fsym_wa>).
* updating result cluster table
SELECT (columns)
FROM (source)
INTO #<fsym_wa>.
UPDATE (<cluster>-tabname)
SET (sets)
WHERE (cond).
ENDSELECT.
ENDLOOP.
This piece selects all cluster tables from DD02L and makes an assumption you have reduced DB table prefixed with Z for each target cluster table. E.g. ZBSEG for BSEG, ZBSET for BSET, ZKONV for KONV and so on.
Tables are updated by primary key which must be included in reduced table. The fields to be updated are taken from reduced table as all fields excluding key fields, because primary key is prohibited for update.
You could try to use the MODIFY statement to update the tables.
An other way to do it would be to use the cl_abap_typedescr to get the fields of each table and compare them for the update.
Here is an example of how to get the fields.
DATA : ref_table_des TYPE REF TO cl_abap_structdescr,
columns TYPE abap_compdescr_tab.
ref_table_des ?= cl_abap_typedescr=>describe_by_data( struc ).
columns = ref_table_des->components[].

Error when SELECT...FOR ALL ENTRIES have currency fields

I get this error:
In a SELECT access, the read file could not be placed in the target field provided.
when executing this line of code:
SELECT vbeln
posnr
matnr
netpr
netwr
kondm
FROM vbap INTO TABLE t_tab
FOR ALL ENTRIES IN postab
WHERE vbeln = postab-vbeln.
I try one by one, and every time I put a currency field it will trigger this dump. Anyone know the root cause?
How is your t_tab declared? It seems like it is declared like a structure or, maybe, component order is wrong. Try to make declarations like this:
DATA: postab LIKE TABLE OF vbap,
t_tab LIKE TABLE OF vbap.
and replace INTO clause with this piece of code
FROM vbap INTO CORRESPONDING FIELDS OF TABLE t_tab
If your fields in the t_tab have other names then the fields your selecting be sure to match these with as:
SELECT vbeln AS ....
posnr AS ....
matnr AS ....
netpr
netwr
kondm
FROM vbap INTO TABLE t_tab
FOR ALL ENTRIES IN postab
WHERE vbeln = postab-vbeln.
If they have the same names try INTO CORRESPONDING FIELDS OF TABLE.
Also be sure that the fields in the t_tab have the right format.