ABAP Change Documents: Logging Data Changes

Category
ABAP-Statements
Published
Author
Johannes

Change documents automatically log data changes in SAP. They enable complete tracking of changes to business objects for audit and compliance.

Change Document Concepts

ConceptDescription
Change document objectDefinition of tables to be logged
Change documentIndividual log entry
CDHDRHeader table for change documents
CDPOSItem table (field changes)

Important Transactions

TransactionDescription
SCDOMaintain change document objects
SCU3Display change documents (generic)
RSSCD100Evaluate change documents

Basic Examples

Write Change Document

DATA: ls_order_old TYPE zorder,
ls_order_new TYPE zorder,
lv_objectid TYPE cdhdr-objectid.
" Read old data (before change)
SELECT SINGLE * FROM zorder INTO ls_order_old
WHERE order_id = '1000000001'.
" Set new data
ls_order_new = ls_order_old.
ls_order_new-status = 'C'. " Changed
ls_order_new-amount = 1500.
" Object ID for change document
lv_objectid = ls_order_old-order_id.
" Open change document
CALL FUNCTION 'CHANGEDOCUMENT_OPEN'
EXPORTING
objectclass = 'ZORDER'
objectid = lv_objectid
planned_change_number = ' '
planned_or_real_changes = 'R'.
" Log changes
CALL FUNCTION 'CHANGEDOCUMENT_SINGLE_CASE'
EXPORTING
tablename = 'ZORDER'
workarea_old = ls_order_old
workarea_new = ls_order_new
change_indicator = 'U'. " Update
" Close and save change document
CALL FUNCTION 'CHANGEDOCUMENT_CLOSE'
EXPORTING
objectclass = 'ZORDER'
objectid = lv_objectid
date_of_change = sy-datum
time_of_change = sy-uzeit
tcode = sy-tcode
username = sy-uname
EXCEPTIONS
no_position_found = 1
OTHERS = 2.
IF sy-subrc = 0.
" Actually change data
UPDATE zorder FROM ls_order_new.
COMMIT WORK.
ENDIF.

Generated Function Module

" After SCDO definition, FM is generated: ZORDER_WRITE_DOCUMENT
DATA: ls_order_old TYPE zorder,
ls_order_new TYPE zorder,
lt_xorder TYPE TABLE OF zorder,
lt_yorder TYPE TABLE OF zorder.
" Single record change
CALL FUNCTION 'ZORDER_WRITE_DOCUMENT'
EXPORTING
objectid = ls_order_new-order_id
tcode = sy-tcode
utime = sy-uzeit
udate = sy-datum
username = sy-uname
upd_zorder = 'U' " Update
TABLES
xzorder = lt_xorder " New data
yzorder = lt_yorder " Old data
EXCEPTIONS
OTHERS = 1.

Changes for Insert (Creation)

DATA: ls_order_new TYPE zorder.
" Create new order
ls_order_new-order_id = '1000000002'.
ls_order_new-customer_id = 'CUST001'.
ls_order_new-status = 'O'.
ls_order_new-amount = 1000.
" Empty old record signals insert
DATA(ls_order_old) = VALUE zorder( ).
CALL FUNCTION 'CHANGEDOCUMENT_OPEN'
EXPORTING
objectclass = 'ZORDER'
objectid = ls_order_new-order_id.
CALL FUNCTION 'CHANGEDOCUMENT_SINGLE_CASE'
EXPORTING
tablename = 'ZORDER'
workarea_old = ls_order_old
workarea_new = ls_order_new
change_indicator = 'I'. " Insert
CALL FUNCTION 'CHANGEDOCUMENT_CLOSE'
EXPORTING
objectclass = 'ZORDER'
objectid = ls_order_new-order_id
date_of_change = sy-datum
time_of_change = sy-uzeit
tcode = sy-tcode
username = sy-uname.
INSERT zorder FROM ls_order_new.
COMMIT WORK.

Changes for Delete (Deletion)

DATA: ls_order_old TYPE zorder.
SELECT SINGLE * FROM zorder INTO ls_order_old
WHERE order_id = '1000000001'.
" Empty new record signals delete
DATA(ls_order_new) = VALUE zorder( ).
CALL FUNCTION 'CHANGEDOCUMENT_OPEN'
EXPORTING
objectclass = 'ZORDER'
objectid = ls_order_old-order_id.
CALL FUNCTION 'CHANGEDOCUMENT_SINGLE_CASE'
EXPORTING
tablename = 'ZORDER'
workarea_old = ls_order_old
workarea_new = ls_order_new
change_indicator = 'D'. " Delete
CALL FUNCTION 'CHANGEDOCUMENT_CLOSE'
EXPORTING
objectclass = 'ZORDER'
objectid = ls_order_old-order_id
date_of_change = sy-datum
time_of_change = sy-uzeit
tcode = sy-tcode
username = sy-uname.
DELETE FROM zorder WHERE order_id = ls_order_old-order_id.
COMMIT WORK.

