Change documents automatically log data changes in SAP. They enable complete tracking of changes to business objects for audit and compliance.
Change Document Concepts
| Concept | Description |
|---|---|
| Change document object | Definition of tables to be logged |
| Change document | Individual log entry |
| CDHDR | Header table for change documents |
| CDPOS | Item table (field changes) |
Important Transactions
| Transaction | Description |
|---|---|
| SCDO | Maintain change document objects |
| SCU3 | Display change documents (generic) |
| RSSCD100 | Evaluate 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 datals_order_new = ls_order_old.ls_order_new-status = 'C'. " Changedls_order_new-amount = 1500.
" Object ID for change documentlv_objectid = ls_order_old-order_id.
" Open change documentCALL FUNCTION 'CHANGEDOCUMENT_OPEN' EXPORTING objectclass = 'ZORDER' objectid = lv_objectid planned_change_number = ' ' planned_or_real_changes = 'R'.
" Log changesCALL 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 documentCALL 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 changeCALL 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 orderls_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 insertDATA(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 deleteDATA(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 documentCALL FUNCTION 'CHANGEDOCUMENT_OPEN' EXPORTING objectclass = 'ZORDER' objectid = ls_header_new-order_id.
" Log header dataCALL FUNCTION 'CHANGEDOCUMENT_SINGLE_CASE' EXPORTING tablename = 'ZORDER_HEAD' workarea_old = ls_header_old workarea_new = ls_header_new change_indicator = 'U'.
" Log itemsLOOP 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 itemsLOOP 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 dataSELECT * FROM cdhdr INTO TABLE lt_cdhdr WHERE objectclas = 'ZORDER' AND objectid = '1000000001' ORDER BY udate DESCENDING, utime DESCENDING.
" Read item dataIF 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.
" OutputLOOP 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/DENDCLASS.
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 ALVcl_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 changeCALL 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 SCDO2. Enter object name: ZORDER3. Add tables: - ZORDER_HEAD (header table) - ZORDER_ITEM (item table)4. Mark fields to be logged5. Generate function module6. Activate and transportBatch Change Logging
" For mass changesCALL 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 recordENDLOOP.
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
| Indicator | Description |
|---|---|
| I | Insert (creation) |
| U | Update (change) |
| D | Delete (deletion) |
| E | Single field change |
CDPOS Structure
| Field | Description |
|---|---|
| OBJECTCLAS | Change document object |
| OBJECTID | Object ID |
| CHANGENR | Change number |
| TABNAME | Table name |
| FNAME | Field name |
| CHNGIND | Change indicator |
| VALUE_OLD | Old value |
| VALUE_NEW | New value |
Best Practices
- Relevant fields: Only log business-relevant fields
- Performance: Write change documents in update task
- Object ID: Use unique, meaningful ID
- Archiving: Archive old change documents (SARA)
- Authorizations: Protect read access to change documents
- Compliance: Consider retention period requirements
Related Topics
- Application Logging - Application logging
- Lock Objects - Lock management
- Authorization Checks - Access control
- BAPI Development - Document processing