• Inicio
  • Novedades
  • Academia SAP
  • FAQ
  • Blog
  • Contacto
S4PCADEMY_Logo
Twitter Linkedin Instagram
S4PCADEMY_Logo
Twitter Linkedin Instagram
FORMACIÓN SAP

Datos maestros de materiales: compare datos en dos sistemas

By s4pcademy 


En el escenario de Master Data Hub, el sistema MDG es la «fuente de la verdad», los datos maestros se replican en los sistemas satelitales y se pueden ampliar allí, pero en general, la parte común no debe cambiarse localmente porque en la próxima replicación se sobrescribirá con la versión de MDG.

En algunos casos, puede suceder que un usuario travieso o una interfaz externa modifiquen parte de los datos que no deben cambiarse y, a menudo, es necesario detectar tal situación lo antes posible.

En el mercado existen herramientas para comparar los datos maestros entre sistemas, pero normalmente no son gratuitas, tienen una configuración compleja y lleva tiempo aprender cómo funcionan.

En este blog, ofrezco una solución sobre cómo comparar los datos maestros de materiales entre dos sistemas con una codificación relativamente corta (en total, alrededor de 1k líneas de ABAP) y «algunos clics del mouse». El código se puede implementar en el sistema Sandbox y está listo para comparar datos en cualquier sistema en el que configure una conexión RFC.

Cómo funciona

A Programa de recogida Delta se conecta a dos sistemas seleccionados (los destinos RFC deben configurarse en SM59) y lee las tablas de datos maestros de material del Sistema 1 y el Sistema 2 (con el módulo de función RFC estándar RFC_READ_TABLE; lea atentamente la nota 382318). Después de la comparación campo por campo, el delta calculado se muestra en la salida.

Como las tablas de datos maestros de materiales suelen ser enormes y la recopilación delta lleva tiempo, la salida del programa mencionado anteriormente se puede guardar en un Tabla Delta personalizada.

Para una buena visualización de la comparación, se debe crear una aplicación FPM simple (sin embargo, también puede usar MS Excel).

Como resultado obtenemos algo como lo siguiente:

Dónde Programa de recogida Delta parece:

y el Tabla Delta personalizada:

Cómo configurarlo

(He intentado proporcionar instrucciones detalladas para que quede claro para las personas con conocimientos básicos de ABAP)

Primero el Tabla Delta personalizada donde se almacenarán los registros delta recopilados, así que SE11 y cree una tabla como se muestra a continuación (para simplificar, he usado elementos de datos genéricos, pero, por supuesto, puede configurar los suyos propios con etiquetas bonitas)

Luego, la clase utilizada para recopilar los datos y calcular delta:

Simplemente cree una nueva clase ZCL_TABLES_COMPARE, cambie al modo «Basado en código fuente» y pegue el código a continuación (+ guardar y activar). Ver los comentarios en la llamada de ‘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.

Ahora el programa que recoge el delta:

Cree un nuevo programa ejecutable ZPST_TEST_COMPARE con el siguiente código (el nombre del programa no se usa en ninguna parte, así que configúrelo como desee):

*&---------------------------------------------------------------------*
*& 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( ).

Haga doble clic:

Y crea el include con el siguiente código:

*&---------------------------------------------------------------------*
*&  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.

Crear estado de GUI SALV_STANDARD (pantalla normal):

Copie de la plantilla como a continuación:

Usar estado de plantilla: SALV_STANDARD del programa SALV_DEMO_TABLE_FUNCTIONS

Elimine MYFUNCTION y ajuste los tres códigos de función:

(Guardar y activar)

Luego los textos de selección del programa:

Después de la activación, el programa debería funcionar, ahora unas pocas palabras sobre los parámetros de selección

Sistema RFC 1 y 2 – estas son conexiones RFC configuradas en SM59 que apuntan a dos sistemas desde los cuales desea comparar los datos maestros de materiales. Si las conexiones se configuran sin usuario/contraseña (recomendado), cada vez que se ejecute el programa se mostrará una ventana de inicio de sesión (comportamiento estándar con llamada RFC). Por lo general, seleccione el Hub de datos como Sistema 1 y como Sistema 2 el sistema de destino de datos.

En Rango de número de material proporcione la gama de materiales que desea comparar. Se recomienda probar primero en un rango pequeño (por ejemplo, 100 materiales) qué tan rápido se procesan, la velocidad dependerá del tamaño de las subtablas (MARC, MVKE, …). Normalmente, ejecuta la comparación en paquetes de unos pocos miles de registros y guarda cada resultado de la comparación en la tabla Delta personalizada con el botón «Guardar base de datos» (ver más abajo).

