-
Oracle SQL → SAP AMDP 전환 가이드 #1SAP 2026. 5. 10. 21:27
Oracle → SAP AMDP 변환 완전 가이드 Oracle → SAP AMDP 변환 완전 가이드
📋 목차1. Oracle ↔ HANA SQLScript 함수 대응표
Oracle HANA SQLScript 비고 NVL(a, b)COALESCE(a, b)NULL 대체 NVL2(a, b, c)CASE WHEN a IS NOT NULL THEN b ELSE c ENDDECODE(col, v1,r1, v2,r2, def)CASE col WHEN v1 THEN r1 WHEN v2 THEN r2 ELSE def ENDCASE WHEN 방식 DECODE(col, v1,r1, v2,r2, def)MAP(col, v1,r1, v2,r2, def)🆕 MAP 함수 (HANA 전용, 간결) ROWNUM <= nLIMIT n또는ROW_NUMBER() OVER(...)ROWNUM = 1LIMIT 1첫 행만 SYSDATECURRENT_DATE/NOW()TRUNC(date)TO_DATE(date)날짜 절삭 TRUNC(num, n)FLOOR(num * POWER(10,n)) / POWER(10,n)숫자 절삭 TO_CHAR(date, 'YYYYMMDD')TO_VARCHAR(date, 'YYYYMMDD')SUBSTR(str, 1, 5)SUBSTRING(str, 1, 5)INSTR(str, 'x')LOCATE(str, 'x')인자 순서 동일 MONTHS_BETWEEN(d1, d2)MONTHS_BETWEEN(d1, d2)동일 ADD_MONTHS(d, n)ADD_MONTHS(d, n)동일 LPAD(str, n, 'x')LPAD(str, n, 'x')동일 CONNECT BY LEVELSERIES_GENERATE_INTEGER(1, 1, n)계층/순번 MINUSEXCEPT차집합 a (+) = ba LEFT OUTER JOIN b ON ...표준 문법으로 DUALFROM 절 생략 또는 DUMMYSELECT 1 FROM DUMMYLISTAGG(col, ',')STRING_AGG(col, ',')WMSYS.WM_CONCATSTRING_AGG(col, ',')REGEXP_LIKE(col, pat)col LIKE_REGEXPR pat💡 DECODE → MAP: HANA의
MAP()함수는 OracleDECODE와 인자 순서가 동일하여 마이그레이션이 직관적입니다. 단, HANA 전용 함수이므로 표준 SQL 호환이 필요한 경우CASE WHEN을 사용하세요.2. SELECT / JOIN / 서브쿼리 변환
2-1. Oracle Outer Join (+) → 표준 LEFT JOIN
OracleSELECT a.vbeln, b.matnr FROM vbak a, vbap b WHERE a.vbeln = b.vbeln(+) AND a.mandt = '100';AMDP SQLScriptRETURN SELECT a.vbeln, b.matnr FROM vbak AS a LEFT OUTER JOIN vbap AS b ON a.mandt = b.mandt AND a.vbeln = b.vbeln WHERE a.mandt = SESSION_CONTEXT('CLIENT');2-2. ROWNUM → LIMIT / TOP
OracleSELECT * FROM vbak WHERE ROWNUM <= 10 ORDER BY erdat DESC;AMDP SQLScriptRETURN SELECT TOP 10 * FROM vbak WHERE mandt = SESSION_CONTEXT('CLIENT') ORDER BY erdat DESC;2-3. 인라인 뷰(서브쿼리) → CTE (WITH 절) 권장
OracleSELECT x.vbeln, x.total_qty FROM ( SELECT vbeln, SUM(kwmeng) AS total_qty FROM vbap GROUP BY vbeln ) x WHERE x.total_qty > 100;AMDP SQLScriptRETURN WITH order_qty AS ( SELECT vbeln, SUM(kwmeng) AS total_qty FROM vbap WHERE mandt = SESSION_CONTEXT('CLIENT') GROUP BY vbeln ) SELECT vbeln, total_qty FROM order_qty WHERE total_qty > 100;2-4. DECODE → CASE WHEN / MAP
AMDP — MAP 함수 방식 (HANA 전용, 더 간결)OracleSELECT vbeln, DECODE(auart, 'TA', '표준', 'KE', '반품', '기타') AS order_type FROM vbak;AMDP — CASE WHEN 방식RETURN SELECT vbeln, CASE auart WHEN 'TA' THEN '표준' WHEN 'KE' THEN '반품' ELSE '기타' END AS order_type FROM vbak WHERE mandt = SESSION_CONTEXT('CLIENT');RETURN SELECT vbeln, MAP(auart, 'TA', '표준', 'KE', '반품', '기타') AS order_type FROM vbak WHERE mandt = SESSION_CONTEXT('CLIENT');💡 MAP vs CASE WHEN 선택 기준
• 단순 등치 비교 →
MAP(컬럼, 값1, 결과1, 값2, 결과2, ..., 기본값)— Oracle DECODE와 인자 순서 동일, 간결• 범위 조건 / 복합 조건 (
BETWEEN,>,<) →CASE WHEN사용• 표준 SQL 호환 필요 →
CASE WHEN권장 (MAP은 HANA 전용)3. 집계 함수 변환 (GROUP BY / HAVING)
3-1. NVL + 집계 조합
OracleSELECT kunnr, SUM(NVL(netwr, 0)) AS total_sales, COUNT(*) AS order_cnt FROM vbak WHERE mandt = '100' AND erdat BETWEEN '20240101' AND '20241231' GROUP BY kunnr HAVING SUM(NVL(netwr, 0)) > 1000000;AMDP SQLScriptRETURN SELECT kunnr, SUM(COALESCE(netwr, 0)) AS total_sales, COUNT(*) AS order_cnt FROM vbak WHERE mandt = SESSION_CONTEXT('CLIENT') AND erdat BETWEEN :iv_date_from AND :iv_date_to GROUP BY kunnr HAVING SUM(COALESCE(netwr, 0)) > 1000000;3-2. LISTAGG → STRING_AGG
OracleSELECT vbeln, LISTAGG(matnr, ', ') WITHIN GROUP (ORDER BY posnr) AS mat_list FROM vbap GROUP BY vbeln;AMDP SQLScriptRETURN SELECT vbeln, STRING_AGG(matnr, ', ' ORDER BY posnr) AS mat_list FROM vbap WHERE mandt = SESSION_CONTEXT('CLIENT') GROUP BY vbeln;4. Window 함수 변환
💡 HANA SQLScript는 표준 Window 함수를 대부분 지원하며 Oracle과 문법이 거의 동일합니다.
TABLE FUNCTION에서 Window 함수 사용 가능 / PROCEDURE에서는 테이블 변수에 담아 처리합니다.4-1. RANK / DENSE_RANK — 문법 동일
OracleSELECT kunnr, netwr, RANK() OVER ( PARTITION BY vkorg ORDER BY netwr DESC) AS rnk FROM vbak;AMDP SQLScriptRETURN SELECT kunnr, netwr, RANK() OVER ( PARTITION BY vkorg ORDER BY netwr DESC) AS rnk FROM vbak WHERE mandt = SESSION_CONTEXT('CLIENT');4-2. ROW_NUMBER — 중복 제거 / 최신 1건 추출
OracleSELECT * FROM ( SELECT matnr, werks, labst, ROW_NUMBER() OVER ( PARTITION BY matnr ORDER BY budat DESC) AS rn FROM mard ) WHERE rn = 1;AMDP — CTE로 변환RETURN WITH ranked AS ( SELECT matnr, werks, labst, ROW_NUMBER() OVER ( PARTITION BY matnr ORDER BY budat DESC) AS rn FROM mard WHERE mandt = SESSION_CONTEXT('CLIENT') ) SELECT matnr, werks, labst FROM ranked WHERE rn = 1;4-3. LAG / LEAD — 이전/다음 행 참조 (문법 동일)
RETURN SELECT budat, dmbtr, LAG(dmbtr, 1, 0) OVER (ORDER BY budat) AS prev_amt, LEAD(dmbtr, 1, 0) OVER (ORDER BY budat) AS next_amt FROM bkpf WHERE mandt = SESSION_CONTEXT('CLIENT');4-4. 누계 (Cumulative SUM) — 문법 동일
RETURN SELECT budat, dmbtr, SUM(dmbtr) OVER (ORDER BY budat ROWS UNBOUNDED PRECEDING) AS cum_sum FROM bkpf WHERE mandt = SESSION_CONTEXT('CLIENT');5. Query Provider 패턴 (@ObjectModel.query.implementedBy)
5-1. 개념 구조
CDS View └── @ObjectModel.query.implementedBy: 'ABAP:ZCL_##_QUERY_PROVIDER' ↓ (데이터 조회 위임) ZCL_##_QUERY_PROVIDER (implements IF_RAP_QUERY_PROVIDER) └── method SELECT └── AMDP 호출 또는 직접 SQLScript 실행⚠️
@ObjectModel.query.implementedBy를 선언하면 CDS 자체 DB 조회가 비활성화됩니다.
지정 클래스의SELECT메서드가 모든 데이터 조회를 담당하므로, 필터·페이징·정렬을 직접 구현해야 합니다.5-2. CDS View 선언
@ObjectModel.query.implementedBy: 'ABAP:ZCL_ZSALES_QUERY_PROVIDER' define root view entity ZC_SALES_ORDER as projection on ZI_SALES_ORDER { key SalesOrder, Customer, NetAmount, CreatedDate }5-3. Query Provider 클래스 구현 (AMDP 연동)
CLASS zcl_zsales_query_provider DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES if_rap_query_provider. ENDCLASS. CLASS zcl_zsales_query_provider IMPLEMENTATION. METHOD if_rap_query_provider~select. " 1) 필터 파라미터 추출 DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ). DATA lv_date_from TYPE vbak-erdat. DATA lv_date_to TYPE vbak-erdat. LOOP AT lt_filter INTO DATA(ls_filter) WHERE name = 'CREATEDDATE'. lv_date_from = ls_filter-range[ 1 ]-low. lv_date_to = ls_filter-range[ 1 ]-high. ENDLOOP. " 2) AMDP 호출 DATA lt_result TYPE TABLE OF zst_sales_result. zcl_amdp_sales=>get_sales_data( EXPORTING iv_date_from = lv_date_from iv_date_to = lv_date_to IMPORTING et_result = lt_result ). " 3) 페이징 처리 (필수 구현) DATA(lv_offset) = io_request->get_paging( )->get_offset( ). DATA(lv_limit) = io_request->get_paging( )->get_page_size( ). IF lv_limit <> if_rap_query_paging=>page_size_unlimited. lt_result = lt_result[ lv_offset + 1 .. MIN( lv_offset + lv_limit, lines( lt_result ) ) ]. ENDIF. " 4) 응답 설정 io_response->set_total_number_of_records( lines( lt_result ) ). io_response->set_data( lt_result ). ENDMETHOD. ENDCLASS.5-4. Query Provider 사용 가능 기능 및 제약
항목 가능 비고 io_request->get_filter()✅ 필터 조건 추출 io_request->get_paging()✅ 오프셋/페이지 크기 io_request->get_sort_elements()✅ 정렬 기준 io_request->get_requested_elements()✅ 요청된 필드 목록 io_request->get_aggregation()✅ (7.57+) 집계 요청 처리 AMDP 직접 호출 ✅ 권장 방식 ABAP SELECT 사용 ✅ AMDP 대비 성능 낮음 io_response->set_data()✅ 결과 반환 필수 io_response->set_total_number_of_records()✅ 전체 건수 반환 필수 CDS 자체 필터 자동 적용 ❌ 직접 구현 필요 CDS Association 자동 JOIN ❌ 직접 JOIN 구현 6. 변환 체크리스트
Oracle → AMDP 변환 시 아래 항목을 반드시 확인하세요.
NVL→COALESCE치환DECODE→MAP(col, v1, r1, ..., default)또는CASE WHEN치환ROWNUM→LIMIT또는ROW_NUMBER() OVER(...)치환(+)outer join →LEFT/RIGHT OUTER JOIN표준 문법으로TO_CHAR→TO_VARCHAR치환LISTAGG→STRING_AGG치환MINUS→EXCEPT치환SYSDATE→CURRENT_DATE또는NOW()치환FROM DUAL제거 또는FROM DUMMY로- 모든 테이블에
WHERE mandt = SESSION_CONTEXT('CLIENT')추가 - 사용하는 모든 테이블을
USING절에 선언 - TABLE FUNCTION이면
OPTIONS READ-ONLY선언 - PROCEDURE에서
EXECUTE IMMEDIATE사용 시 SQL Injection 검토
7. WITH 구문 + TABLE FUNCTION / EXECUTE IMMEDIATE 패턴
7-1. TABLE FUNCTION 내부에서 WITH(CTE) 구문 — 다단계 집계
METHOD get_sales_summary BY DATABASE FUNCTION FOR HDB LANGUAGE SQLSCRIPT OPTIONS READ-ONLY USING vbak vbap. RETURN WITH " 1단계: 기간 내 주문 헤더 base_orders AS ( SELECT vbeln, kunnr, vkorg, erdat FROM vbak WHERE mandt = SESSION_CONTEXT('CLIENT') AND erdat BETWEEN :iv_date_from AND :iv_date_to ), " 2단계: 아이템 집계 item_summary AS ( SELECT vbeln, SUM(kwmeng) AS total_qty, SUM(netwr) AS total_netwr, COUNT(*) AS line_count FROM vbap WHERE mandt = SESSION_CONTEXT('CLIENT') GROUP BY vbeln ), " 3단계: 고객별 매출 + 순위 ranked AS ( SELECT o.kunnr, o.vkorg, SUM(i.total_netwr) AS sales_amt, RANK() OVER ( PARTITION BY o.vkorg ORDER BY SUM(i.total_netwr) DESC ) AS sales_rank FROM base_orders AS o JOIN item_summary AS i ON o.vbeln = i.vbeln GROUP BY o.kunnr, o.vkorg ) " 4단계: 상위 N건 반환 SELECT kunnr, vkorg, sales_amt, sales_rank FROM ranked WHERE sales_rank <= :iv_top_n; ENDMETHOD.7-2. PROCEDURE에서 TABLE FUNCTION 결과를 변수에 직접 받기 (권장)
METHOD execute_with_table_func BY DATABASE PROCEDURE FOR HDB LANGUAGE SQLSCRIPT USING ZCL_AMDP_SALES=>GET_SALES_SUMMARY. " USING 절에 반드시 선언 -- TABLE FUNCTION 결과를 테이블 변수에 직접 할당 lt_result = SELECT kunnr, vkorg, sales_amt, sales_rank FROM ZCL_AMDP_SALES=>GET_SALES_SUMMARY( iv_date_from => :iv_date_from, iv_date_to => :iv_date_to, iv_top_n => :iv_top_n ); -- 추가 가공 et_result = SELECT * FROM :lt_result WHERE sales_rank = 1; ENDMETHOD.7-3. PROCEDURE에서 EXECUTE IMMEDIATE — 동적 호출
METHOD execute_dynamic_call BY DATABASE PROCEDURE FOR HDB LANGUAGE SQLSCRIPT USING ZCL_AMDP_SALES=>GET_SALES_SUMMARY. DECLARE lv_sql NVARCHAR(2000); DECLARE lv_top_n INT; lv_top_n := :iv_top_n; lv_sql := 'SELECT kunnr, vkorg, sales_amt, sales_rank' || ' FROM ZCL_AMDP_SALES=>GET_SALES_SUMMARY(' || ' iv_date_from => ''' || :iv_date_from || ''',' || ' iv_date_to => ''' || :iv_date_to || ''',' || ' iv_top_n => ' || lv_top_n || ')'; EXECUTE IMMEDIATE lv_sql INTO et_result; ENDMETHOD.⚠️ EXECUTE IMMEDIATE 주의사항
• TABLE FUNCTION이 아닌 PROCEDURE 안에서만 사용 가능
• 동적 호출 시에도
USING절에 해당 TABLE FUNCTION을 반드시 선언해야 활성화됨• 문자열 연결로 파라미터 조립 시 SQL Injection 위험 → 가능하면 바인딩 파라미터(
:param) 방식 사용7-4. ABAP에서 TABLE FUNCTION 결과 호출 (Open SQL SELECT INTO)
" ABAP 레이어에서 AMDP Table Function 직접 호출 SELECT kunnr, vkorg, sales_amt, sales_rank INTO TABLE @DATA(lt_top_sales) FROM ( SELECT kunnr, vkorg, sales_amt, sales_rank FROM ZCL_AMDP_SALES=>GET_SALES_SUMMARY( iv_date_from = @lv_date_from iv_date_to = @lv_date_to iv_top_n = @lv_top_n ) ) AS result WHERE sales_rank = 1.'SAP' 카테고리의 다른 글
SAP ABAP 호출 구조 정리 — Entity · Query Provider · AMDP (0) 2026.05.12 CDS Consumption Entity 어노테이션 정리 (0) 2026.05.12 [claude] AMDP Scalar Function 이용 가이드 (0) 2026.05.11 [claude] Oracle → SAP Consumption Entity 전환 (0) 2026.05.11 AMDP Table Function에서 MANDT는 꼭 필요한가? (Made by Claude.) (0) 2026.05.10