目录
1. 表技术设置-日志数据更改
2. SE16N-表数据更改日志
3. SCDO 文档更改对象
4. 自定义日志
表技术设置中勾选如下标识-Log Data Changes
使用SM30维护表数据时会自动记录数据变更日志,日志查看路径如下(或使用程序RSVTPROT)
日志效果:
前置:
RZ11参数rec/client设置为all或者含当前client的值(默认关闭)
日志数据对应表:DBTABLOG
PS: 日志可以归档,删除操作,参考官方help文档地址help(Execute Customizing Project or Project Administration 是指 SPRO)
使用SE16N更改表数据时会记录更改日志。
日志数据记录表 SE16N_CD_KEY、SE16N_CD_DATA
使用SCDO文档更改对象记录数据变更在标准的业务功能中比较常见,比如采购订单等,文档更改对象可以关联工作流事件的触发。
这里简单介绍自定义文档更改对象的创建及使用。
创建自定义表,并勾选字段数据元素下的change document复选框
进入SCDO,输入对象名,点击新建
输入表名,并点击生成(表名后的选项控制更新函数的生成,int.Table控制更新函数数据更新时是单条还是多条)
选择yes
填入Function group,如下图,Generate DATA for ABAP OO会影响生成的更新程序变量(OO中不允许*开头的变量)
Function group 可以自动生成,不用单独创建
生成的更新程序信息,include program 可以直接使用,已经包含了变量及function调用
生成的include程序如下
使用时可以include生成的include程序,调用更新form,或者直接调用更新function,此处以在SM30中添加日志记录为例
添加数据保存之前和之后事件对应的Routines
FORM 代码如下,该代码为通用代码,需要注意更新函数生成时的参数选择可能导致参数名不一样,需要对应调整代码。参考博客Change log for Z-Table Maintenance (via SCDO) | Zafer Onbaş
FORM before_save.
TYPES: BEGIN OF ty_tcdrp,
object TYPE cdobjectcl,
reportname TYPE cdreport,
END OF ty_tcdrp,
BEGIN OF ty_view_tab,
object TYPE cdobjectcl,
tabname TYPE cdtabname,
END OF ty_view_tab.
DATA: lt_ptab TYPE STANDARD TABLE OF string,
lv_prog TYPE string,
lv_mess TYPE string,
lv_sid TYPE string,
lt_obj TYPE STANDARD TABLE OF ty_view_tab,
lt_tcdrp TYPE STANDARD TABLE OF ty_tcdrp,
lv_fugn TYPE funct_pool,
lv_table TYPE cdtabname,
lv_namesfunc TYPE namespace,
lv_funcgroup TYPE progname,
lv_namesprog TYPE namespace,
lv_program TYPE progname,
lrt_tabname TYPE RANGE OF tabname,
lt_dd26v TYPE TABLE OF dd26v,
lv_object TYPE cdobjectcl.
" Get tabnames
" DD: Interface for reading a view from the ABAP/4 Dictionary
CALL FUNCTION 'DDIF_VIEW_GET'
EXPORTING
name = vim_view_name
TABLES
dd26v_tab = lt_dd26v
EXCEPTIONS
illegal_input = 1
OTHERS = 2.
IF sy-subrc IS NOT INITIAL.
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
ENDIF.
IF lt_dd26v IS INITIAL.
APPEND INITIAL LINE TO lrt_tabname ASSIGNING FIELD-SYMBOL().
-sign = 'I'.
-option = 'EQ'.
-low = vim_view_name.
ELSE.
SORT lt_dd26v BY tabname.
DELETE ADJACENT DUPLICATES FROM lt_dd26v COMPARING tabname.
"*-
LOOP AT lt_dd26v INTO DATA(ls_dd26v).
APPEND INITIAL LINE TO lrt_tabname ASSIGNING .
-sign = 'I'.
-option = 'EQ'.
-low = ls_dd26v-tabname.
ENDLOOP.
ENDIF.
" Objects for change document creation
SELECT object tabname
FROM tcdob
INTO TABLE lt_obj
WHERE tabname IN lrt_tabname
##WARN_OK.
IF sy-subrc IS NOT INITIAL.
" No change document objects found
MESSAGE i899(cd).
RETURN.
ENDIF.
" Information on Include Reports Generated by RSSCD000
SELECT object reportname
FROM tcdrp
INTO TABLE lt_tcdrp
FOR ALL ENTRIES IN lt_obj
WHERE object EQ lt_obj-object
##WARN_OK.
IF sy-subrc IS NOT INITIAL.
" Update program does not yet exist
MESSAGE i446(m2).
RETURN.
ENDIF.
" View Directory
SELECT SINGLE area
FROM tvdir
INTO lv_fugn
WHERE tabname EQ vim_view_name.
"*-
LOOP AT lt_obj ASSIGNING FIELD-SYMBOL().
READ TABLE lt_tcdrp ASSIGNING FIELD-SYMBOL()
WITH KEY object = -object.
IF sy-subrc IS NOT INITIAL.
CONTINUE.
ENDIF.
" Split namespace
CLEAR: lv_namesprog, lv_program.
lv_program = -reportname.
CALL FUNCTION 'RS_NAME_SPLIT_NAMESPACE'
EXPORTING
name_with_namespace = lv_program
IMPORTING
namespace = lv_namesprog
name_without_namespace = lv_program
EXCEPTIONS
delimiter_error = 1
OTHERS = 2.
IF sy-subrc <> 0.
lv_program = -reportname.
ENDIF.
CLEAR: lv_namesfunc, lv_funcgroup.
lv_funcgroup = lv_fugn.
CALL FUNCTION 'RS_NAME_SPLIT_NAMESPACE'
EXPORTING
name_with_namespace = lv_funcgroup
IMPORTING
namespace = lv_namesfunc
name_without_namespace = lv_funcgroup
EXCEPTIONS
delimiter_error = 1
OTHERS = 2.
IF sy-subrc <> 0.
lv_funcgroup = lv_fugn.
ENDIF.
" Namespace conversion
lv_object = -object.
IF lv_object(1) = '/'.
SHIFT lv_object LEFT DELETING LEADING '/'.
REPLACE FIRST OCCURRENCE OF '/' IN lv_object WITH '_'.
ENDIF.
lv_table = -tabname.
IF lv_table(1) = '/'.
SHIFT lv_table LEFT DELETING LEADING '/'.
REPLACE FIRST OCCURRENCE OF '/' IN lv_table WITH '_'.
ENDIF.
" Subroutine pool
APPEND ##NO_TEXT:
`PROGRAM SUBPOOL.` TO lt_ptab,
` INCLUDE ` && lv_namesprog && `F` && lv_program && `CDT.` TO lt_ptab,
` INCLUDE ` && lv_namesprog && `F` && lv_program && `CDC.` TO lt_ptab,
` FORM f_process.` TO lt_ptab,
` TYPES: BEGIN OF total.` TO lt_ptab,
` INCLUDE STRUCTURE ` && vim_view_name && `.` TO lt_ptab,
` INCLUDE STRUCTURE vimflagtab.` TO lt_ptab,
` TYPES: END OF total.` TO lt_ptab,
` FIELD-SYMBOLS: TYPE ANY TABLE,` TO lt_ptab,
` TYPE total,` TO lt_ptab,
` TYPE ANY TABLE,` TO lt_ptab,
` TYPE vimnamtab,` TO lt_ptab,
` TYPE any.` TO lt_ptab,
` DATA: lv_tabname(40) TYPE c VALUE '(` && lv_namesfunc && `SAPL` && lv_funcgroup && `)TOTAL[]',` TO lt_ptab,
` lv_cond_line TYPE string,` TO lt_ptab,
` lv_cond_line2 TYPE string.` TO lt_ptab,
` ASSIGN (lv_tabname) TO .` TO lt_ptab,
` LOOP AT ASSIGNING CASTING.` TO lt_ptab,
` CASE -action.` TO lt_ptab,
` WHEN 'U'. " Update` TO lt_ptab,
` lv_tabname = '(` && lv_namesfunc && `SAPL` && lv_funcgroup && `)X_NAMTAB[]'.` TO lt_ptab,
` ASSIGN (lv_tabname) TO .` TO lt_ptab,
` lv_cond_line2 = |keyflag EQ 'X' AND viewfield NE 'MANDT'|.` TO lt_ptab,
` LOOP AT ASSIGNING WHERE (lv_cond_line2).` TO lt_ptab,
` ASSIGN COMPONENT -viewfield OF STRUCTURE TO .` TO lt_ptab,
` IF sy-subrc IS INITIAL.` TO lt_ptab,
` lv_cond_line = lv_cond_line && |AND | && ` TO lt_ptab,
` -viewfield && | EQ '| && && |' |.` TO lt_ptab,
` objectid = objectid && .` TO lt_ptab,
` UNASSIGN .` TO lt_ptab,
` ENDIF.` TO lt_ptab,
` ENDLOOP.` TO lt_ptab,
` IF sy-subrc IS INITIAL.` TO lt_ptab,
` SHIFT lv_cond_line LEFT BY 3 PLACES.` TO lt_ptab,
` SELECT SINGLE *` TO lt_ptab,
` FROM ` && -tabname TO lt_ptab,
` INTO *` && -tabname TO lt_ptab,
` WHERE (lv_cond_line).` TO lt_ptab,
` MOVE-CORRESPONDING TO ` && -tabname && `.` TO lt_ptab,
` objectid = objectid.` TO lt_ptab,
` tcode = sy-tcode.` TO lt_ptab,
` udate = sy-datum.` TO lt_ptab,
` utime = sy-uzeit.` TO lt_ptab,
` username = sy-uname.` TO lt_ptab,
` cdoc_upd_object = 'U'.` TO lt_ptab,
` upd_` && lv_table && ` = 'U'.` TO lt_ptab,
` PERFORM cd_call_` && lv_object && `.` TO lt_ptab,
` ENDIF.` TO lt_ptab,
` WHEN 'N'. " New` TO lt_ptab,
` lv_tabname = '(` && lv_namesfunc && `SAPL` && lv_funcgroup && `)X_NAMTAB[]'.` TO lt_ptab,
` ASSIGN (lv_tabname) TO .` TO lt_ptab,
` lv_cond_line2 = |keyflag EQ 'X' AND viewfield NE 'MANDT'|.` TO lt_ptab,
` LOOP AT ASSIGNING WHERE (lv_cond_line2).` TO lt_ptab,
` ASSIGN COMPONENT -viewfield OF STRUCTURE TO .` TO lt_ptab,
` IF sy-subrc IS INITIAL.` TO lt_ptab,
` objectid = objectid && .` TO lt_ptab,
` UNASSIGN .` TO lt_ptab,
` ENDIF.` TO lt_ptab,
` ENDLOOP.` TO lt_ptab,
` IF sy-subrc IS INITIAL.` TO lt_ptab,
` MOVE-CORRESPONDING TO ` && -tabname && `.` TO lt_ptab,
` objectid = objectid.` TO lt_ptab,
` tcode = sy-tcode.` TO lt_ptab,
` udate = sy-datum.` TO lt_ptab,
` utime = sy-uzeit.` TO lt_ptab,
` username = sy-uname.` TO lt_ptab,
` cdoc_upd_object = 'I'.` TO lt_ptab,
` upd_` && lv_table && ` = 'I'.` TO lt_ptab,
` PERFORM cd_call_` && lv_object && `.` TO lt_ptab,
` ENDIF.` TO lt_ptab,
` WHEN 'D'. " Delete` TO lt_ptab,
` lv_tabname = '(` && lv_namesfunc && `SAPL` && lv_funcgroup && `)X_NAMTAB[]'.` TO lt_ptab,
` ASSIGN (lv_tabname) TO .` TO lt_ptab,
` lv_cond_line2 = |keyflag EQ 'X' AND viewfield NE 'MANDT'|.` TO lt_ptab,
` LOOP AT ASSIGNING WHERE (lv_cond_line2).` TO lt_ptab,
` ASSIGN COMPONENT -viewfield OF STRUCTURE TO .` TO lt_ptab,
` IF sy-subrc IS INITIAL.` TO lt_ptab,
` objectid = objectid && .` TO lt_ptab,
` UNASSIGN .` TO lt_ptab,
` ENDIF.` TO lt_ptab,
` ENDLOOP.` TO lt_ptab,
` IF sy-subrc IS INITIAL.` TO lt_ptab,
` MOVE-CORRESPONDING TO *` && -tabname && `.` TO lt_ptab,
` objectid = objectid.` TO lt_ptab,
` tcode = sy-tcode.` TO lt_ptab,
` udate = sy-datum.` TO lt_ptab,
` utime = sy-uzeit.` TO lt_ptab,
` username = sy-uname.` TO lt_ptab,
` cdoc_upd_object = 'D'.` TO lt_ptab,
` upd_` && lv_table && ` = 'D'.` TO lt_ptab,
` PERFORM cd_call_` && lv_object && `.` TO lt_ptab,
` ENDIF.` TO lt_ptab,
` ENDCASE.` TO lt_ptab,
` CLEAR: lv_cond_line, lv_cond_line2, objectid, ` && -tabname && `, *` && -tabname && `.` TO lt_ptab,
` ENDLOOP.` TO lt_ptab,
` ENDFORM.` TO lt_ptab.
"*-
GENERATE SUBROUTINE POOL lt_ptab NAME lv_prog
MESSAGE lv_mess
SHORTDUMP-ID lv_sid.
IF sy-subrc = 0.
PERFORM ('F_PROCESS') IN PROGRAM (lv_prog) IF FOUND.
ELSEIF sy-subrc = 4.
MESSAGE lv_mess TYPE 'I'.
ELSEIF sy-subrc = 8.
MESSAGE lv_sid TYPE 'I'.
ENDIF.
CLEAR: lt_ptab.
ENDLOOP.
ENDFORM.
FORM AFTER_SAVE.
COMMIT WORK AND WAIT.
ENDFORM.
使用程序CHANGEDOCU_READ 查看更改日志
日志数据记录表 CDHDR、CDPOS
注意:如果数据元素不勾选change document,则该字段数据变更不会记录
实际上自定义SCDO文档更改对象也属于自定义日志,只是结合了标准的日志记录功能,当然也可以结合SLG1标准日志来记录表数据更改。
而完全自定义的更改日志记录,一般为了方便,只是简单使用自建表记录表数据的更改记录,搭建一个具有普适性的更改日志功能,还不如使用标准日志记录更方便。
如果需求比较特殊,需要搭建完全自定义的日志功能,参考标准日志也是一种选择。