Nombres de campos excluidos – aquí enumera los campos que no desea comparar, estos serán «creados en», «creados por» y todos los campos que son «editables» en el sistema satelital (como pronósticos, costos, período/año actual, existencias, etc.). Por lo general, puede identificar dichos campos ejecutando la colección delta sin excluir ningún campo (en una pequeña cantidad de registros) y verificando qué campos tienen muchos registros delta. La forma del campo a configurar aquí es –, por ejemplo, MARA-AENAM, MARC-LFGJA, MAKT-MAKTG

Casillas de verificación de nombres de tabla – aquí selecciona qué tablas desea comparar (el programa se puede ampliar fácilmente con otras tablas de materiales como MPOP, MDMA o extensiones personalizadas)

Las dos últimas casillas de verificación se utilizan para recopilar registros que existen en un sistema pero no en el otro. De manera predeterminada, el primero está activado (queremos averiguar si todos los registros del sistema de origen se replicaron en el destino) y el segundo está desactivado (en el sistema de destino, el material se puede mejorar con plantas adicionales, organizaciones de ventas, etc. si hay muchos). de dichas mejoras, la tabla delta puede crecer rápidamente si tiene la bandera activada)

Después de ejecutar el programa, debería obtener un resultado similar al siguiente

Con los dos botones señalados arriba puede: guardar el resultado en la tabla ZTAB_COMPARE (añadiendo los registros recopilados a los existentes) y limpiar toda la tabla ZTAB_COMPARE.

Programa de visualización de resultados

Ya en este punto, puede exportar los registros recopilados de Custom Delta Table a xlsx y formatearlos según sea necesario. Sin embargo, para aquellos a quienes les gusta FPM pero no saben cómo usarlo, describo a continuación los pasos para crear un informe simple.

Primero cree una nueva clase ZCL_TABLES_COMPARE_FEEDER a partir del siguiente código:

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.

Luego, la aplicación FPM (algunas pantallas de FPM se verán diferentes en las versiones más nuevas de SAP):

Configurar el diseño de OVP:

agregue allí la lista UIBB con la clase de alimentador creada anteriormente:

Proporcione «ID de configuración» y «Título», guarde y haga clic en «Configurar UIBB» (ignore los errores):

Debido a alguna codificación en la clase de alimentador, no necesita configurar nada más manualmente aquí, excepto una configuración crucial:

Establezca «Contraer grupos de forma predeterminada»; de lo contrario, la aplicación querrá mostrar todos los registros a la vez, lo que podría ser un desafío demasiado grande en el caso de grandes deltas.

(En S/4, la pantalla a continuación se ve diferente, pero aún así la opción se puede encontrar allí)

Después de Guardar, la aplicación debería funcionar, puede iniciarla desde SE80 o con un enlace configurado en SAP GUI:

Consejos sobre el uso de la herramienta.

  • En los sistemas remotos, solo hay un módulo de función llamado: RFC_READ_TABLE, solo lee datos, sin embargo, podría estar bloqueado en sus sistemas (consulte más detalles en la nota 382318).
  • Para recopilar los datos, ejecute el Programa de recogida Delta varias veces con rangos más pequeños de materiales (y guarde cada resultado en Tabla Delta personalizada).
  • Inicialmente, realice algunas ejecuciones de prueba en <1000 materiales para identificar los campos que no desea comparar y guarde los campos excluidos en la variante de selección para su uso posterior.

Cada línea del código proporcionado aquí fue escrita por mí y puedes usarla/modificarla libremente.

Cualquier comentario/pregunta/retroalimentación es bienvenida 🙂




Desmitificando SAP Build para principiantes
Previo
【SAP備忘録】SAP Build Process AutomationでAPI接続アクションを定義する | Blogs de SAP
Siguiente

Madrid

Calle Eloy Gonzalo, 27
Madrid, Madrid.
Código Postal 28010

México

Paseo de la Reforma 26
Colonia Juárez,  Cuauhtémoc
Ciudad de México 06600

Costa Rica

Real Cariari
Autopista General Cañas, 
San José, SJ 40104

Perú

Av. Jorge Basadre 349
San Isidro
Lima, LIM 15073

Twitter Linkedin Instagram
Copyright 2022 | All Right Reserved.