In Master Data Hub scenario the MDG system is the “source of truth”, master data is replicated to satellite systems and can be extended there, but in general the common part should not be changed locally because at next replication it will get back overwritten with the version from MDG.
In some cases, it may happen that either naughty user or external interface modify part of the data which should not be changed, and it is often needed to detect such situation ASAP.
On the market exist tools to compare the master data between systems, but usually they are not free, have complex configuration and it takes time to learn how they work.
In this blog I provide a solution how to compare Material Master Data between two systems with relatively short coding (in total about 1k lines of ABAP) and “some clicks of the mouse”. The code can be implemented in Sandbox system and is ready to compare data in any systems to which you set up an RFC connection.
How it works
A Delta Collection Program connects to two selected systems (RFC destinations must be set up in SM59), and reads Material Master Data tables from System1 and System2 (with standard RFC function module RFC_READ_TABLE – read carefully note 382318). After field-by-field comparison the calculated delta is shown on output.
As the Material Master Data tables are usually huge and the delta collection takes time – the output of the program mentioned above can be saved in a Custom Delta Table.
For nice visualization of the comparison there is simple FPM application to be created (however you can also use MS Excel).
As result we get something like below:
Where Delta Collection Program looks like:
and the Custom Delta Table:
How to set it up
(I’ve tried to provide detailed instructions to make it clear for person with just basic ABAP knowledge)
First, the Custom Delta Table where collected delta records will be stored, so SE11 and create table as below (to make it simple I’ve used generic data elements, but of course you can set up your own with nice labels)
Then the class used to collect the data and calculate delta:
Simply create new class ZCL_TABLES_COMPARE, switch into “Source Code-Based” mode and paste the code below (+ save and activate). See the comments at call of ‘RFC_READ_TABLE’
CLASS zcl_tables_compare DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
TYPES:
ty_fields_t TYPE STANDARD TABLE OF rfc_db_fld WITH DEFAULT KEY .
TYPES:
BEGIN OF ty_log,
rkey TYPE ztab_compare-rkey,
tfname TYPE ztab_compare-tfname,
v1 TYPE ztab_compare-v1,
v2 TYPE ztab_compare-v2,
fdescr TYPE string,
END OF ty_log .
TYPES:
ty_log_t TYPE STANDARD TABLE OF ty_log WITH KEY rkey tfname .
CONSTANTS gc_key_delimeter TYPE char1 VALUE '/' ##NO_TEXT.
CLASS-METHODS run_comparison
IMPORTING
!iv_rfc_dest1 TYPE rfcdest
!iv_rfc_dest2 TYPE rfcdest
!iv_tabname TYPE tabname
!iv_key_fields TYPE string
!iv_excl_fields TYPE string OPTIONAL
!iv_range_from TYPE char100
!iv_range_to TYPE char100
!iv_ec_s1 TYPE abap_bool DEFAULT abap_true
!iv_ec_s2 TYPE abap_bool DEFAULT abap_true
RETURNING
VALUE(rt_result) TYPE ty_log_t .
CLASS-METHODS get_table_key_fields
IMPORTING
!iv_tabname TYPE tabname
RETURNING
VALUE(rv_result) TYPE string .
CLASS-METHODS get_table_field_descr
IMPORTING
!iv_tfname TYPE ztab_compare-tfname
RETURNING
VALUE(rv_result) TYPE string .
PROTECTED SECTION.
PRIVATE SECTION.
CLASS-METHODS is_status_different
IMPORTING
!iv_s1 TYPE any
!iv_s2 TYPE any
RETURNING
VALUE(rv_result) TYPE abap_bool .
CLASS-METHODS compare_tables
IMPORTING
!iv_tabname TYPE tabname
!ir_tab1 TYPE REF TO data
!ir_tab2 TYPE REF TO data
!iv_ec_s1 TYPE abap_bool
!iv_ec_s2 TYPE abap_bool
CHANGING
!ct_log TYPE ty_log_t .
CLASS-METHODS read_table
IMPORTING
!iv_rfc_dest TYPE rfcdest
!iv_tabname TYPE tabname
!iv_key_fields TYPE string
!iv_excl_fields TYPE string OPTIONAL
!iv_range_from TYPE char100 OPTIONAL
!iv_range_to TYPE char100 OPTIONAL
RETURNING
VALUE(rt_result) TYPE REF TO data .
ENDCLASS.
CLASS ZCL_TABLES_COMPARE IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_TABLES_COMPARE=>COMPARE_TABLES
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_TABNAME TYPE TABNAME
* | [--->] IR_TAB1 TYPE REF TO DATA
* | [--->] IR_TAB2 TYPE REF TO DATA
* | [--->] IV_EC_S1 TYPE ABAP_BOOL
* | [--->] IV_EC_S2 TYPE ABAP_BOOL
* | [<-->] CT_LOG TYPE TY_LOG_T
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD compare_tables.
DEFINE copy_tab.
ASSIGN &1->* TO <lt_data>.
IF sy-subrc EQ 0.
&2 = CORRESPONDING #( <lt_data> ).
SORT &2.
ENDIF.
END-OF-DEFINITION.
DEFINE check1.
IF sy-subrc EQ 0.
IF &1 NE &2.
LOOP AT lo_str_descr->components ASSIGNING <ls_comp>.
ASSIGN COMPONENT <ls_comp>-name OF STRUCTURE &1 TO <lv_value1>.
ASSIGN COMPONENT <ls_comp>-name OF STRUCTURE &2 TO <lv_value2>.
IF ';VPSTA;PSTAT;' CS |;{ <ls_comp>-name };|. " status field
IF abap_true EQ is_status_different( iv_s1 = <lv_value1> iv_s2 = <lv_value2> ).
APPEND VALUE #( rkey = lv_key tfname = |{ iv_tabname }-{ <ls_comp>-name }| v1 = <lv_value1> v2 = <lv_value2> ) TO ct_log.
ENDIF.
ELSE.
IF <lv_value1> NE <lv_value2>.
APPEND VALUE #( rkey = lv_key tfname = |{ iv_tabname }-{ <ls_comp>-name }| v1 = <lv_value1> v2 = <lv_value2> ) TO ct_log.
ENDIF.
ENDIF.
ENDLOOP.
ENDIF.
ELSE.
IF iv_ec_s1 EQ abap_true.
APPEND VALUE #( rkey = lv_key tfname = iv_tabname v1 = 'record exists' v2 = 'does not exist' ) TO ct_log. " key does not exist in System 2
ENDIF.
ENDIF.
END-OF-DEFINITION.
DEFINE check2.
IF sy-subrc NE 0.
APPEND VALUE #( rkey = lv_key tfname = iv_tabname v1 = 'does not exist' v2 = 'record exists' ) TO ct_log. " key does not exist in System 1
ENDIF.
END-OF-DEFINITION.
DATA: lt_mara1 TYPE STANDARD TABLE OF mara WITH KEY matnr,
lt_mara2 LIKE lt_mara1,
lt_marc1 TYPE STANDARD TABLE OF marc WITH KEY matnr werks,
lt_marc2 LIKE lt_marc1,
lt_makt1 TYPE STANDARD TABLE OF makt WITH KEY matnr spras,
lt_makt2 LIKE lt_makt1,
lt_mard1 TYPE STANDARD TABLE OF mard WITH KEY matnr werks lgort,
lt_mard2 LIKE lt_mard1,
lt_marm1 TYPE STANDARD TABLE OF marm WITH KEY matnr meinh,
lt_marm2 LIKE lt_marm1,
lt_mbew1 TYPE STANDARD TABLE OF mbew WITH KEY matnr bwkey bwtar,
lt_mbew2 LIKE lt_mbew1,
lt_mean1 TYPE STANDARD TABLE OF mean WITH KEY matnr meinh ean11, "this one is special case - LFNUM may differ between sytstems
lt_mean2 LIKE lt_mean1,
lt_mlgn1 TYPE STANDARD TABLE OF mlgn WITH KEY matnr lgnum,
lt_mlgn2 LIKE lt_mlgn1,
lt_mlgt1 TYPE STANDARD TABLE OF mlgt WITH KEY matnr lgnum lgtyp,
lt_mlgt2 LIKE lt_mlgt1,
lt_mvke1 TYPE STANDARD TABLE OF mvke WITH KEY matnr vkorg vtweg,
lt_mvke2 LIKE lt_mvke1,
lt_qmat1 TYPE STANDARD TABLE OF qmat WITH KEY art matnr werks,
lt_qmat2 LIKE lt_qmat1,
lo_str_descr TYPE REF TO cl_abap_structdescr,
lv_key TYPE string.
FIELD-SYMBOLS: <lt_data> TYPE ANY TABLE,
<ls_comp> TYPE abap_compdescr,
<lv_value1> TYPE any,
<lv_value2> TYPE any.
lo_str_descr = CAST #( cl_abap_structdescr=>describe_by_name( iv_tabname ) ).
CASE iv_tabname.
WHEN 'MARA'.
copy_tab ir_tab1 lt_mara1.
copy_tab ir_tab2 lt_mara2.
LOOP AT lt_mara1 ASSIGNING FIELD-SYMBOL(<ls_mara1>).
lv_key = |{ <ls_mara1>-matnr ALPHA = OUT }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mara2 WITH KEY matnr = <ls_mara1>-matnr ASSIGNING FIELD-SYMBOL(<ls_mara2>) BINARY SEARCH.
check1 <ls_mara1> <ls_mara2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_mara2 ASSIGNING <ls_mara2>.
lv_key = |{ <ls_mara1>-matnr ALPHA = OUT }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mara1 WITH KEY matnr = <ls_mara2>-matnr TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MARC'.
copy_tab ir_tab1 lt_marc1.
copy_tab ir_tab2 lt_marc2.
LOOP AT lt_marc1 ASSIGNING FIELD-SYMBOL(<ls_marc1>).
lv_key = |{ <ls_marc1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_marc1>-werks }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_marc2 WITH KEY matnr = <ls_marc1>-matnr werks = <ls_marc1>-werks ASSIGNING FIELD-SYMBOL(<ls_marc2>) BINARY SEARCH.
check1 <ls_marc1> <ls_marc2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_marc2 ASSIGNING <ls_marc2>.
lv_key = |{ <ls_marc2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_marc2>-werks }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_marc1 WITH KEY matnr = <ls_marc2>-matnr werks = <ls_marc2>-werks TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MAKT'.
copy_tab ir_tab1 lt_makt1.
copy_tab ir_tab2 lt_makt2.
LOOP AT lt_makt1 ASSIGNING FIELD-SYMBOL(<ls_makt1>).
lv_key = |{ <ls_makt1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_makt1>-spras }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_makt2 WITH KEY matnr = <ls_makt1>-matnr spras = <ls_makt1>-spras ASSIGNING FIELD-SYMBOL(<ls_makt2>) BINARY SEARCH.
check1 <ls_makt1> <ls_makt2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_makt2 ASSIGNING <ls_makt2>.
lv_key = |{ <ls_makt2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_makt2>-spras }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_makt1 WITH KEY matnr = <ls_makt2>-matnr spras = <ls_makt2>-spras TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MARD'.
copy_tab ir_tab1 lt_mard1.
copy_tab ir_tab2 lt_mard2.
LOOP AT lt_mard1 ASSIGNING FIELD-SYMBOL(<ls_mard1>).
lv_key = |{ <ls_mard1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mard1>-werks }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mard1>-lgort }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mard2 WITH KEY matnr = <ls_mard1>-matnr werks = <ls_mard1>-werks lgort = <ls_mard1>-lgort ASSIGNING FIELD-SYMBOL(<ls_mard2>) BINARY SEARCH.
check1 <ls_mard1> <ls_mard2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_mard2 ASSIGNING <ls_mard2>. " check form the other side to detect missing records in System 1
lv_key = |{ <ls_mard2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mard2>-werks }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mard2>-lgort }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mard1 WITH KEY matnr = <ls_mard2>-matnr werks = <ls_mard2>-werks lgort = <ls_mard2>-lgort TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MARM'.
copy_tab ir_tab1 lt_marm1.
copy_tab ir_tab2 lt_marm2.
LOOP AT lt_marm1 ASSIGNING FIELD-SYMBOL(<ls_marm1>).
lv_key = |{ <ls_marm1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_marm1>-meinh }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_marm2 WITH KEY matnr = <ls_marm1>-matnr meinh = <ls_marm1>-meinh ASSIGNING FIELD-SYMBOL(<ls_marm2>) BINARY SEARCH.
check1 <ls_marm1> <ls_marm2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_marm2 ASSIGNING <ls_marm2>.
lv_key = |{ <ls_marm2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_marm2>-meinh }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_marm1 WITH KEY matnr = <ls_marm2>-matnr meinh = <ls_marm2>-meinh TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MBEW'.
copy_tab ir_tab1 lt_mbew1.
copy_tab ir_tab2 lt_mbew2.
LOOP AT lt_mbew1 ASSIGNING FIELD-SYMBOL(<ls_mbew1>).
lv_key = |{ <ls_mbew1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mbew1>-bwkey }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mbew1>-bwtar }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mbew2 WITH KEY matnr = <ls_mbew1>-matnr bwkey = <ls_mbew1>-bwkey bwtar = <ls_mbew1>-bwtar ASSIGNING FIELD-SYMBOL(<ls_mbew2>) BINARY SEARCH.
check1 <ls_mbew1> <ls_mbew2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_mbew2 ASSIGNING <ls_mbew2>. " check form the other side to detect missing records in System 1
lv_key = |{ <ls_mbew2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mbew2>-bwkey }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mbew2>-bwtar }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mbew1 WITH KEY matnr = <ls_mbew2>-matnr bwkey = <ls_mbew2>-bwkey bwtar = <ls_mbew2>-bwtar TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MEAN'.
copy_tab ir_tab1 lt_mean1.
copy_tab ir_tab2 lt_mean2.
LOOP AT lt_mean1 ASSIGNING FIELD-SYMBOL(<ls_mean1>).
lv_key = |{ <ls_mean1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mean1>-meinh }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mean1>-ean11 }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mean2 WITH KEY matnr = <ls_mean1>-matnr meinh = <ls_mean1>-meinh ean11 = <ls_mean1>-ean11 ASSIGNING FIELD-SYMBOL(<ls_mean2>) BINARY SEARCH.
CLEAR: <ls_mean1>-lfnum, <ls_mean2>-lfnum. " we don't want to compare this field, it may be different in both systems
check1 <ls_mean1> <ls_mean2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_mean2 ASSIGNING <ls_mean2>. " check form the other side to detect missing records in System 1
lv_key = |{ <ls_mean2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mean2>-meinh }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mean2>-ean11 }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mean1 WITH KEY matnr = <ls_mean2>-matnr meinh = <ls_mean2>-meinh ean11 = <ls_mean2>-ean11 TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MLGN'.
copy_tab ir_tab1 lt_mlgn1.
copy_tab ir_tab2 lt_mlgn2.
LOOP AT lt_mlgn1 ASSIGNING FIELD-SYMBOL(<ls_mlgn1>).
lv_key = |{ <ls_mlgn1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mlgn1>-lgnum }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mlgn2 WITH KEY matnr = <ls_mlgn1>-matnr lgnum = <ls_mlgn1>-lgnum ASSIGNING FIELD-SYMBOL(<ls_mlgn2>) BINARY SEARCH.
check1 <ls_mlgn1> <ls_mlgn2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_mlgn2 ASSIGNING <ls_mlgn2>.
lv_key = |{ <ls_mlgn2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mlgn2>-lgnum }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mlgn1 WITH KEY matnr = <ls_mlgn2>-matnr lgnum = <ls_mlgn2>-lgnum TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MLGT'.
copy_tab ir_tab1 lt_mlgt1.
copy_tab ir_tab2 lt_mlgt2.
LOOP AT lt_mlgt1 ASSIGNING FIELD-SYMBOL(<ls_mlgt1>).
lv_key = |{ <ls_mlgt1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mlgt1>-lgnum }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mlgt1>-lgtyp }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mlgt2 WITH KEY matnr = <ls_mlgt1>-matnr lgnum = <ls_mlgt1>-lgnum lgtyp = <ls_mlgt1>-lgtyp ASSIGNING FIELD-SYMBOL(<ls_mlgt2>) BINARY SEARCH.
check1 <ls_mlgt1> <ls_mlgt2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_mlgt2 ASSIGNING <ls_mlgt2>. " check form the other side to detect missing records in System 1
lv_key = |{ <ls_mlgt2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mlgt2>-lgnum }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mlgt2>-lgtyp }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mlgt1 WITH KEY matnr = <ls_mlgt2>-matnr lgnum = <ls_mlgt2>-lgnum lgtyp = <ls_mlgt2>-lgtyp TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'MVKE'.
copy_tab ir_tab1 lt_mvke1.
copy_tab ir_tab2 lt_mvke2.
LOOP AT lt_mvke1 ASSIGNING FIELD-SYMBOL(<ls_mvke1>).
lv_key = |{ <ls_mvke1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mvke1>-vkorg }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mvke1>-vtweg }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mvke2 WITH KEY matnr = <ls_mvke1>-matnr vkorg = <ls_mvke1>-vkorg vtweg = <ls_mvke1>-vtweg ASSIGNING FIELD-SYMBOL(<ls_mvke2>) BINARY SEARCH.
check1 <ls_mvke1> <ls_mvke2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_mvke2 ASSIGNING <ls_mvke2>. " check form the other side to detect missing records in System 1
lv_key = |{ <ls_mvke2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mvke2>-vkorg }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_mvke2>-vtweg }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_mvke1 WITH KEY matnr = <ls_mvke2>-matnr vkorg = <ls_mvke2>-vkorg vtweg = <ls_mvke2>-vtweg TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
WHEN 'QMAT'.
copy_tab ir_tab1 lt_qmat1.
copy_tab ir_tab2 lt_qmat2.
LOOP AT lt_qmat1 ASSIGNING FIELD-SYMBOL(<ls_qmat1>).
lv_key = |{ <ls_qmat1>-art }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_qmat1>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_qmat1>-werks }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_qmat2 WITH KEY art = <ls_qmat1>-art matnr = <ls_qmat1>-matnr werks = <ls_qmat1>-werks ASSIGNING FIELD-SYMBOL(<ls_qmat2>) BINARY SEARCH.
check1 <ls_qmat1> <ls_qmat2>.
ENDLOOP.
IF iv_ec_s2 EQ abap_true.
LOOP AT lt_qmat2 ASSIGNING <ls_qmat2>. " check form the other side to detect missing records in System 1
lv_key = |{ <ls_qmat2>-art }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_qmat2>-matnr ALPHA = OUT }{ zcl_tables_compare=>gc_key_delimeter }{ <ls_qmat2>-werks }|.
CONDENSE lv_key NO-GAPS.
READ TABLE lt_qmat1 WITH KEY art = <ls_qmat2>-art matnr = <ls_qmat2>-matnr werks = <ls_qmat2>-werks TRANSPORTING NO FIELDS BINARY SEARCH.
check2.
ENDLOOP.
ENDIF.
ENDCASE.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_TABLES_COMPARE=>GET_TABLE_FIELD_DESCR
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_TFNAME TYPE ZTAB_COMPARE-TFNAME
* | [<-()] RV_RESULT TYPE STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_table_field_descr.
TYPES: BEGIN OF lty_tf_name,
tfname TYPE ztab_compare-tfname,
fdescr TYPE string,
END OF lty_tf_name.
STATICS: st_fields TYPE HASHED TABLE OF lty_tf_name WITH UNIQUE KEY tfname,
sv_tables TYPE string.
READ TABLE st_fields ASSIGNING FIELD-SYMBOL(<ls_fld>) WITH TABLE KEY tfname = iv_tfname.
IF sy-subrc EQ 0.
rv_result = <ls_fld>-fdescr.
RETURN.
ENDIF.
DATA(lv_tname) = substring_before( val = iv_tfname sub = '-' ).
IF lv_tname IS INITIAL OR sv_tables CS lv_tname.
RETURN.
ENDIF.
sv_tables = |{ sv_tables };{ lv_tname }|.
DATA(lo_struc_descr) = CAST cl_abap_structdescr( cl_abap_structdescr=>describe_by_name( lv_tname ) ).
LOOP AT lo_struc_descr->get_ddic_field_list( p_langu = sy-langu ) ASSIGNING FIELD-SYMBOL(<ls_comp>).
INSERT VALUE #( tfname = |{ lv_tname }-{ <ls_comp>-fieldname }| fdescr = <ls_comp>-scrtext_l ) INTO TABLE st_fields.
ENDLOOP.
rv_result = VALUE #( st_fields[ tfname = iv_tfname ]-fdescr OPTIONAL ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_TABLES_COMPARE=>GET_TABLE_KEY_FIELDS
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_TABNAME TYPE TABNAME
* | [<-()] RV_RESULT TYPE STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_table_key_fields.
SELECT fieldname INTO TABLE @DATA(lt_key_fields) FROM dd03l
WHERE tabname EQ @iv_tabname AND keyflag EQ @abap_true AND fieldname NE 'MANDT'
ORDER BY position.
CONCATENATE LINES OF lt_key_fields INTO rv_result SEPARATED BY ';'.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_TABLES_COMPARE=>IS_STATUS_DIFFERENT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_S1 TYPE ANY
* | [--->] IV_S2 TYPE ANY
* | [<-()] RV_RESULT TYPE ABAP_BOOL
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD is_status_different.
DATA: lv_pos TYPE i VALUE 0,
lv_len TYPE i.
rv_result = abap_false.
lv_len = strlen( iv_s1 ).
IF lv_len NE strlen( iv_s2 ).
rv_result = abap_true. RETURN.
ENDIF.
DO.
IF lv_pos GE lv_len.
RETURN.
ENDIF.
IF iv_s2 NS iv_s1+lv_pos(1).
rv_result = abap_true. RETURN.
ENDIF.
lv_pos = lv_pos + 1.
ENDDO.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_TABLES_COMPARE=>READ_TABLE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_RFC_DEST TYPE RFCDEST
* | [--->] IV_TABNAME TYPE TABNAME
* | [--->] IV_KEY_FIELDS TYPE STRING
* | [--->] IV_EXCL_FIELDS TYPE STRING(optional)
* | [--->] IV_RANGE_FROM TYPE CHAR100(optional)
* | [--->] IV_RANGE_TO TYPE CHAR100(optional)
* | [<-()] RT_RESULT TYPE REF TO DATA
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD read_table.
DATA: lt_options TYPE STANDARD TABLE OF rfc_db_opt WITH DEFAULT KEY,
lt_all_fields TYPE ty_fields_t,
lt_key_fields TYPE ty_fields_t,
lt_sel_fields TYPE ty_fields_t,
lt_pkg_fields TYPE ty_fields_t,
lt_data TYPE STANDARD TABLE OF tab512 WITH DEFAULT KEY,
lt_comp TYPE cl_abap_structdescr=>component_table,
lr_tab TYPE REF TO data,
lr_wa TYPE REF TO data,
lt_tr_fields TYPE ty_fields_t,
lv_rowcount TYPE soid-accnt VALUE 0.
FIELD-SYMBOLS: <lt_out> TYPE INDEX TABLE.
DATA: lt_packages TYPE stringtab,
lv_offset TYPE i.
" first compare if structures of table are the same in both systems
CALL FUNCTION 'RFC_READ_TABLE'
DESTINATION iv_rfc_dest
EXPORTING
query_table = iv_tabname
no_data = abap_true
TABLES
fields = lt_all_fields
EXCEPTIONS
OTHERS = 7.
" remove excluded fields and get key fields
LOOP AT lt_all_fields ASSIGNING FIELD-SYMBOL(<ls_field>).
IF iv_key_fields CS <ls_field>-fieldname.
APPEND <ls_field> TO lt_key_fields.
CONTINUE.
ENDIF.
IF iv_excl_fields CS <ls_field>-fieldname.
CONTINUE.
ENDIF.
APPEND <ls_field> TO lt_sel_fields.
ENDLOOP.
ASSERT lt_key_fields IS NOT INITIAL.
" ... and prepare selection range for WHERE clause
DATA(lv_first_key) = VALUE #( lt_key_fields[ 1 ]-fieldname ).
IF iv_range_from IS INITIAL AND iv_range_to IS INITIAL.
lt_options = VALUE #( ( text = |1 EQ 1| ) ). " select all but with some fuse
lv_rowcount = 100000.
ELSEIF iv_range_from IS NOT INITIAL AND iv_range_to IS NOT INITIAL.
lt_options = VALUE #( ( text = |{ lv_first_key } BETWEEN '{ iv_range_from }' AND '{ iv_range_to }'| ) ).
ELSEIF iv_range_from IS NOT INITIAL.
lt_options = VALUE #( ( text = |{ lv_first_key } EQ '{ iv_range_from }'| ) ).
ELSE. " upper limit provided without lower
ASSERT 1 EQ 2.
ENDIF.
*>>> prepare output table
LOOP AT lt_all_fields ASSIGNING <ls_field>.
APPEND VALUE #( name = <ls_field>-fieldname type = cl_abap_elemdescr=>get_c( p_length = CONV #( <ls_field>-length ) ) ) TO lt_comp.
ENDLOOP.
DATA(lo_tab_struc) = cl_abap_structdescr=>create( p_components = lt_comp ).
DATA(lt_ddic) = CAST cl_abap_structdescr( cl_abap_structdescr=>describe_by_name( iv_tabname ) )->get_ddic_field_list( ).
SORT lt_ddic BY fieldname.
CREATE DATA lr_wa TYPE HANDLE lo_tab_struc.
ASSIGN lr_wa->* TO FIELD-SYMBOL(<ls_wa_out>).
DATA(lt_keys) = VALUE abap_table_keydescr_tab( ( name = 'MAIN'
is_primary = abap_true
is_unique = abap_true
access_kind = cl_abap_tabledescr=>tablekind_sorted
key_kind = cl_abap_tabledescr=>keydefkind_user "keydefkind_user
components = CORRESPONDING #( lt_key_fields MAPPING name = fieldname ) ) ).
TRY.
DATA(lo_tab_descr) = cl_abap_tabledescr=>create_with_keys( p_line_type = lo_tab_struc
p_keys = lt_keys ).
CATCH cx_sy_table_creation INTO DATA(lx_error).
RETURN.
ENDTRY.
CREATE DATA lr_tab TYPE HANDLE lo_tab_descr.
ASSIGN lr_tab->* TO <lt_out>.
*<<<
" collect data in packages up to 512 characters
DO.
IF lt_sel_fields IS INITIAL.
EXIT. " DO..ENDDO
ENDIF.
CLEAR: lv_offset, lt_pkg_fields, lt_tr_fields, lt_data.
LOOP AT lt_key_fields ASSIGNING <ls_field>.
lv_offset = lv_offset + <ls_field>-length.
APPEND <ls_field> TO lt_pkg_fields.
ENDLOOP.
LOOP AT lt_sel_fields ASSIGNING <ls_field>.
lv_offset = lv_offset + <ls_field>-length.
IF lv_offset GE 512.
EXIT. "loop
ENDIF.
APPEND <ls_field> TO: lt_pkg_fields, lt_tr_fields.
DELETE lt_sel_fields USING KEY loop_key.
ENDLOOP.
CALL FUNCTION 'RFC_READ_TABLE'
DESTINATION iv_rfc_dest
EXPORTING
query_table = iv_tabname
rowcount = lv_rowcount
TABLES
options = lt_options
fields = lt_pkg_fields
data = lt_data
EXCEPTIONS
OTHERS = 7.
IF sy-subrc <> 0.
* Implement suitable error handling here
ENDIF.
LOOP AT lt_data ASSIGNING FIELD-SYMBOL(<ls_data>).
CLEAR <ls_wa_out>.
LOOP AT lt_pkg_fields ASSIGNING <ls_field>.
" the RFC FM is a bit crappy, when e.g. field is of type P length 3 dec 1 (example MDA MARC-LGRAD for 23430/0400)
" it tries to convert the 99.0 into CHAR3 and in result there is set '*.0' there !?!?
" in fact the FM is obsolete (see note 382318), see also something new in notes 2246160 and 3139000
ASSIGN COMPONENT <ls_field>-fieldname OF STRUCTURE <ls_wa_out> TO FIELD-SYMBOL(<lv_fvalue>).
IF sy-subrc EQ 0.
READ TABLE lt_ddic ASSIGNING FIELD-SYMBOL(<ls_ddic>) WITH KEY fieldname = <ls_field>-fieldname.
IF sy-subrc EQ 0 AND <ls_ddic>-inttype EQ 'P'.
IF <ls_data>-wa+<ls_field>-offset(<ls_field>-length) CS '*'.
<lv_fvalue> = '6.9'. " set here something not initial to at least detect comparison with 0
ELSE.
<lv_fvalue> = <ls_data>-wa+<ls_field>-offset(<ls_field>-length).
ENDIF.
ELSE.
<lv_fvalue> = <ls_data>-wa+<ls_field>-offset(<ls_field>-length).
ENDIF.
ENDIF.
ENDLOOP.
READ TABLE <lt_out> FROM <ls_wa_out> ASSIGNING FIELD-SYMBOL(<ls_found>).
IF sy-subrc EQ 0.
LOOP AT lt_tr_fields ASSIGNING <ls_field>.
ASSIGN COMPONENT <ls_field>-fieldname OF STRUCTURE <ls_wa_out> TO FIELD-SYMBOL(<lv_src>).
IF sy-subrc EQ 0.
ASSIGN COMPONENT <ls_field>-fieldname OF STRUCTURE <ls_found> TO FIELD-SYMBOL(<lv_dst>).
IF sy-subrc EQ 0.
<lv_dst> = <lv_src>.
ENDIF.
ENDIF.
ENDLOOP.
ELSE.
INSERT <ls_wa_out> INTO TABLE <lt_out>.
ENDIF.
ENDLOOP.
ENDDO.
rt_result = lr_tab.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_TABLES_COMPARE=>RUN_COMPARISON
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_RFC_DEST1 TYPE RFCDEST
* | [--->] IV_RFC_DEST2 TYPE RFCDEST
* | [--->] IV_TABNAME TYPE TABNAME
* | [--->] IV_KEY_FIELDS TYPE STRING
* | [--->] IV_EXCL_FIELDS TYPE STRING(optional)
* | [--->] IV_RANGE_FROM TYPE CHAR100
* | [--->] IV_RANGE_TO TYPE CHAR100
* | [--->] IV_EC_S1 TYPE ABAP_BOOL (default =ABAP_TRUE)
* | [--->] IV_EC_S2 TYPE ABAP_BOOL (default =ABAP_TRUE)
* | [<-()] RT_RESULT TYPE TY_LOG_T
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD run_comparison.
" get data from RFC1
DATA(lr_data1) = read_table( EXPORTING iv_rfc_dest = iv_rfc_dest1
iv_tabname = iv_tabname
iv_key_fields = iv_key_fields
iv_excl_fields = iv_excl_fields
iv_range_from = iv_range_from
iv_range_to = iv_range_to ).
" get data from RFC2
DATA(lr_data2) = read_table( EXPORTING iv_rfc_dest = iv_rfc_dest2
iv_tabname = iv_tabname
iv_key_fields = iv_key_fields
iv_excl_fields = iv_excl_fields
iv_range_from = iv_range_from
iv_range_to = iv_range_to ).
compare_tables( EXPORTING iv_tabname = iv_tabname
ir_tab1 = lr_data1
ir_tab2 = lr_data2
iv_ec_s1 = iv_ec_s1
iv_ec_s2 = iv_ec_s2
CHANGING ct_log = rt_result ).
ENDMETHOD.
ENDCLASS.
Now the program which collects the delta:
Create new executable program ZPST_TEST_COMPARE with the code below (program name is nowhere used, so set it as you wish):
*&---------------------------------------------------------------------*
*& Report ZPST_TEST_COMPARE
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zpst_test_compare.
DATA: gr_salv TYPE REF TO cl_salv_table,
gt_log TYPE zcl_tables_compare=>ty_log_t.
PARAMETERS: p_rfc1 TYPE rfcdest DEFAULT 'MDGCLNT100',
p_rfc2 TYPE rfcdest DEFAULT 'ECCCLNT100'.
SELECT-OPTIONS: s_matnr FOR ('MATNR') NO-EXTENSION OBLIGATORY DEFAULT '000000000000042373' TO '000000000000044335',
s_fexcl FOR ('USMD_FIELDNAME') NO INTERVALS.
PARAMETERS: p_mara TYPE c AS CHECKBOX DEFAULT 'X',
p_marc TYPE c AS CHECKBOX,
p_makt TYPE c AS CHECKBOX,
p_mard TYPE c AS CHECKBOX,
p_marm TYPE c AS CHECKBOX,
p_mbew TYPE c AS CHECKBOX,
p_mean TYPE c AS CHECKBOX,
p_mlgn TYPE c AS CHECKBOX,
p_mlgt TYPE c AS CHECKBOX,
* p_mpgd TYPE c AS CHECKBOX,
* p_mpop TYPE c AS CHECKBOX,
p_mvke TYPE c AS CHECKBOX,
p_qmat TYPE c AS CHECKBOX.
* p_mdma TYPE c AS CHECKBOX,
SELECTION-SCREEN SKIP.
PARAMETERS: p_s1_ec TYPE abap_bool AS CHECKBOX DEFAULT abap_true,
p_s2_ec TYPE abap_bool AS CHECKBOX DEFAULT abap_false.
INCLUDE zpst_test_compare_forms.
*-----------------------------------------------------------------------------------
START-OF-SELECTION.
IF p_mara EQ abap_true.
PERFORM compare_table USING 'MARA' CHANGING gt_log.
ENDIF.
IF p_marc EQ abap_true.
PERFORM compare_table USING 'MARC' CHANGING gt_log.
ENDIF.
IF p_makt EQ abap_true.
PERFORM compare_table USING 'MAKT' CHANGING gt_log.
ENDIF.
IF p_mard EQ abap_true.
PERFORM compare_table USING 'MARD' CHANGING gt_log.
ENDIF.
IF p_marm EQ abap_true.
PERFORM compare_table USING 'MARM' CHANGING gt_log.
ENDIF.
IF p_mbew EQ abap_true.
PERFORM compare_table USING 'MBEW' CHANGING gt_log.
ENDIF.
IF p_mean EQ abap_true.
PERFORM compare_table USING 'MEAN' CHANGING gt_log.
ENDIF.
IF p_mlgn EQ abap_true.
PERFORM compare_table USING 'MLGN' CHANGING gt_log.
ENDIF.
IF p_mlgt EQ abap_true.
PERFORM compare_table USING 'MLGT' CHANGING gt_log.
ENDIF.
IF p_mvke EQ abap_true.
PERFORM compare_table USING 'MVKE' CHANGING gt_log.
ENDIF.
IF p_qmat EQ abap_true.
PERFORM compare_table USING 'QMAT' CHANGING gt_log.
ENDIF.
SORT gt_log BY rkey tfname.
*-----------------------------------------------------------------------------------
END-OF-SELECTION.
DATA(gv_repid) = sy-repid.
TRY.
cl_salv_table=>factory( IMPORTING r_salv_table = gr_salv
CHANGING t_table = gt_log ).
CATCH cx_salv_msg. "#EC NO_HANDLER
RETURN.
ENDTRY.
gr_salv->set_screen_status( pfstatus = 'SALV_STANDARD'
report = gv_repid
set_functions = cl_salv_table=>c_functions_all ).
PERFORM salv_set_columns USING gr_salv.
gr_salv->get_layout( )->set_key( VALUE #( report = gv_repid ) ).
gr_salv->get_layout( )->set_default( abap_true ).
gr_salv->get_layout( )->set_save_restriction( if_salv_c_layout=>restrict_none ).
gr_salv->get_functions( )->set_print_preview( abap_false ).
DATA(gr_events) = NEW lcl_handle_events( ). "#EC NEEDED
SET HANDLER gr_events->on_user_command FOR gr_salv->get_event( ).
gr_salv->display( ).
Double click:
And create the include with code below:
*&---------------------------------------------------------------------*
*& Include ZPST_TEST_COMPARE_FORMS
*&---------------------------------------------------------------------*
CLASS lcl_handle_events DEFINITION.
PUBLIC SECTION.
METHODS:
on_user_command FOR EVENT added_function OF cl_salv_events IMPORTING e_salv_function.
ENDCLASS.
CLASS lcl_handle_events IMPLEMENTATION.
METHOD on_user_command.
PERFORM show_function_info USING e_salv_function.
ENDMETHOD. "on_user_command
ENDCLASS.
*-----------------------------------------------------------------------------------
FORM salv_set_columns USING ir_alv TYPE REF TO cl_salv_table .
DATA: lo_col TYPE REF TO cl_salv_column_table.
DATA(lo_columns) = ir_alv->get_columns( ).
lo_columns->set_optimize( ).
TRY.
" set Code Text columns names
DATA(lt_columns) = lo_columns->get( ).
LOOP AT lt_columns ASSIGNING FIELD-SYMBOL(<ls_column>).
lo_col = CAST #( <ls_column>-r_column ).
CASE <ls_column>-columnname.
WHEN 'RKEY'. lo_col->set_long_text( CONV #( 'Key' ) ).
WHEN 'TFNAME'. lo_col->set_long_text( CONV #( 'Table-Field Name' ) ).
WHEN 'V1'. lo_col->set_long_text( CONV #( |Value in { p_rfc1 }| ) ).
WHEN 'V2'. lo_col->set_long_text( CONV #( |Value in { p_rfc2 }| ) ).
WHEN 'FDESCR'. lo_col->set_long_text( CONV #( 'Field Description' ) ).
ENDCASE.
ENDLOOP.
CATCH cx_salv_not_found.
RETURN.
ENDTRY.
ENDFORM.
FORM show_function_info USING i_function TYPE salv_de_function.
DATA: ls_ztab_compare TYPE ztab_compare,
lt_ztab_compare TYPE STANDARD TABLE OF ztab_compare WITH KEY tfname rkey.
CASE i_function.
WHEN 'SAVEDB'.
IF gt_log IS NOT INITIAL.
LOOP AT gt_log ASSIGNING FIELD-SYMBOL(<ls_log>).
ls_ztab_compare = CORRESPONDING #( <ls_log> ).
ls_ztab_compare-v1 = condense( val = ls_ztab_compare-v1 from = '' ).
ls_ztab_compare-v2 = condense( val = ls_ztab_compare-v2 from = '' ).
IF ls_ztab_compare-v1 EQ 'does not exist'. CLEAR ls_ztab_compare-v1. ENDIF.
IF ls_ztab_compare-v2 EQ 'does not exist'. CLEAR ls_ztab_compare-v2. ENDIF.
IF ls_ztab_compare-v1 EQ 'record exists'. ls_ztab_compare-v1 = abap_true. ENDIF.
IF ls_ztab_compare-v2 EQ 'record exists'. ls_ztab_compare-v2 = abap_true. ENDIF.
APPEND ls_ztab_compare TO lt_ztab_compare.
ENDLOOP.
MODIFY ztab_compare FROM TABLE lt_ztab_compare.
COMMIT WORK.
MESSAGE s208(00) WITH 'Data saved in ZTAB_COMPARE'.
ENDIF.
WHEN 'CLEARDB'.
DELETE FROM ztab_compare.
COMMIT WORK.
MESSAGE s208(00) WITH 'Content of ZTAB_COMPARE deleted'.
ENDCASE.
ENDFORM.
*-----------------------------------------------------------------------------------
FORM get_excluded USING iv_tabname TYPE tabname
CHANGING cv_excluded TYPE string.
CLEAR cv_excluded.
LOOP AT s_fexcl INTO DATA(ls_so) WHERE sign EQ 'I' AND option EQ 'EQ'.
IF ls_so-low CP |{ iv_tabname }-*|.
IF cv_excluded IS INITIAL.
cv_excluded = substring_after( val = ls_so-low sub = |{ iv_tabname }-| ).
ELSE.
cv_excluded = cv_excluded && ';' && substring_after( val = ls_so-low sub = |{ iv_tabname }-| ).
ENDIF.
ENDIF.
ENDLOOP.
ENDFORM.
FORM compare_table USING iv_tabname TYPE tabname
CHANGING ct_log TYPE zcl_tables_compare=>ty_log_t.
DATA: lv_excluded TYPE string,
lt_log TYPE zcl_tables_compare=>ty_log_t.
PERFORM get_excluded USING iv_tabname CHANGING lv_excluded.
lt_log = zcl_tables_compare=>run_comparison( iv_rfc_dest1 = p_rfc1
iv_rfc_dest2 = p_rfc2
iv_tabname = iv_tabname
iv_key_fields = zcl_tables_compare=>get_table_key_fields( iv_tabname )
iv_excl_fields = lv_excluded
iv_range_from = VALUE #( s_matnr[ 1 ]-low OPTIONAL )
iv_range_to = VALUE #( s_matnr[ 1 ]-high OPTIONAL )
iv_ec_s1 = p_s1_ec
iv_ec_s2 = p_s2_ec ).
"update_field_descriptions
LOOP AT lt_log ASSIGNING FIELD-SYMBOL(<ls_log>).
<ls_log>-fdescr = zcl_tables_compare=>get_table_field_descr( <ls_log>-tfname ).
ENDLOOP.
APPEND LINES OF lt_log TO ct_log.
ENDFORM.
Create GUI status SALV_STANDARD (Normal Screen):
Copy from template like below:
Use template status: SALV_STANDARD of program SALV_DEMO_TABLE_FUNCTIONS
Delete the MYFUNCTION and adjust the three function codes:
(Save and activate)
Then program’s selection texts:
After activation the program should work, now a few words abut the selection parameters
RFC System 1 and 2 – these are RFC connections configured in SM59 pointing to two systems from which you want to compare the material master data. If the connections are set up without user/password (recommended) then each time at program execution there will be logon window shown (standard behavior with RFC call). Usually select the Data Hub as System 1 and as System 2 the data destination system.
In Material Number Range provide the range of materials you want to compare. It is recommended to first test on some small range (e.g., 100 materials) how fast they are processed, the speed will depend on size of the sub-tables (MARC, MVKE, …). Normally you run the comparison in packages of a few thousands records and save each comparison result in Custom Delta Table with “Save DB” button (see below).
Excluded field names – here you list fields which you don’t want to compare these will be “created on”, “created by” and all the fields which are “editable” in satellite system (like forecasting, costing, current period/year, stocks, etc.). Usually, you can identify such fields by running the delta collection without exclusion of any fields (on small amount of records) and checking which fields have many delta records. The form of the field to be set here is <table name>-<field name>, e.g. MARA-AENAM, MARC-LFGJA, MAKT-MAKTG
Table Names Checkboxes – here you select which tables you want to compare (the program can be easily extended with other material tables like MPOP, MDMA or custom extensions)
The final two checkboxes are used to collect records which exist in one system but not in the other. By default, the first is ON (we want to find if all records from source system we replicated to destination) and the second is OFF (in destination system the material can be enhanced with additional plants, sales orgs, etc. if there is many of such enhancements the delta table may grow up rapidly if have the flag ON)
After running the program, you should get in result something like below
With the two pointed above buttons you can: save the result in table ZTAB_COMPARE (appending the collected records to existing ones) and clean up the whole table ZTAB_COMPARE.
Results Display Program
Already at this point you can export the collected records from Custom Delta Table to xlsx and format them as needed. However, for those who like FPM but don’t know how to use it I’m describing below the steps how to create simple report.
First create new class ZCL_TABLES_COMPARE_FEEDER from the code below:
class ZCL_TABLES_COMPARE_FEEDER definition
public
final
create public .
public section.
interfaces IF_FPM_GUIBB .
interfaces IF_FPM_GUIBB_LIST .
PROTECTED SECTION.
PRIVATE SECTION.
TYPES:
BEGIN OF ty_difference,
tabname TYPE tabname16,
tfname TYPE ztab_compare-tfname,
fdescr TYPE text40,
rkey TYPE ztab_compare-rkey,
v1 TYPE ztab_compare-v1,
v2 TYPE ztab_compare-v2,
END OF ty_difference .
TYPES:
ty_difference_tab TYPE STANDARD TABLE OF ty_difference WITH DEFAULT KEY .
CLASS-DATA gt_data TYPE ty_difference_tab .
ENDCLASS.
CLASS ZCL_TABLES_COMPARE_FEEDER IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB_LIST~CHECK_CONFIG
* +-------------------------------------------------------------------------------------------------+
* | [--->] IO_LAYOUT_CONFIG TYPE REF TO IF_FPM_GUIBB_LIST_CONFIG
* | [<---] ET_MESSAGES TYPE FPMGB_T_MESSAGES
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD IF_FPM_GUIBB_LIST~CHECK_CONFIG.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB_LIST~FLUSH
* +-------------------------------------------------------------------------------------------------+
* | [--->] IT_CHANGE_LOG TYPE FPMGB_T_CHANGELOG
* | [--->] IT_DATA TYPE REF TO DATA
* | [--->] IV_OLD_LEAD_SEL TYPE I(optional)
* | [--->] IV_NEW_LEAD_SEL TYPE I(optional)
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD IF_FPM_GUIBB_LIST~FLUSH.
FIELD-SYMBOLS: <lt_data> LIKE gt_data.
ASSIGN it_data->* TO <lt_data>.
gt_data = <lt_data>.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB_LIST~GET_DATA
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_EVENTID TYPE REF TO CL_FPM_EVENT
* | [--->] IT_SELECTED_FIELDS TYPE FPMGB_T_SELECTED_FIELDS(optional)
* | [--->] IV_RAISED_BY_OWN_UI TYPE BOOLE_D(optional)
* | [--->] IV_VISIBLE_ROWS TYPE I(optional)
* | [--->] IV_EDIT_MODE TYPE FPM_EDIT_MODE(optional)
* | [--->] IO_EXTENDED_CTRL TYPE REF TO IF_FPM_LIST_ATS_EXT_CTRL(optional)
* | [<---] ET_MESSAGES TYPE FPMGB_T_MESSAGES
* | [<---] EV_DATA_CHANGED TYPE BOOLE_D
* | [<---] EV_FIELD_USAGE_CHANGED TYPE BOOLE_D
* | [<---] EV_ACTION_USAGE_CHANGED TYPE BOOLE_D
* | [<---] EV_SELECTED_LINES_CHANGED TYPE BOOLE_D
* | [<---] EV_DND_ATTR_CHANGED TYPE BOOLE_D
* | [<---] EO_ITAB_CHANGE_LOG TYPE REF TO IF_SALV_ITAB_CHANGE_LOG
* | [<-->] CT_DATA TYPE DATA
* | [<-->] CT_FIELD_USAGE TYPE FPMGB_T_FIELDUSAGE
* | [<-->] CT_ACTION_USAGE TYPE FPMGB_T_ACTIONUSAGE
* | [<-->] CT_SELECTED_LINES TYPE RSTABIXTAB
* | [<-->] CV_LEAD_INDEX TYPE SYTABIX
* | [<-->] CV_FIRST_VISIBLE_ROW TYPE I
* | [<-->] CS_ADDITIONAL_INFO TYPE FPMGB_S_ADDITIONAL_INFO(optional)
* | [<-->] CT_DND_ATTRIBUTES TYPE FPMGB_T_DND_DEFINITION(optional)
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD IF_FPM_GUIBB_LIST~GET_DATA.
DATA: lv_tname TYPE string,
lv_fname TYPE string.
IF iv_eventid->mv_event_id EQ 'FPM_START'.
SELECT tfname, rkey, v1, v2 INTO CORRESPONDING FIELDS OF TABLE @gt_data FROM ztab_compare ORDER BY PRIMARY KEY.
LOOP AT gt_data ASSIGNING FIELD-SYMBOL(<ls_data>).
SPLIT <ls_data>-tfname AT '-' INTO <ls_data>-tabname lv_fname.
IF 'MAKT;MARA;MARC;MARD;MARM;MBEW;MEAN;MLGN;MLGT;MVKE;QMAT' CS <ls_data>-tfname.
IF <ls_data>-v1 EQ abap_true.
<ls_data>-fdescr = | Record exists only in System 1|.
ELSE.
<ls_data>-fdescr = | Record exists only in System 2|.
ENDIF.
ELSE.
<ls_data>-fdescr = |{ lv_fname } : { zcl_tables_compare=>get_table_field_descr( <ls_data>-tfname ) }|.
ENDIF.
ENDLOOP.
ct_data = gt_data.
ev_data_changed = abap_true.
ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB_LIST~GET_DEFAULT_CONFIG
* +-------------------------------------------------------------------------------------------------+
* | [--->] IO_LAYOUT_CONFIG TYPE REF TO IF_FPM_GUIBB_LIST_CONFIG
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD if_fpm_guibb_list~get_default_config.
DEFINE add_column.
io_layout_config->add_column( iv_name = &2
iv_display_type = 'TV'
iv_index = &1
iv_header = &3 ).
END-OF-DEFINITION.
io_layout_config->set_settings( iv_height_mode_ats = if_fpm_list_types=>cs_height_mode_ats-all_rows
iv_export_to_excel = abap_true
iv_export_format = if_fpm_list_types=>cs_export_format-office_open_xml
iv_fit_to_table_width = abap_true
iv_selection_mode_ats = if_fpm_list_types=>cs_selection_mode-single_no_lead
iv_scroll_mode = if_fpm_list_types=>cs_scroll_mode-scrolling
iv_allow_sorting = if_fpm_list_types=>cs_settings_allow_sorting-only_ad_hoc
iv_allow_grouping = if_fpm_list_types=>cs_settings_allow_grouping-no_grouping
iv_sort_by_relevance = abap_true
it_default_sorting_ats = VALUE #( ( column_name = 'TABNAME' is_grouped = abap_true )
( column_name = 'FDESCR' is_grouped = abap_true ) )
).
TRY.
add_column 1 'TABNAME' 'Table'.
add_column 2 'FDESCR' 'Field'.
add_column 3 'RKEY' 'Key'.
add_column 4 'V1' 'Value in System 1'.
add_column 5 'V2' 'Value in System 2'.
CATCH cx_fpm_configuration. "#EC NO_HANDLER
ENDTRY.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB_LIST~GET_DEFINITION
* +-------------------------------------------------------------------------------------------------+
* | [<---] EO_FIELD_CATALOG TYPE REF TO CL_ABAP_TABLEDESCR
* | [<---] ET_FIELD_DESCRIPTION TYPE FPMGB_T_LISTFIELD_DESCR
* | [<---] ET_ACTION_DEFINITION TYPE FPMGB_T_ACTIONDEF
* | [<---] ET_SPECIAL_GROUPS TYPE FPMGB_T_SPECIAL_GROUPS
* | [<---] ES_MESSAGE TYPE FPMGB_S_T100_MESSAGE
* | [<---] EV_ADDITIONAL_ERROR_INFO TYPE DOKU_OBJ
* | [<---] ET_DND_DEFINITION TYPE FPMGB_T_DND_DEFINITION
* | [<---] ET_ROW_ACTIONS TYPE FPMGB_T_ROW_ACTION
* | [<---] ES_OPTIONS TYPE FPMGB_S_LIST_OPTIONS
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD IF_FPM_GUIBB_LIST~GET_DEFINITION.
eo_field_catalog ?= cl_abap_tabledescr=>describe_by_data( gt_data ).
et_field_description = VALUE #( ( name = 'TFNAME' technical_field = abap_true )
( name = 'TABNAME' allow_sort = abap_true group_same_cells = abap_true )
( name = 'FDESCR' allow_sort = abap_true group_same_cells = abap_true )
( name = 'RKEY' )
( name = 'V1' )
( name = 'V2' ) ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB_LIST~PROCESS_EVENT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IO_EVENT TYPE REF TO CL_FPM_EVENT
* | [--->] IV_RAISED_BY_OWN_UI TYPE BOOLE_D(optional)
* | [--->] IV_LEAD_INDEX TYPE SYTABIX
* | [--->] IV_EVENT_INDEX TYPE SYTABIX
* | [--->] IT_SELECTED_LINES TYPE RSTABIXTAB
* | [--->] IO_UI_INFO TYPE REF TO IF_FPM_LIST_ATS_UI_INFO(optional)
* | [<---] EV_RESULT TYPE FPM_EVENT_RESULT
* | [<---] ET_MESSAGES TYPE FPMGB_T_MESSAGES
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD IF_FPM_GUIBB_LIST~PROCESS_EVENT.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB~GET_PARAMETER_LIST
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RT_PARAMETER_DESCR TYPE FPMGB_T_PARAM_DESCR
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD IF_FPM_GUIBB~GET_PARAMETER_LIST.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_TABLES_COMPARE_FEEDER->IF_FPM_GUIBB~INITIALIZE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IT_PARAMETER TYPE FPMGB_T_PARAM_VALUE
* | [--->] IO_APP_PARAMETER TYPE REF TO IF_FPM_PARAMETER(optional)
* | [--->] IV_COMPONENT_NAME TYPE FPM_COMPONENT_NAME(optional)
* | [--->] IS_CONFIG_KEY TYPE WDY_CONFIG_KEY(optional)
* | [--->] IV_INSTANCE_ID TYPE FPM_INSTANCE_ID(optional)
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD IF_FPM_GUIBB~INITIALIZE.
ENDMETHOD.
ENDCLASS.
Then, FPM application (some FPM screens will look different on newer SAP versions):
Set up OVP layout:
add there list UIBB with the feeder class created previously:
Provide „Config ID“ and „Title“, Save and click „Configure UIBB“ (ignore errors):
Because of some coding in the feeder class you don’t need to set anything more manually here except one crucial configuration:
Set “Collapse Groups by Default” – otherwise the application will want to show all the records at once which might be too big challenge in case of big deltas.
(In S/4 the screen below looks different, but still the option can be found there)
After Save the application should work, you can start it from SE80 or with configured link in SAP GUI:
Tips on usage of the tool
- In remote systems there is only one function module called: RFC_READ_TABLE, it just reads data, however might be blocked in your systems.
- To collect the data run the Delta Collection Program several times with smaller ranges of materials (and save each result in Custom Delta Table).
- Initially do some trial runs on <1000 materials to identify fields which you don’t want to compare, and save the excluded fields in selection variant for later use.
Each line of the code provided here was written by me and you can freely use/modify it.