SAP 분류 시스템(Classification) 을 ABAP 으로 다루다 보면 "자재 클래스가 뭐 뭐 있지", "이 자재에 어떤 클래스가 할당돼 있지", "이 클래스에는 어떤 특성이 있지" 같은 질문이 끊임없이 나옵니다. 매번 표준 BAPI 호출만으로는 답이 안 나오는 케이스가 많아 결국 표준 테이블 조인이 필요해집니다.
분류 시스템 관련 표준 테이블은 클래스 헤더 · 클래스↔특성 매핑 · 객체↔클래스 매핑 · 특성 마스터 · 특성값 으로 역할이 나뉘어 있습니다. 처음 마주하면 테이블 7~8 개가 흩어져 있어 복잡해 보이지만, 구조를 한 번 잡아두면 어떤 분류 관련 요구사항이 와도 같은 패턴으로 해결할 수 있습니다.
이 글은 자재 클래스(KLART = '001') 를 중심으로 표준 분류 테이블 8 종의 역할과 조인 키, 실무 조인 예시를 정리한 메모입니다. 같이 보면 좋은 글은 "BATCH 자재 특성값 조회 — AUSP·CABN·MCH1 조인" 과 "내부 특성(ATINN) 변환 — CONVERSION_EXIT_ATINN_INPUT/OUTPUT 사용법" 입니다.
핵심 — 분류 시스템 4계층 구조
표준 분류 시스템 테이블은 다음 4개 계층으로 묶을 수 있습니다. 각 계층마다 마스터 테이블과 텍스트(다국어) 테이블이 짝지어져 있는 경우가 많습니다.
| 계층 | 역할 | 대표 테이블 |
|---|---|---|
| 1. 클래스 유형 | 자재·배치·장비·고객 등 분류 가능한 객체 유형 정의 | TCLA · TCLAT |
| 2. 클래스 마스터 | 클래스 정의 (예: STEEL_RAW, PACKAGING) |
KLAH · KLAT |
| 3. 매핑 (관계) | "이 객체는 어느 클래스에" · "이 클래스에는 어느 특성이" | KSSK · KSML |
| 4. 특성·값 | 특성 정의 + 허용값 + 객체별 실제 값 | CABN · CABNT · CAWN · CAWNT · AUSP |
조인 핵심 키는 두 가지입니다.
| 키 | 역할 |
|---|---|
CLINT (NUMC 10) |
내부 클래스 번호. 사용자가 보는 클래스명(CLASS) 대신 시스템 내부 키로 사용 — KLAH·KSSK·KSML 조인 키 |
KLART (CHAR 3) |
클래스 유형. 001=자재, 002=장비, 017=고객, 023=배치 등 |
이 글에서는 가장 자주 쓰는 자재 클래스(KLART = '001') 기준으로 설명합니다. 다른 클래스 유형(배치·장비 등) 도 동일한 테이블 구조를 그대로 따릅니다.
1단계 — 클래스 헤더 (KLAH · KLAT · TCLA)
클래스 자체의 정의가 들어있는 테이블입니다. SAP CL01(클래스 생성) 트랜잭션에서 입력한 정보가 여기 쌓입니다.
| 테이블 | 설명 | 주요 필드 |
|---|---|---|
TCLA |
클래스 유형 코드 마스터 | KLART(001/002/023/...) |
KLAH |
클래스 헤더 — 클래스 정의 본체 | CLINT(내부키), CLASS(클래스명), KLART(유형), STATU(상태) |
KLAT |
클래스 설명 텍스트 (다국어) | CLINT, SPRAS(언어), KLAFT(설명) |
핵심 포인트: 사용자는 CLASS(클래스명, 영문 30자) 로 인지하지만, 다른 테이블과의 조인은 무조건 CLINT(NUMC 10) 로 합니다. 클래스명으로 시작했다면 KLAH 에서 먼저 CLINT 를 얻은 뒤 다른 테이블로 넘어갑니다.
" 클래스명 → 내부 키 변환
SELECT SINGLE clint FROM klah
INTO @DATA(lv_clint)
WHERE class = 'STEEL_RAW'
AND klart = '001'. " 자재 클래스
2단계 — 클래스 ↔ 특성 매핑 (KSML)
한 클래스에 어떤 특성(CABN) 들이 속해 있는지 알려주는 매핑 테이블입니다.
| 테이블 | 설명 | 주요 필드 |
|---|---|---|
KSML |
클래스에 할당된 특성 목록 | CLINT(클래스 내부키), IMERK(내부 특성번호=ATINN), POSNR(순서) |
핵심 필드 IMERK 가 곧 CABN-ATINN 과 같은 내부 특성 번호입니다. 이름은 다르지만 값은 같은 NUMC 10 키입니다.
" 클래스의 모든 특성 조회
SELECT ksml~imerk, cabn~atnam, cabnt~atbez
FROM ksml
INNER JOIN cabn ON cabn~atinn = ksml~imerk
LEFT JOIN cabnt ON cabnt~atinn = cabn~atinn
AND cabnt~adzhl = cabn~adzhl
AND cabnt~spras = @sy-langu
INTO TABLE @DATA(lt_class_chars)
WHERE ksml~clint = @lv_clint.
3단계 — 객체 ↔ 클래스 매핑 (KSSK · INOB)
"이 자재가 어느 클래스에 속해있나" 를 알려주는 매핑입니다. CL20N(객체 분류) 트랜잭션에서 자재에 클래스를 할당하면 여기 쌓입니다.
| 테이블 | 설명 | 주요 필드 |
|---|---|---|
KSSK |
객체에 할당된 클래스 목록 (자재 분류의 메인 테이블) | OBJEK(객체키=자재번호), CLINT(클래스), KLART(유형) |
INOB |
객체 → 내부 분류 번호 매핑 (배치 등 일부 객체 유형에서 사용) | CUOBJ(내부 객체번호), OBTAB(테이블명), OBJEK(객체키) |
자재 분류(KLART='001') 의 경우 OBJEK 에 자재번호(MATNR) 가 들어갑니다. INOB 는 배치 같은 일부 유형에서만 필요하며 자재 분류는 보통 KSSK 만으로 충분합니다.
" 자재가 속한 모든 클래스 조회
SELECT kssk~clint, klah~class, klat~klaft
FROM kssk
INNER JOIN klah ON klah~clint = kssk~clint
LEFT JOIN klat ON klat~clint = klah~clint
AND klat~spras = @sy-langu
INTO TABLE @DATA(lt_obj_class)
WHERE kssk~objek = @lv_matnr
AND kssk~klart = '001'
AND kssk~mafid = 'O' " O = Object, K = Class
AND kssk~stdcl = 'X'. " 표준 클래스 (선택)
4단계 — 특성·값 (CABN · CABNT · CAWN · AUSP)
특성 정의와 객체별 실제 값이 들어있는 계층입니다. 별도 글 "BATCH 자재 특성값 조회 — AUSP·CABN·MCH1 조인" 에서 자세히 다룬 부분과 동일하지만, 자재 분류 관점에서 다시 정리합니다.
| 테이블 | 설명 | 주요 필드 |
|---|---|---|
CABN |
특성 마스터 (정의) | ATINN(내부키), ATNAM(특성명), ATFOR(포맷) |
CABNT |
특성 설명 텍스트 (다국어) | ATINN, SPRAS, ATBEZ(설명) |
CAWN |
특성별 허용값 목록 (드롭다운 후보) | ATINN, ATWRT(값), ATZHL(카운터) |
CAWNT |
허용값 텍스트 (다국어) | ATINN, ATWRT, SPRAS, ATWTB(텍스트) |
AUSP |
객체별 실제 특성값 (분류 데이터의 본체) | OBJEK(객체), ATINN, KLART, ATWRT(값), ATFLV(숫자) |
특성값을 다룰 때는 CABN-ATFOR 포맷에 따라 AUSP-ATWRT(문자) 와 AUSP-ATFLV(숫자) 중 어느 컬럼에 값이 들어있는지 분기해야 합니다(068 글 참고).
5단계 — 전체 조인 예시 (자재 → 클래스 → 특성 → 값)
각 계층을 한 번에 조인해 자재의 모든 분류 정보를 끌어오는 SQL 예시입니다.
SELECT
kssk~objek AS matnr, " 자재
klah~class AS class_name, " 클래스명
klat~klaft AS class_desc, " 클래스 설명
cabn~atnam AS char_name, " 특성명
cabnt~atbez AS char_desc, " 특성 설명
ausp~atwrt AS char_value, " 특성 값 (문자)
ausp~atflv AS char_value_n " 특성 값 (숫자)
FROM kssk
INNER JOIN klah ON klah~clint = kssk~clint
LEFT JOIN klat ON klat~clint = klah~clint
AND klat~spras = @sy-langu
INNER JOIN ksml ON ksml~clint = klah~clint
INNER JOIN cabn ON cabn~atinn = ksml~imerk
LEFT JOIN cabnt ON cabnt~atinn = cabn~atinn
AND cabnt~adzhl = cabn~adzhl
AND cabnt~spras = @sy-langu
LEFT JOIN ausp ON ausp~objek = kssk~objek
AND ausp~atinn = cabn~atinn
AND ausp~klart = '001'
AND ausp~lkenz = ' '
INTO TABLE @DATA(lt_full)
WHERE kssk~objek = @lv_matnr
AND kssk~klart = '001'
AND kssk~mafid = 'O'.
이 한 SQL 로 자재의 모든 클래스, 각 클래스의 모든 특성, 그리고 그 특성의 실제 값까지 한 번에 끌어옵니다. 표준 BAPI(BAPI_OBJCL_GETDETAIL) 보다 빠르지만, 변환 로직(숫자 포맷·단위) 은 직접 처리해야 합니다.
흔히 빠뜨리는 함정
KSSK-MAFID 조건 누락
KSSK 는 객체↔클래스(MAFID = 'O') 와 클래스↔클래스(MAFID = 'K', 클래스 계층 구조) 매핑이 같이 들어있습니다. 자재 분류만 원할 때는 MAFID = 'O' 조건이 필수입니다.
CLINT 와 CLASS 헷갈림
CLINT(내부 NUMC 10) 와 CLASS(외부 CHAR 18) 는 다른 값입니다. KSSK·KSML 같은 매핑 테이블에는 CLINT 만 있고 CLASS 명은 없으므로, 사용자 입력 클래스명으로 시작했다면 KLAH 에서 변환부터 합니다.
IMERK vs ATINN
KSML-IMERK 와 CABN-ATINN 은 같은 NUMC 10 값을 담지만 컬럼 이름이 다릅니다. 조인 시 두 컬럼이 같다는 것을 알고 있어야 합니다(cabn).atinn = ksmlimerk
다국어 텍스트 LEFT JOIN
KLAT·CABNT·CAWNT 는 언어별 텍스트 테이블이라 해당 언어로 등록되지 않았으면 비어있을 수 있습니다. INNER JOIN 으로 묶으면 텍스트 없는 행이 통째로 빠지므로 LEFT JOIN 을 사용합니다.
CABN-ADZHL 누락 → 같은 특성 중복
CABN 에는 같은 ATINN 에 여러 ADZHL(버전 카운터) 가 존재할 수 있습니다. 텍스트 테이블 조인 시 CABN 조건을 빼면 중복 행이 발생합니다.ADZHL = CABNTADZHL
AUSP-LKENZ 삭제표시 필터링
AUSP-LKENZ = 'X' 인 행은 삭제 표시된 특성값입니다. 화면에서는 자동 제외되지만 SQL 조회는 그대로 가져오므로 AND lkenz <> 'X' 또는 AND lkenz = ' ' 조건이 필요합니다.
STDCL(표준 클래스) 사용 의도 확인
KSSK-STDCL = 'X' 는 자재의 "표준 클래스" 표시입니다. 한 자재가 여러 클래스에 속하는데 그중 대표 1개만 표시할 때 사용됩니다. 전체 클래스가 필요한지 표준 1개만 필요한지에 따라 조건을 다르게 설정합니다.
자재 분류 OBJEK 가 항상 MATNR 인 것은 아님
KSSK-OBJEK 는 CHAR 50 의 일반화된 객체 키입니다. 자재 분류(KLART='001') 에서는 자재번호가 그대로 들어가지만, 자재번호가 alpha-converted 되는 경우(앞 0 제거) 직접 SQL 비교 시 매칭 안 될 수 있습니다. 변수 사용 시 alpha conversion 주의(071 글 참고).
전체 코드 — 복사용 통합본
자재번호 입력 → 그 자재의 모든 클래스·특성·특성값을 한꺼번에 출력하는 분류 정보 통합 조회 프로그램입니다. SE38 에 그대로 붙여 실행 가능합니다.
REPORT z_material_class_query.
PARAMETERS: p_matnr TYPE matnr OBLIGATORY.
TYPES: BEGIN OF ty_full,
matnr TYPE matnr,
class_name TYPE klah-class,
class_desc TYPE klat-klaft,
char_name TYPE cabn-atnam,
char_desc TYPE cabnt-atbez,
char_value_c TYPE ausp-atwrt,
char_value_n TYPE ausp-atflv,
char_format TYPE cabn-atfor,
END OF ty_full.
DATA: lt_full TYPE STANDARD TABLE OF ty_full,
lv_obj TYPE ausp-objek.
* ★ 1) Alpha conversion — 자재번호를 OBJEK 형태로 (선행 0 제거 필요시)
lv_obj = |{ p_matnr ALPHA = OUT }|.
* ★ 2) 자재 → 클래스 → 특성 → 값 한 번에 조인
SELECT
kssk~objek AS matnr,
klah~class AS class_name,
klat~klaft AS class_desc,
cabn~atnam AS char_name,
cabnt~atbez AS char_desc,
ausp~atwrt AS char_value_c,
ausp~atflv AS char_value_n,
cabn~atfor AS char_format
FROM kssk
INNER JOIN klah ON klah~clint = kssk~clint
LEFT JOIN klat ON klat~clint = klah~clint
AND klat~spras = @sy-langu
INNER JOIN ksml ON ksml~clint = klah~clint
INNER JOIN cabn ON cabn~atinn = ksml~imerk
LEFT JOIN cabnt ON cabnt~atinn = cabn~atinn
AND cabnt~adzhl = cabn~adzhl
AND cabnt~spras = @sy-langu
LEFT JOIN ausp ON ausp~objek = kssk~objek
AND ausp~atinn = cabn~atinn
AND ausp~klart = '001'
AND ausp~lkenz = ' '
INTO TABLE @lt_full
WHERE kssk~objek = @p_matnr " ※ alpha 변환 필요시 lv_obj 사용
AND kssk~klart = '001' " 자재 분류
AND kssk~mafid = 'O'. " Object (Class 가 아님)
IF sy-subrc <> 0.
WRITE: / '해당 자재에 할당된 클래스가 없습니다:', p_matnr.
RETURN.
ENDIF.
* ★ 3) 결과 출력 — 포맷별 분기
WRITE: / 'CLASS | CHAR | VALUE'.
ULINE.
LOOP AT lt_full ASSIGNING FIELD-SYMBOL(<ls>).
CASE <ls>-char_format.
WHEN 'CHAR' OR 'DATE' OR 'TIME'.
WRITE: / <ls>-class_name, <ls>-char_name, <ls>-char_value_c.
WHEN 'NUM' OR 'CURR'.
WRITE: / <ls>-class_name, <ls>-char_name, <ls>-char_value_n.
WHEN OTHERS.
WRITE: / <ls>-class_name, <ls>-char_name,
<ls>-char_value_c, <ls>-char_value_n.
ENDCASE.
ENDLOOP.
요약
| 계층 | 테이블 | 조인 키 |
|---|---|---|
| 1 | TCLA · KLAH · KLAT |
CLINT · KLART |
| 2 | KSML (클래스 ↔ 특성) |
CLINT · IMERK(=ATINN) |
| 3 | KSSK · INOB (객체 ↔ 클래스) |
OBJEK(=MATNR) · CLINT · MAFID='O' |
| 4 | CABN · CABNT · CAWN · AUSP |
ATINN · ADZHL · OBJEK |
| 5 | 대안 | 표준 BAPI BAPI_OBJCL_GETDETAIL — 단건 상세 조회 시 권장 |
자재 분류 시스템은 클래스 헤더(KLAH) → 클래스-특성 매핑(KSML) → 객체-클래스 매핑(KSSK) → 특성·값(CABN·AUSP) 4계층으로 구성됩니다. 키 두 개(CLINT · KLART) 만 잡으면 어떤 자재든 분류 정보를 한 SQL 로 끌어올 수 있습니다. 다국어 텍스트는 LEFT JOIN, 삭제표시는 LKENZ 필터, 객체 키는 alpha conversion 만 주의하면 안정적인 결과를 얻을 수 있습니다. 단건 조회나 자동 포맷팅이 필요하면 직접 SQL 대신 표준 BAPI BAPI_OBJCL_GETDETAIL 을 권장합니다.
Disclaimer — 이 포스트는 실무 정리 노트를 바탕으로 AI 보조로 정리되었습니다.
KLAH · KLAT · KSSK · KSML · CABN · CABNT · CAWN · CAWNT · AUSP · INOB · TCLA 표준 분류 시스템 테이블과 BAPI_OBJCL_GETDETAIL 표준 BAPI 는 SAP NetWeaver 표준 기능으로 시스템 버전 의존 없이 동작합니다. 자재 분류는 KLART = '001', 배치 분류는 '023', 장비는 '002' 등 클래스 유형이 명확히 구분되어 있으니 잘못된 유형 코드 사용 시 조회 결과가 비거나 다른 객체 데이터가 섞일 수 있습니다. 회사별 커스터마이징으로 추가 KLART 가 정의되어 있을 수 있어 전체 목록은 TCLA 테이블에서 사전에 확인하시기 바랍니다. KSSK-OBJEK 와 자재번호의 alpha conversion 차이는 변수로 WHERE 절을 구성할 때 매칭 실패의 흔한 원인이므로, 변수 사용 시 |{ var ALPHA = OUT }| 처리를 권장합니다.