Log Multiple Tables

DATA: ls_header_old TYPE zorder_head,
ls_header_new TYPE zorder_head,
lt_items_old TYPE TABLE OF zorder_item,
lt_items_new TYPE TABLE OF zorder_item.
" Open change document
CALL FUNCTION 'CHANGEDOCUMENT_OPEN'
EXPORTING
objectclass = 'ZORDER'
objectid = ls_header_new-order_id.
" Log header data
CALL FUNCTION 'CHANGEDOCUMENT_SINGLE_CASE'
EXPORTING
tablename = 'ZORDER_HEAD'
workarea_old = ls_header_old
workarea_new = ls_header_new
change_indicator = 'U'.
" Log items
LOOP AT lt_items_new INTO DATA(ls_item_new).
READ TABLE lt_items_old INTO DATA(ls_item_old)
WITH KEY item_id = ls_item_new-item_id.
IF sy-subrc = 0.
" Item changed
CALL FUNCTION 'CHANGEDOCUMENT_SINGLE_CASE'
EXPORTING
tablename = 'ZORDER_ITEM'
workarea_old = ls_item_old
workarea_new = ls_item_new
change_indicator = 'U'.
ELSE.
" New item
CALL FUNCTION 'CHANGEDOCUMENT_SINGLE_CASE'
EXPORTING
tablename = 'ZORDER_ITEM'
workarea_old = VALUE zorder_item( )
workarea_new = ls_item_new
change_indicator = 'I'.
ENDIF.
ENDLOOP.
" Deleted items
LOOP AT lt_items_old INTO ls_item_old.
READ TABLE lt_items_new TRANSPORTING NO FIELDS
WITH KEY item_id = ls_item_old-item_id.
IF sy-subrc <> 0.
CALL FUNCTION 'CHANGEDOCUMENT_SINGLE_CASE'
EXPORTING
tablename = 'ZORDER_ITEM'
workarea_old = ls_item_old
workarea_new = VALUE zorder_item( )
change_indicator = 'D'.
ENDIF.
ENDLOOP.
CALL FUNCTION 'CHANGEDOCUMENT_CLOSE'
EXPORTING
objectclass = 'ZORDER'
objectid = ls_header_new-order_id
date_of_change = sy-datum
time_of_change = sy-uzeit
tcode = sy-tcode
username = sy-uname.

Read Change Documents

DATA: lt_cdhdr TYPE TABLE OF cdhdr,
lt_cdpos TYPE TABLE OF cdpos.
" Read header data
SELECT * FROM cdhdr INTO TABLE lt_cdhdr
WHERE objectclas = 'ZORDER'
AND objectid = '1000000001'
ORDER BY udate DESCENDING, utime DESCENDING.
" Read item data
IF lt_cdhdr IS NOT INITIAL.
SELECT * FROM cdpos INTO TABLE lt_cdpos
FOR ALL ENTRIES IN lt_cdhdr
WHERE objectclas = lt_cdhdr-objectclas
AND objectid = lt_cdhdr-objectid
AND changenr = lt_cdhdr-changenr.
ENDIF.
" Output
LOOP AT lt_cdhdr INTO DATA(ls_cdhdr).
WRITE: / 'Changed on:', ls_cdhdr-udate, ls_cdhdr-utime,
'by:', ls_cdhdr-username.
LOOP AT lt_cdpos INTO DATA(ls_cdpos)
WHERE changenr = ls_cdhdr-changenr.
WRITE: / ' Field:', ls_cdpos-fname,
'Old:', ls_cdpos-value_old(20),
'New:', ls_cdpos-value_new(20).
ENDLOOP.
ENDLOOP.

Change Documents with Class

