News

AQ 실전 3/5 — Unit Conversion: @Semantics.quantity.unitOfMeasure 완전 정복

▶ YouTube에서 보기
# AQ 실전 3/5 — Unit Conversion: @Semantics.quantity.unitOfMeasure 완전 정복 ABAP CDS Analytics Query에서 수량(Quantity)을 다룰 때 가장 흔히 마주치는 함정은 "숫자는 나오는데 단위가 안 보인다" 혹은 "Fiori Elements Analytical List Page에서 EA를 KG로 바꾸려 했더니 변환 버튼이 비활성화돼 있다"는 상황이다. 이 시리즈 3편에서는 `@Semantics.quantity.unitOfMeasure`와 짝꿍 어노테이션 `@Semantics.unitOfMeasure: true`를 어떻게 조합해야 Fiori의 자동 단위 변환·집계 정합성·OData 메타데이터 노출이 한 번에 풀리는지 정리한다. --- ## 1. 왜 수량 필드에 단위 어노테이션이 필요한가 수량 데이터는 단순한 숫자가 아니다. `100`이라는 값은 그 자체로 의미가 없으며 `100 EA(개)`, `100 KG`, `100 L(리터)` 중 무엇인지 알아야 비로소 비즈니스 데이터가 된다. SAP의 표준 데이터 모델은 수량(`QUAN`)과 단위(`UNIT`) 두 컬럼을 한 쌍으로 보관하는 전통이 있고, ABAP CDS 역시 이 관습을 그대로 이어받는다. 문제는 데이터베이스 레벨에서는 두 컬럼이 단순히 옆에 나란히 놓여 있을 뿐, "이 숫자의 단위는 저 컬럼이다"라는 관계를 시스템이 자동으로 알 수 없다는 점이다. 어노테이션이 없는 상태에서는 다음과 같은 실무 문제가 줄줄이 발생한다. - **집계 오류**: `SUM(Quantity)`를 단위 구분 없이 합산하여 EA와 KG가 한 값으로 더해진다. - **변환 불가**: Fiori Elements Analytical List Page(ALP)나 Smart Table의 단위 변환 메뉴가 비활성화된다. - **OData 메타데이터 누락**: `sap:unit` 속성이 EDM에 노출되지 않아 클라이언트 SDK가 단위를 인식하지 못한다. - **리포트 출력**: SAC, Analysis for Office 등 외부 클라이언트가 단위 헤더를 그릴 근거를 잃는다. 이 모든 문제를 한 번에 닫는 열쇠가 `@Semantics.quantity.unitOfMeasure`(수량 → 단위 참조)와 `@Semantics.unitOfMeasure: true`(단위 필드 선언) 두 어노테이션의 페어링이다. 두 어노테이션은 반드시 짝으로 등장해야 하며, 한쪽만 선언하면 절반의 효과조차 보장되지 않는다. > 비유하자면 수량 필드는 "주소가 적힌 봉투"이고 단위 필드는 "수신자 본인"이다. 봉투에 주소만 적고 수신자가 등록돼 있지 않으면 우체부(Fiori)는 배달을 포기한다. --- ## 2. @Semantics.quantity.unitOfMeasure 기초 문법 `@Semantics.quantity.unitOfMeasure`는 **수량 필드 위**에 선언하며, 값으로는 같은 뷰 내에 존재하는 **단위 코드 필드의 이름**을 문자열로 지정한다. ```abap @DefaultAggregation: #SUM @Semantics.quantity.unitOfMeasure: 'BaseUnit' Quantity, ``` 작성 시 반드시 지켜야 할 규칙은 다음과 같다. 1. **필드명은 작은따옴표로 감싼 문자열**이다. CDS 식별자가 아니라 문자열 리터럴이다. 2. **대소문자가 정확히 일치**해야 한다. CDS는 표면적으로 대소문자에 관대하지만 어노테이션 파서는 식별자 비교 시 엄격한 매칭을 요구하는 케이스가 존재한다. 가급적 정의부와 동일한 표기를 쓰는 것이 권장된다. 3. 참조 대상은 **같은 뷰의 SELECT 리스트에 포함된 단위 코드 필드**여야 한다. 외부 뷰의 필드를 직접 가리킬 수 없다. 4. `@DefaultAggregation: #SUM`과 자연스럽게 결합된다. 단위가 다른 행끼리 합산되지 않도록 OData $apply 레이어가 단위별 그룹핑을 수행한다. 이 어노테이션 한 줄이 OData 메타데이터에서는 다음과 같이 매핑된다. ```xml ``` `sap:unit="BaseUnit"`이 바로 어노테이션이 만들어낸 결과물이며, Fiori Elements는 이 속성을 보고 단위 변환 메뉴를 활성화한다. --- ## 3. @Semantics.unitOfMeasure: true 선언 수량 쪽에 참조 화살표를 그렸다면, 단위 코드 필드 쪽에는 "내가 바로 그 단위다"라고 명패를 달아야 한다. 그 명패가 `@Semantics.unitOfMeasure: true`이다. ```abap @Semantics.unitOfMeasure: true BaseUnit ``` 이 어노테이션이 하는 일은 다음과 같다. - 해당 필드 값을 **ISO/T006 단위 마스터(MEINH 등)와 호환되는 단위 코드**로 인식하게 만든다. - OData 메타데이터에서 `sap:semantics="unit-of-measure"` 속성을 추가한다. - Fiori UI에 노출될 때 단위 텍스트(예: `KG → 킬로그램`) 결합 표시가 가능해진다. - 통화/단위 변환 함수(`UNIT_CONVERSION(...)`)와의 정합성을 확보한다. 주의해야 할 점은 **데이터 타입**이다. 단위 필드는 `meins` 도메인 또는 그에 상응하는 3자리 문자형(`CHAR3`/`UNIT`) 데이터 타입이어야 변환 함수가 안전하게 동작한다. 임의의 문자열 컬럼에 `@Semantics.unitOfMeasure: true`만 붙인다고 변환이 활성화되지는 않는다. 수량과 단위 두 어노테이션이 짝을 이뤄야 비로소 다음 체크포인트가 모두 통과된다. | 체크 항목 | 단독 선언 시 | 페어 선언 시 | |---|---|---| | Fiori 단위 변환 메뉴 | 비활성 | 활성 | | `SUM` 단위별 그룹핑 | 부정확 | 정확 | | OData `sap:unit` | 누락/불완전 | 정상 | | SAC/AfO 헤더 표시 | 미인식 | 인식 | --- ## 4. 실전 코드 — ZQ_SalesQuery 전체 예시 이제 두 어노테이션을 실제 Analytics Query 뷰에 결합한 전체 예제를 본다. Cube 뷰(`ZC_SalesCube`)는 이미 존재한다고 가정하고, 그 위에 얹는 Query 뷰만 다룬다. ```abap @AbapCatalog.sqlViewName: 'ZQSALESQRY' @AbapCatalog.compiler.compareFilter: true @AccessControl.authorizationCheck: #CHECK @EndUserText.label: 'Sales Analytics Query' @Analytics.query: true @OData.publish: true define view ZQ_SalesQuery as select from ZC_SalesCube { @AnalyticsDetails.query.axis: #ROWS key Material, @AnalyticsDetails.query.axis: #ROWS key Plant, @AnalyticsDetails.query.axis: #COLUMNS @DefaultAggregation: #SUM @Semantics.quantity.unitOfMeasure: 'BaseUnit' @EndUserText.label: 'Sales Quantity' Quantity, @AnalyticsDetails.query.axis: #FREE @Semantics.unitOfMeasure: true BaseUnit } ``` 각 라인의 역할을 풀어보면 다음과 같다. - `@Analytics.query: true` — 이 뷰가 단순 데이터 소스가 아니라 **Analytical Query**임을 선언한다. 이 어노테이션이 있어야 BW Analytic Manager가 처리한다. - `@AnalyticsDetails.query.axis: #ROWS` — Material, Plant를 기본 Row 축에 배치. ALP를 열면 좌측 표 영역에 자동 배치된다. - `@AnalyticsDetails.query.axis: #COLUMNS` — Quantity를 Column 축, 즉 수치(KPI) 영역에 배치. - `@AnalyticsDetails.query.axis: #FREE` — BaseUnit은 Free 축으로 두어 기본 화면에는 노출되지 않지만 필요 시 드래그로 추가 가능. - `@DefaultAggregation: #SUM` — 수량 합산 동작 지정. - `@Semantics.quantity.unitOfMeasure: 'BaseUnit'` — Quantity의 단위 참조. - `@Semantics.unitOfMeasure: true` — BaseUnit이 단위 코드 필드임을 명시. 이 뷰를 OData 서비스로 노출한 뒤 Fiori Elements ALP를 띄우면 차트와 표 헤더에 `Quantity (BaseUnit)` 형태로 단위가 함께 표시되고, 컬럼 우클릭 메뉴에 "단위 변환" 항목이 활성화된다. --- ## 5. 다중 수량 필드 연결 패턴 실무 데이터 모델에서는 한 뷰에 여러 수량 필드가 등장한다. 주문 수량, 출하 수량, 재고 수량처럼 동일 자재의 여러 상태를 한 화면에서 비교해야 하기 때문이다. 이때도 단위 필드는 보통 **하나**(예: `BaseUnit`)로 통일된다. 모든 수량이 자재의 기본 단위(MARA-MEINS) 기준이라면 단위 컬럼을 여러 개 둘 이유가 없다. ```abap @Analytics.query: true define view ZQ_StockMovementQuery as select from ZC_StockCube { @AnalyticsDetails.query.axis: #ROWS key Material, @AnalyticsDetails.query.axis: #COLUMNS @DefaultAggregation: #SUM @Semantics.quantity.unitOfMeasure: 'BaseUnit' OrderQuantity, @AnalyticsDetails.query.axis: #COLUMNS @DefaultAggregation: #SUM @Semantics.quantity.unitOfMeasure: 'BaseUnit' DeliveryQuantity, @AnalyticsDetails.query.axis: #COLUMNS @DefaultAggregation: #SUM @Semantics.quantity.unitOfMeasure: 'BaseUnit' StockQuantity, @AnalyticsDetails.query.axis: #FREE @Semantics.unitOfMeasure: true BaseUnit } ``` 다중 수량 패턴에서 빈번한 실수는 다음 두 가지이다. 1. **단위 필드 누락**: 수량 3개에 모두 어노테이션을 달았는데 정작 BaseUnit 필드를 SELECT 리스트에서 빠뜨리는 경우. 컴파일은 통과돼도 런타임에 OData 메타데이터 생성 단계에서 경고가 발생한다. 2. **단위 필드를 수량 개수만큼 여러 개 두는 것**: `OrderUnit`, `DeliveryUnit`, `StockUnit`처럼 개별 단위 필드를 두면 어노테이션도 각각 다른 필드명으로 지정해야 한다. 단위가 실제로 다를 수 있는 시나리오(예: 주문은 PAL 단위, 출하는 EA 단위)라면 분리가 옳지만, 단순히 "그래야 깔끔할 것 같아서"라는 이유라면 오히려 단위 변환 로직이 복잡해진다. 서로 다른 단위가 섞이는 시나리오에서는 Cube 단계에서 `UNIT_CONVERSION` 함수로 모든 수량을 동일 단위로 통일한 뒤 Query에 올리는 것이 일반적으로 권장된다. ```abap unit_conversion( quantity => OrderQuantity, source_unit => OrderUnit, target_unit => cast('EA' as abap.unit( 3 )), error_handling => 'SET_TO_NULL' ) as OrderQuantityInEA ``` --- ## 6. 통화 변환(@Semantics.amount.currencyCode)과의 비교 수량 어노테이션의 형제 격으로 통화 어노테이션이 있다. 구조가 거의 동일하므로 둘을 비교하면 머리에 정리하기 쉽다. | 구분 | 수량(Quantity) | 통화(Amount) | |---|---|---| | 값 필드 어노테이션 | `@Semantics.quantity.unitOfMeasure: 'UnitField'` | `@Semantics.amount.currencyCode: 'CurrField'` | | 코드 필드 어노테이션 | `@Semantics.unitOfMeasure: true` | `@Semantics.currencyCode: true` | | 코드 데이터 타입 | `meins` (UNIT, CHAR3) | `waers` (CUKY, CHAR5) | | 변환 함수 | `UNIT_CONVERSION(...)` | `CURRENCY_CONVERSION(...)` | | OData 메타데이터 | `sap:unit="..."` | `sap:unit="..."` (동일 속성에 매핑) | ```abap @DefaultAggregation: #SUM @Semantics.amount.currencyCode: 'Currency' NetAmount, @Semantics.currencyCode: true Currency ``` 두 패턴은 서로 독립적으로 적용 가능하며, 매출 분석처럼 금액과 수량을 함께 다루는 뷰에서는 동일 뷰에 두 페어를 모두 선언하는 것이 일반적이다. 한 뷰 안에 `BaseUnit`(단위)과 `Currency`(통화) 코드 필드가 공존하는 패턴이 매출/수익성 분석에서 가장 자주 등장한다. --- ## 7. 자주 발생하는 오류와 해결법 ### Q1. "Field 'BaseUnit' specified in @Semantics.quantity.unitOfMeasure is not part of the SELECT list" 에러 가장 흔한 활성화 오류다. 원인은 단순하다. 어노테이션에서 가리킨 단위 필드가 정작 SELECT 리스트에 없는 경우다. Cube 뷰에서 단위 필드를 노출시키지 않은 채 Query 뷰에서 그것을 참조하려고 하면 발생한다. 해결은 Cube 뷰와 Query 뷰 양쪽 모두에 단위 필드를 포함시키는 것이다. Query 뷰에서 화면에 보이게 하고 싶지 않다면 `@AnalyticsDetails.query.axis: #FREE` 또는 `#NOT_ASSIGNED`로 두면 된다. ### Q2. Fiori ALP에서 단위 변환 메뉴가 여전히 비활성 체크리스트는 다음 순서로 본다. 1. 수량 필드에 `@Semantics.quantity.unitOfMeasure`가 있는가 2. 단위 필드에 `@Semantics.unitOfMeasure: true`가 있는가 3. 단위 필드의 데이터 타입이 `meins`/`unit` 계열인가 4. OData 메타데이터(`$metadata`)를 직접 열어 `sap:unit="..."`이 보이는가 5. Cube 뷰부터 끝단까지 어노테이션이 전파되는가(중간 뷰에서 단위 필드를 빠뜨리는 경우 자주 발생) 4번 단계까지 통과하는데도 메뉴가 안 보이면 Fiori 앱의 매니페스트(`manifest.json`) 측 설정 또는 SAPUI5 버전 이슈를 점검해야 한다. ### Q3. 단위가 다른 행이 한 셀에 합산돼 잘못된 합계가 표시 이는 어노테이션이 빠진 상태에서 `SUM`만 적용했을 때 나타나는 전형적 증상이다. 단위 어노테이션 페어를 정확히 선언하면 Analytic Manager가 단위별로 그룹핑하여 합산한다. 그래도 한 셀에 여러 단위가 섞여 있다면 표시 측면에서 `*`(혼합 단위) 마커가 나타난다. 비즈니스적으로 의미 있는 비교를 위해서는 Cube 단계에서 단위를 통일하는 것이 일반적으로 권장된다. ### Q4. 단위 변환은 되는데 텍스트("킬로그램")가 안 보인다 T006A(단위 텍스트 테이블) 조인이 OData 응답에 포함되지 않은 경우다. Fiori Smart Field가 단위 코드로 텍스트를 fetch 하려면 `sap:text` 또는 `@ObjectModel.text.element` 연결이 필요하다. Cube 뷰에서 단위 마스터를 조인하여 텍스트 컬럼을 함께 노출하고, 단위 필드에 `@ObjectModel.text.element: ['BaseUnitText']`를 추가하면 해결된다. --- ## 8. 마무리 및 다음 단계 수량 어노테이션 페어링은 외형은 두 줄에 불과하지만, 그 두 줄이 빠지면 Fiori 단위 변환, OData 메타데이터, 집계 정합성, 외부 클라이언트 호환성이 모두 한꺼번에 무너진다. 정리하면 다음 세 가지를 기억하면 된다. - 수량 필드에는 `@Semantics.quantity.unitOfMeasure: '단위필드명'`을 선언한다. - 단위 필드에는 `@Semantics.unitOfMeasure: true`를 선언한다. - 두 어노테이션은 반드시 같은 뷰의 SELECT 리스트 안에서 짝을 이뤄야 한다. 다중 수량 필드 패턴, 통화 어노테이션과의 비교, 그리고 단위 변환 함수(`UNIT_CONVERSION`)의 활용까지 함께 익혀두면 Analytics Query에서 수량 관련 이슈의 90% 이상은 사전에 차단할 수 있다.

댓글 0

아직 댓글이 없습니다.