SAP에서 자체 개발한 CBO 테이블을 운영하다 보면, 결재함이나 주문서 같은 화면에서 사용자가 "이 데이터를 누가 언제 어떻게 변경했는지" 확인하고 싶어 하는 경우가 자주 발생합니다.
표준 트랜잭션(MM01·VA02·ME22N 등) 은 변경이력을 자동으로 CDHDR·CDPOS 에 저장하지만, 이는 SAP가 표준 프로그램 안에 변경 기록 로직을 이미 박아두었기 때문입니다. Z 테이블과 Z 프로그램은 동일한 기능을 쓰려면 데이터 element 설정 · SCDO 객체 정의 · Function Module 호출 까지 직접 구현해야 합니다.
이 글은 CBO 테이블 변경이력을 표준 CDHDR·CDPOS 에 적재하고 사용자 화면에서 조회할 수 있게 만드는 5단계 구현 절차와, 자주 혼동되는 Log Data Changes 옵션과의 차이를 정리한 메모입니다.
핵심 — DBTABLOG 와 CDHDR / CDPOS 는 서로 다른 메커니즘입니다
두 방식이 이름도 비슷하고 결과(변경이력 확인) 도 비슷해서 자주 혼동되지만, 트리거·저장 위치·조회 방법·구현 난이도가 모두 다릅니다.
| 항목 | DBTABLOG | CDHDR / CDPOS |
|---|---|---|
| 트리거 | SE13 의 Log Data Changes 체크 + 시스템 파라미터 rec/client 활성화 |
Data Element 의 Change Document 체크 + SCDO 객체 정의 + 프로그램에서 FM 호출 |
| 저장 테이블 | DBTABLOG |
CDHDR(헤더) · CDPOS(필드별 전/후) |
| 조회 방법 | SCU3 트랜잭션 (관리자 권한) |
표준 TX 의 Environment → Changes 메뉴 · RSSCD100 프로그램 |
| 묶음 단위 | 테이블 row 단위 | 업무 객체(예: 결재문서 1건) 단위로 묶임 |
| 구현 난이도 | 체크박스 한 번 — 코딩 불필요 | 3단계 구현 — ABAP 코딩 필요 |
| 일반 사용자 노출 | 불가 (관리자용 감사 로그) | 가능 (업무 화면에서 직접 조회) |
Log Data Changes 체크는 CDHDR·CDPOS 에 자동 적재해 주지 않으며, 반대로 SCDO 객체 정의가 DBTABLOG 에 기록을 남기지도 않습니다. 한쪽을 켰다고 다른 한쪽이 같이 작동하는 게 아닙니다. 둘 다 원하면 두 가지를 모두 별도로 설정해야 하며, 실무에서 중요한 CBO 테이블은 감사용(DBTABLOG) + 사용자 노출용(CDHDR) 둘 다 적용하기도 합니다.
이 글은 사용자 화면 노출용인 CDHDR·CDPOS 방식의 구현 절차를 다룹니다.
1단계 — Data Element 에 Change Document 체크 (SE11)
추적할 필드의 데이터 element 에 변경 추적 자격을 먼저 부여합니다. 이 단계 없이 SCDO 객체에 테이블을 등록해도 해당 필드는 CDPOS 에 기록되지 않습니다.
SE11 → Data type → 데이터 element 이름 입력 (예: ZAPPSTA) → Change
→ "Further Characteristics" 탭
→ ☑ Change document ← 체크
→ 저장 + 활성화 (Ctrl + F3)
예시) CBO 테이블 ZTXX0123(결재문서) 기준으로, 결재 흐름에서 변경되는 핵심 필드인 APPSTA(승인 상태) · APPROVER1~APPROVER3(승인자) 등의 데이터 element 를 우선 체크합니다. 사용자가 결재함 화면에서 변경 흐름을 직접 확인해야 하는 필드만 골라 체크하면 됩니다.
2단계 — SCDO 에서 Change Document Object 생성
SCDO 객체는 변경이력을 묶어서 보여주는 업무 단위 정의입니다. "이 결재문서 한 건의 변경이력" 처럼 한 화면에 묶여 보이려면 SCDO 객체가 그 묶음의 식별자가 됩니다.
SCDO → "Change document objects" 입력란에 이름 (예: ZTXX0123) → Create
→ Object description: "결재문서 변경이력"
→ Table 등록: ZTXX0123
☑ Insertion documentation (INSERT 시 기록)
☑ Deletion documentation (DELETE 시 기록)
☑ Single field documentation (필드별 변경 기록)
→ 저장 → Generate update pgm. → 패키지·트랜스포트 입력
→ 자동 생성됨: ZTXX0123_WRITE_DOCUMENT (Function Module)
세 가지 documentation 옵션의 의미는 다음과 같습니다.
| 옵션 | 의미 |
|---|---|
| Insertion documentation | INSERT 시 CDPOS 에 신규 row 기록 |
| Deletion documentation | DELETE 시 CDPOS 에 삭제 row 기록 |
| Single field documentation | UPDATE 시 변경된 필드별로 전 / 후 값 기록 |
세 가지 모두 켜두는 것이 표준입니다. 옵션을 끄면 해당 유형의 변경은 CDPOS 에 기록되지 않습니다.
3단계 — 자동 생성된 Function Module 확인 (SE37)
SCDO 활성화 시 Write Function Module 한 개가 자동 생성됩니다. 호출 인자를 미리 확인해 두면 다음 단계의 프로그램 작성이 편해집니다.
SE37 → Function module: ZTXX0123_WRITE_DOCUMENT → Display
| 인자 | 의미 |
|---|---|
OBJECTID |
변경이력을 묶을 업무 키 (예: 결재문서 번호). CDHDR.OBJECTID 에 그대로 저장됨 |
TCODE |
변경 시점의 트랜잭션 코드 — 보통 sy-tcode 그대로 전달 |
UTIME · UDATE |
변경 시각 — sy-uzeit · sy-datum |
USERNAME |
변경자 — sy-uname |
N_<테이블명> |
변경 후 (NEW) 데이터 — 구조 또는 인터널 테이블 |
O_<테이블명> |
변경 전 (OLD) 데이터 |
UPD_<테이블명> |
작업 구분 — U(Update) · I(Insert) · D(Delete) |
FM 은 내부에서 OLD / NEW 를 자동으로 비교해 차이 나는 필드만 CDPOS 에 기록합니다. 호출자가 "어떤 필드가 바뀌었는지" 일일이 계산할 필요는 없습니다.
4단계 — 프로그램에서 FM 호출
호출 순서는 OPEN → WRITE → CLOSE 세 단계입니다. 표준 트랜잭션이 변경이력을 자동으로 남기는 것처럼 보이는 이유는, SAP 가 표준 프로그램 안에 이 세 호출을 이미 박아두었기 때문입니다. Z 프로그램에서는 동일 흐름을 직접 작성합니다.
DATA: ls_new TYPE ztxx0123,
ls_old TYPE ztxx0123.
* 1) 변경 전 데이터 읽기 (OLD)
SELECT SINGLE * FROM ztxx0123
INTO ls_old
WHERE docid = lv_docid.
* 2) 변경 후 데이터 구성 (NEW)
ls_new = ls_old.
ls_new-appsta = 'A'. " 1차 승인 처리
ls_new-approver1 = sy-uname.
ls_new-apdat1 = sy-datum.
ls_new-aptim1 = sy-uzeit.
* 3) DB UPDATE
UPDATE ztxx0123 FROM ls_new.
* 4) Change Document 기록
CALL FUNCTION 'CHANGEDOCUMENT_OPEN'
EXPORTING
objectclass = 'ZTXX0123'
objectid = lv_docid.
CALL FUNCTION 'ZTXX0123_WRITE_DOCUMENT'
EXPORTING
objectid = lv_docid
tcode = sy-tcode
utime = sy-uzeit
udate = sy-datum
username = sy-uname
n_ztxx0123 = ls_new
o_ztxx0123 = ls_old
upd_ztxx0123 = 'U'.
CALL FUNCTION 'CHANGEDOCUMENT_CLOSE'.
COMMIT WORK.
핵심 포인트:
OBJECTID는 사용자 화면에서 "이 문서의 변경이력" 으로 묶어 볼 단위 키입니다. 결재문서면 결재번호, 주문서면 주문번호처럼 업무 의미 있는 단일 키로 지정합니다.OPEN·WRITE·CLOSE세 호출은 항상 묶음으로 다룹니다.CLOSE누락 시 같은 세션의 다음 변경이 같은 묶음에 잘못 합쳐질 수 있습니다.COMMIT WORK직전까지 기록은 임시 큐에 쌓이며, COMMIT 시점에CDHDR·CDPOS로 일괄 적재됩니다. 데이터 UPDATE 와 같은 트랜잭션 안에서 처리해야 정합성이 보장됩니다.
5단계 — 변경이력 조회 (RSSCD100 또는 자체 화면)
조회 방식은 두 가지입니다. 단발 조회·디버깅이 목적이면 표준 프로그램을 그대로 실행하고, 사용자 업무 화면에 노출하려면 자체 화면에서 표준 FM 을 호출합니다.
방식 A — 표준 프로그램 RSSCD100
SE38 → RSSCD100 → Execute
Object Class : ZTXX0123
Object Value : (결재문서번호)
Date / Time : (기간 지정)
→ Execute
실행하면 "누가 · 언제 · 어떤 필드를 어떤 값에서 어떤 값으로 바꿨는지" 가 한 줄씩 출력됩니다. 일반 사용자에게도 권한 부여 시 조회 가능합니다.
방식 B — 자체 화면에서 변경이력 보기 버튼 구현
자체 결재함이나 주문서 조회 화면에 "변경이력 보기" 버튼을 만들 때 사용합니다. 표준 FM 두 개를 조합합니다.
DATA: lt_cdhdr TYPE STANDARD TABLE OF cdhdr,
lt_cdpos TYPE STANDARD TABLE OF cdpos.
CALL FUNCTION 'CHANGEDOCUMENT_READ_HEADERS'
EXPORTING
objectclass = 'ZTXX0123'
objectid = lv_docid
TABLES
i_cdhdr = lt_cdhdr.
LOOP AT lt_cdhdr INTO DATA(ls_cdhdr).
CALL FUNCTION 'CHANGEDOCUMENT_READ_POSITIONS'
EXPORTING
changenumber = ls_cdhdr-changenr
TABLES
editpos = lt_cdpos.
" 결과를 ALV · 테이블 컨트롤 등에 표시
ENDLOOP.
조회 결과에서 핵심 컬럼은 CDPOS-CHNGIND(변경 유형 표시) 입니다.
| CHNGIND | 의미 |
|---|---|
| U | Update — 필드 값 변경 (전 / 후 값 모두 기록됨) |
| I | Insert — 신규 row 추가 |
| D | Delete — row 삭제 (삭제 직전 값이 기록됨) |
흔히 빠뜨리는 함정
Data Element 만 체크하고 SCDO 등록 안 함
데이터 element 의 Change document 체크는 추적 자격을 부여하는 것일 뿐, 그 자체로 CDPOS 에 기록되지 않습니다. SCDO 객체에 테이블 · 필드 등록까지 마쳐야 합니다.
SCDO 등록만 하고 FM 호출 안 함
SCDO 활성화 후 FM (ZTXX0123_WRITE_DOCUMENT) 이 자동 생성되지만, 프로그램이 그것을 호출하지 않으면 CDHDR 는 한 줄도 쌓이지 않습니다. SAP 가 자동으로 가로채는 메커니즘이 아닙니다.
Log Data Changes 옵션과 혼동
Log Data Changes 체크는 DBTABLOG 에 기록되는 별개 메커니즘으로, SCU3 트랜잭션으로 조회합니다. CDHDR · CDPOS 와 저장 위치 · 조회 방법 · 구현 절차가 모두 다릅니다. 한쪽을 켰다고 다른 한쪽이 같이 작동하지 않으므로, 두 가지 모두 원하면 둘 다 별도로 설정해야 합니다.
필드 추가 후 SCDO 재생성 안 함
CBO 테이블에 필드를 추가한 후 SCDO 에서 Generate update pgm. 을 다시 실행하지 않으면 새 필드는 추적되지 않습니다. 테이블 구조 변경 시 SCDO 재생성을 워크플로우에 포함시켜야 합니다.
CHANGEDOCUMENT_CLOSE 누락
OPEN 만 부르고 CLOSE 안 부르면 같은 세션의 다음 변경 기록이 잘못된 객체에 묶일 수 있습니다. 함수 내부에서 예외 발생 시에도 CLOSE 가 호출되도록 흐름을 구성합니다.
WRITE_DOCUMENT 중간에 COMMIT WORK 끼우기
OPEN ~ CLOSE 사이에 별도 COMMIT WORK 를 끼우면 데이터 변경은 반영됐는데 이력만 빠지는 사태가 발생할 수 있습니다. 호출 측 프로그램이 마지막에 한 번만 COMMIT WORK 를 호출해야 합니다.
OBJECTID 키 누락 또는 임의 값
OBJECTID 를 빈 값으로 호출하면 CDHDR 에 빈 키로 기록되어, 사용자 화면에서 "어떤 문서의 변경인지" 식별이 불가능합니다. 업무 의미 있는 단일 키를 반드시 채워서 전달합니다.
전체 코드 — 복사용 통합본
결재 1차 승인 처리 시점에 변경이력까지 함께 기록하는 표준 흐름입니다. SE38 에 그대로 붙여넣어 실행 가능한 형태로 구성했습니다.
REPORT z_cdhdr_demo.
* ★ 사전 작업
* 1) SE11 → 추적할 필드의 Data Element 에 Change document 체크
* 2) SCDO → ZTXX0123 객체 생성 → 테이블 등록 → Generate update pgm.
* 3) 위 두 단계 완료 후 본 프로그램 실행
PARAMETERS: p_docid TYPE ztxx0123-docid OBLIGATORY.
DATA: ls_new TYPE ztxx0123,
ls_old TYPE ztxx0123.
* 1) 변경 전 데이터 읽기 (OLD)
SELECT SINGLE * FROM ztxx0123
INTO ls_old
WHERE docid = p_docid.
IF sy-subrc <> 0.
WRITE: / '해당 문서가 존재하지 않습니다:', p_docid.
RETURN.
ENDIF.
* 2) 변경 후 데이터 (NEW) — 1차 승인 처리
ls_new = ls_old.
ls_new-appsta = 'A'.
ls_new-approver1 = sy-uname.
ls_new-apdat1 = sy-datum.
ls_new-aptim1 = sy-uzeit.
* 3) DB UPDATE
UPDATE ztxx0123 FROM ls_new.
IF sy-subrc <> 0.
WRITE: / 'UPDATE 실패'.
ROLLBACK WORK.
RETURN.
ENDIF.
* 4) Change Document 기록 — OPEN → WRITE → CLOSE 묶음
CALL FUNCTION 'CHANGEDOCUMENT_OPEN'
EXPORTING
objectclass = 'ZTXX0123'
objectid = p_docid.
CALL FUNCTION 'ZTXX0123_WRITE_DOCUMENT'
EXPORTING
objectid = p_docid
tcode = sy-tcode
utime = sy-uzeit
udate = sy-datum
username = sy-uname
n_ztxx0123 = ls_new
o_ztxx0123 = ls_old
upd_ztxx0123 = 'U'.
CALL FUNCTION 'CHANGEDOCUMENT_CLOSE'.
* 5) ★ 핵심: 한 트랜잭션으로 일괄 COMMIT — 데이터와 이력이 같이 적재됨
COMMIT WORK.
WRITE: / '결재 1차 승인 처리 완료.'.
WRITE: / 'RSSCD100 또는 자체 화면에서 변경이력 조회 가능.'.
변경이력 조회 화면을 자체 프로그램으로 구현하는 예시는 5단계 방식 B 의 코드 블록을 참고합니다.
요약
| 단계 | 작업 | 핵심 |
|---|---|---|
| 1 | SE11 데이터 element | Further Characteristics → Change document 체크 |
| 2 | SCDO 객체 생성 | 테이블 등록 + 세 documentation 옵션 체크 + Generate |
| 3 | SE37 FM 확인 | 자동 생성된 <obj>_WRITE_DOCUMENT 인자 검토 |
| 4 | 프로그램 FM 호출 | OPEN → WRITE_DOCUMENT(OLD · NEW 전달) → CLOSE → COMMIT |
| 5 | 이력 조회 | RSSCD100 또는 자체 화면에서 CHANGEDOCUMENT_READ_HEADERS / POSITIONS 호출 |
CBO 테이블의 변경이력을 사용자 화면에 노출하려면 CDHDR · CDPOS 기반 Change Document 메커니즘을 직접 구현해야 합니다. 데이터 element 자격 부여, SCDO 객체 정의, 자동 생성된 FM 호출까지 세 단계를 모두 마쳐야 작동하며, 그 결과 표준 트랜잭션의 자동 동작과 같은 화면 경험을 사용자에게 제공할 수 있습니다. 비슷한 이름의 Log Data Changes 옵션은 DBTABLOG 기반 별개 메커니즘이므로 혼동하지 않도록 주의합니다.
Disclaimer — 이 포스트는 실무 정리 노트를 바탕으로 AI 보조로 정리되었습니다.
SCDO · SE11 · SE37 · RSSCD100 트랜잭션 · 프로그램과 CHANGEDOCUMENT_OPEN · CHANGEDOCUMENT_CLOSE · CHANGEDOCUMENT_READ_HEADERS · CHANGEDOCUMENT_READ_POSITIONS 함수 모듈은 SAP NetWeaver 표준 기능으로 시스템 버전 의존 없이 동작합니다. CDHDR · CDPOS 는 TCDOB · TCDRP 마스터에 등록된 객체 구조를 따르며, 변경이력 적재는 COMMIT WORK 시점에 일괄 처리되므로 자체 프로그램에서 중간 COMMIT 호출 시 데이터와 이력의 정합성이 깨질 수 있으니 트랜잭션 경계를 신중히 관리하시기 바랍니다.