CLASS zcl_change_document DEFINITION.
PUBLIC SECTION.
METHODS log_change
IMPORTING
iv_objectid TYPE cdhdr-objectid
is_old TYPE any
is_new TYPE any
iv_tablename TYPE tabname
iv_operation TYPE cdchngind. " I/U/D
ENDCLASS.
CLASS zcl_change_document IMPLEMENTATION.
METHOD log_change.
CALL FUNCTION 'CHANGEDOCUMENT_OPEN'
EXPORTING
objectclass = 'ZORDER'
objectid = iv_objectid.
CALL FUNCTION 'CHANGEDOCUMENT_SINGLE_CASE'
EXPORTING
tablename = iv_tablename
workarea_old = is_old
workarea_new = is_new
change_indicator = iv_operation.
CALL FUNCTION 'CHANGEDOCUMENT_CLOSE'
EXPORTING
objectclass = 'ZORDER'
objectid = iv_objectid
date_of_change = sy-datum
time_of_change = sy-uzeit
tcode = sy-tcode
username = sy-uname
EXCEPTIONS
no_position_found = 1
OTHERS = 2.
ENDMETHOD.
ENDCLASS.

Display Change Documents via ALV

DATA: lt_display TYPE TABLE OF ty_change_display.
SELECT h~objectid, h~udate, h~utime, h~username, h~tcode,
p~tabname, p~fname, p~chngind,
p~value_old, p~value_new
FROM cdhdr AS h
INNER JOIN cdpos AS p ON h~objectclas = p~objectclas
AND h~objectid = p~objectid
AND h~changenr = p~changenr
WHERE h~objectclas = 'ZORDER'
AND h~udate BETWEEN @lv_from_date AND @lv_to_date
INTO CORRESPONDING FIELDS OF TABLE @lt_display
ORDER BY h~udate DESCENDING, h~utime DESCENDING.
" Display ALV
cl_salv_table=>factory(
IMPORTING r_salv_table = DATA(lo_alv)
CHANGING t_table = lt_display ).
lo_alv->display( ).

Log Text Changes

" For long texts (e.g., notes)
DATA: lv_text_old TYPE string VALUE 'Old text',
lv_text_new TYPE string VALUE 'New longer text'.
CALL FUNCTION 'CHANGEDOCUMENT_OPEN'
EXPORTING
objectclass = 'ZORDER'
objectid = lv_objectid.
" Log text change
CALL FUNCTION 'CHANGEDOCUMENT_TEXT_CASE'
EXPORTING
textcase = 'X'
text_old = lv_text_old
text_new = lv_text_new
tablename = 'ZORDER'
fieldname = 'NOTES'
change_indicator = 'U'.
CALL FUNCTION 'CHANGEDOCUMENT_CLOSE'
EXPORTING
objectclass = 'ZORDER'
objectid = lv_objectid
date_of_change = sy-datum
time_of_change = sy-uzeit
tcode = sy-tcode
username = sy-uname.

Define Change Document Object in SCDO

1. Call transaction SCDO
2. Enter object name: ZORDER
3. Add tables:
- ZORDER_HEAD (header table)
- ZORDER_ITEM (item table)
4. Mark fields to be logged
5. Generate function module
6. Activate and transport

Batch Change Logging

" For mass changes
CALL FUNCTION 'CHANGEDOCUMENT_OPEN'
EXPORTING
objectclass = 'ZORDER'
objectid = '*MASS*'. " Special ID
LOOP AT lt_changes INTO DATA(ls_change).
CALL FUNCTION 'CHANGEDOCUMENT_SINGLE_CASE'
EXPORTING
tablename = 'ZORDER'
workarea_old = ls_change-old_data
workarea_new = ls_change-new_data
change_indicator = 'U'
objectid = ls_change-order_id. " Per record
ENDLOOP.
CALL FUNCTION 'CHANGEDOCUMENT_CLOSE'
EXPORTING
objectclass = 'ZORDER'
objectid = '*MASS*'
date_of_change = sy-datum
time_of_change = sy-uzeit
tcode = sy-tcode
username = sy-uname.

Change Indicators

IndicatorDescription
IInsert (creation)
UUpdate (change)
DDelete (deletion)
ESingle field change

CDPOS Structure

FieldDescription
OBJECTCLASChange document object
OBJECTIDObject ID
CHANGENRChange number
TABNAMETable name
FNAMEField name
CHNGINDChange indicator
VALUE_OLDOld value
VALUE_NEWNew value

Best Practices

  1. Relevant fields: Only log business-relevant fields
  2. Performance: Write change documents in update task
  3. Object ID: Use unique, meaningful ID
  4. Archiving: Archive old change documents (SARA)
  5. Authorizations: Protect read access to change documents
  6. Compliance: Consider retention period requirements