시맨틱 레이어(Semantic Layer)는 원천 데이터와 이를 쓰는 도구(SQL, 대시보드, AI) 사이에서 테이블·컬럼 같은 기술 구조를 "매출", "고객 수" 같은 업무 용어와 지표로 정의해 두는 층입니다. 정의가 한 곳에 있으므로 어떤 도구로 조회하든 같은 숫자가 나옵니다. Databricks에서는 Metric View가 이 중 지표 정의를 맡습니다.
시맨틱 레이어가 없으면 같은 지표라도 누가 계산하느냐에 따라 숫자가 달라집니다. "매출"만 해도 대시보드 담당자는 취소 주문을 빼고 분석가는 넣습니다. "고객 수"는 월별 요약표를 다시 합산하는 순간 여러 달 주문한 고객이 중복으로 세어집니다. Metric View는 이런 지표 정의를 Unity Catalog에 한 번만 등록해 두고, SQL·AI/BI 대시보드·Genie가 모두 같은 정의를 쓰게 합니다.
이 글에서는 다음 내용을 다룹니다.
- 메저와 디멘전, 팩트·디멘전 테이블, 스타 스키마의 개념
- Metric View가 일반 뷰와 무엇이 다른지
- 단일 테이블로 첫 Metric View를 만들고 조회하기
- 조인·필터·비율·window 메저로 확장하기
- Catalog Explorer·AI/BI 대시보드·Genie에서 활용하기
ℹ️ 실습의 쇼핑몰 "도토리마켓"과 고객·상품은 모두 가상입니다. 모든 결과는 Databricks 워크스페이스(AWS 서울 리전, Serverless SQL Warehouse, DBSQL 2026.39)에서 직접 실행해 확인했습니다.
1. 먼저 알아둘 개념
Metric View로 지표를 정의하려면 두 가지를 알아야 합니다. 지표를 이루는 메저와 디멘전, 그리고 그 재료가 들어 있는 테이블 구조인 팩트·디멘전 테이블과 스타 스키마입니다.
1.1 메저와 디멘전: 질문을 둘로 나누기
"지역별 매출은?" 같은 분석 질문은 항상 두 부분으로 나뉩니다.
| 구분 | 역할 | 질문에서 | SQL에서 |
|---|---|---|---|
| 디멘전 (Dimension) | 어떻게 나눌까 | "지역별" | GROUP BY 대상 |
| 메저 (Measure) | 무엇을 셀까 | "매출" | SUM·COUNT 같은 집계 함수 |

같은 메저를 다른 디멘전으로 나누면 다른 질문이 됩니다. "월별 매출", "카테고리별 매출", "VIP 고객 매출"은 모두 매출이라는 메저 하나에 디멘전만 바꾼 질문입니다. 그러니 메저의 공식을 한 곳에 정의해 두고, 디멘전은 질문할 때 고르게 하면 됩니다. 이것이 Metric View의 기본 발상입니다.
1.2 팩트 테이블, 디멘전 테이블, 스타 스키마
메저와 디멘전의 재료는 테이블에 있습니다. 분석용 테이블은 성격에 따라 두 종류로 나눕니다.
| 구분 | 담는 내용 | 특징 | 도토리마켓 예 |
|---|---|---|---|
| 팩트 테이블 (Fact) | 일어난 일(사건·거래)의 기록 | 행이 계속 늘어남, 숫자 컬럼 보유 | fact_orders: 주문 1건 = 1행, 수량·단가 |
| 디멘전 테이블 (Dimension) | 사건을 설명하는 기준 정보 | 행 수가 적고 잘 안 바뀜, 설명 컬럼 보유 | dim_customer: 고객의 지역·등급dim_product: 상품의 카테고리 |
팩트 테이블에는 "누가·무엇을" 같은 정보가 ID로만 들어 있습니다. 주문 테이블에는 customer_id = C01만 있고, 그 고객이 서울에 사는 VIP라는 사실은 고객 디멘전 테이블에 있습니다.
팩트 테이블을 가운데 두고 디멘전 테이블을 ID(키)로 연결한 구조를 스타 스키마(Star Schema)라고 합니다. 그림으로 그리면 별 모양이 되어 붙은 이름입니다.

- 디멘전 테이블 1행이 팩트 여러 행과 연결됩니다(1:N). 고객 한 명이 주문을 여러 번 하기 때문입니다.
- 거꾸로 보면 팩트 1행은 디멘전 테이블 딱 1행으로 이어집니다(N:1). Metric View의 조인은 이 N:1 관계를 전제로 합니다.
- 디멘전 테이블이 다시 다른 디멘전 테이블로 이어지는 구조(예: 고객 → 국가)는 스노우플레이크 스키마라고 하며, Metric View는 중첩 조인으로 지원합니다.
ℹ️ 디멘전과 디멘전 테이블은 다릅니다. 디멘전은 "나누는 기준"이고, 디멘전 테이블은 그런 기준 정보를 모아 둔 테이블입니다. 디멘전은 팩트 테이블에서 나올 수도 있습니다. 실습의 주문월(
order_date)과 주문상태(status)는 팩트 테이블의 컬럼이고, 지역(region)과 카테고리(category)는 디멘전 테이블의 컬럼입니다.
이 개념들이 Metric View 정의(YAML)의 어느 키가 되는지는 2.2절 표에 정리했습니다.
2. Metric View는 무엇일까요
Metric View는 Unity Catalog에 등록하는 특별한 뷰입니다. 디멘전과 메저의 정의를 YAML로 적어 두면, 조회하는 쪽에서 원하는 디멘전을 골라 메저를 요청합니다. 그러면 엔진이 그 단위에 맞는 집계를 원본 데이터에서 계산합니다.
2.1 일반 뷰와 무엇이 다를까요
일반 뷰로 지표를 만들려면 GROUP BY를 뷰 안에 적어야 합니다. 그러면 집계 단위가 만들 때 고정됩니다. 월별로 만든 요약 뷰에서 분기 수치가 필요하면 월별 결과를 다시 합산해야 하는데, 이때 틀리는 지표가 생깁니다.

| 지표 유형 | 예 | 요약 결과를 다시 합산하면 |
|---|---|---|
| 합계 (가산형) | 주문금액, 주문수 | 정확함 |
| 고유 개수 | 고객수 | 틀림 — 여러 달 주문한 고객을 중복으로 셈 |
| 비율·평균 | 객단가, 취소율 | 틀림 — 평균의 평균은 전체 평균이 아님 |
Metric View는 집계된 결과가 아니라 정의를 보관합니다. 월별로 묻든 분기로 묻든 원본 행에서 다시 계산하므로 고유 개수와 비율도 정확합니다. 이 차이는 4장에서 실제 숫자로 확인합니다.
2.2 구성 요소

| YAML 키 | 역할 | 1장 개념 | 필수 |
|---|---|---|---|
version |
YAML 명세 버전 (실습은 1.1) |
— | 선택 (기본 1.1) |
source |
기준이 되는 팩트 테이블·뷰·SQL 쿼리 | 팩트 테이블 | 필수 |
joins |
디멘전 테이블 연결 (스타·스노우플레이크) | 디멘전 테이블, 스타 스키마 | 선택 |
filter |
모든 조회에 적용되는 전역 WHERE 조건 |
— | 선택 |
dimensions |
나누는 기준. 이름 + 비집계 SQL 식 | 디멘전 | 필수 (1개 이상) |
measures |
집계 공식. 이름 + 집계 SQL 식 | 메저 | 필수 (1개 이상) |
materialization |
사전 집계 설정 | — | 선택 |
Metric View도 Unity Catalog 객체라서 테이블과 같은 방식으로 권한을 주고, 리니지와 메타데이터가 관리됩니다. 공식 문서에 따르면 SQL 편집기, 노트북, AI/BI 대시보드, Genie, Databricks SQL 알림(정해진 일정에 쿼리를 실행해 조건을 만족하면 알려 주는 기능)에서 사용할 수 있고, Power BI·Tableau 같은 외부 BI 도구에서도 조회할 수 있습니다.
3. 실습 준비
3.1 환경
| 항목 | 실습 값 |
|---|---|
| 워크스페이스 | AWS 서울 리전(ap-northeast-2) |
| 컴퓨트 | Serverless SQL Warehouse (DBSQL 2026.39) |
| 위치 | <catalog>.metric_view_demo 스키마 |
| 필요 권한 | 원본 테이블 SELECT, 스키마 USE SCHEMA·CREATE TABLE |
공식 문서 기준으로 Metric View 생성은 Databricks Runtime 16.4 이상에서 지원되고, 이 글에서 쓰는 YAML version: 1.1과 display_name·synonyms·format 같은 메타데이터(5.5절)는 17.3 이상을 기준으로 합니다. Serverless SQL Warehouse는 최신 버전이 적용되어 있어 따로 신경 쓸 필요가 없습니다.
💡 아래 SQL은 모두 SQL 편집기에서 대상 catalog와
metric_view_demo스키마를 선택한 상태로 실행한다고 가정합니다. Metric View의source·joins에는 세 부분 이름(catalog.schema.table)을 적어야 하므로<catalog>를 실제 이름으로 바꿉니다.
3.2 샘플 데이터 만들기
스타 스키마 그림과 똑같은 테이블 세 개를 만듭니다.
CREATE SCHEMA IF NOT EXISTS metric_view_demo;
-- 고객 디멘전
CREATE OR REPLACE TABLE dim_customer (
customer_id STRING, customer_name STRING, region STRING, grade STRING
) COMMENT '고객 디멘전';
INSERT OVERWRITE dim_customer VALUES
('C01','도토리','서울','VIP'), ('C02','다람쥐','서울','일반'),
('C03','참나무','부산','일반'), ('C04','솔방울','부산','VIP'),
('C05','밤송이','대구','일반'), ('C06','은행잎','대구','일반');
-- 상품 디멘전
CREATE OR REPLACE TABLE dim_product (
product_id STRING, product_name STRING, category STRING
) COMMENT '상품 디멘전';
INSERT OVERWRITE dim_product VALUES
('P01','무선 이어폰','전자'), ('P02','스마트워치','전자'),
('P03','텀블러','생활'), ('P04','백팩','패션');
-- 주문 팩트: 주문 1건 = 1행
CREATE OR REPLACE TABLE fact_orders (
order_id STRING, order_date DATE, customer_id STRING, product_id STRING,
quantity INT, unit_price INT, status STRING
) COMMENT '주문 팩트';
INSERT OVERWRITE fact_orders VALUES
('O01', DATE'2026-01-05','C01','P01',1,150000,'완료'),
('O02', DATE'2026-01-12','C02','P03',2, 25000,'완료'),
('O03', DATE'2026-01-18','C03','P04',1, 80000,'완료'),
('O04', DATE'2026-01-25','C01','P03',1, 25000,'취소'),
('O05', DATE'2026-02-02','C04','P02',1,300000,'완료'),
('O06', DATE'2026-02-09','C01','P04',1, 80000,'완료'),
('O07', DATE'2026-02-14','C05','P03',3, 25000,'완료'),
('O08', DATE'2026-02-20','C02','P01',1,150000,'취소'),
('O09', DATE'2026-02-27','C06','P03',1, 25000,'완료'),
('O10', DATE'2026-03-03','C01','P02',1,300000,'완료'),
('O11', DATE'2026-03-10','C03','P01',2,150000,'완료'),
('O12', DATE'2026-03-17','C04','P03',2, 25000,'완료'),
('O13', DATE'2026-03-24','C05','P04',1, 80000,'취소'),
('O14', DATE'2026-03-30','C02','P03',1, 25000,'완료');
데이터의 특징을 미리 짚어 두면 뒤의 결과를 읽기 쉽습니다.
- 3개월 동안 고객 6명이 14건을 주문했고, 금액 합계는 1,690,000원입니다.
- 고객
C01(도토리)과C02(다람쥐)는 세 달 모두 주문했습니다. 고유 고객 수를 셀 때 이 중복이 문제가 됩니다. - 취소 주문이 3건(
O04,O08,O13) 있습니다.
4. 첫 Metric View 만들기: 단일 테이블
조인 없이 fact_orders 하나만으로 시작합니다. 먼저 일반 뷰의 한계를 직접 확인하고, 같은 지표를 Metric View로 만들어 비교합니다.
4.1 일반 뷰로 월별 요약을 만들면 생기는 문제
흔히 만드는 월별 요약 뷰입니다.
CREATE OR REPLACE VIEW monthly_summary AS
SELECT CAST(DATE_TRUNC('MONTH', order_date) AS DATE) AS order_month,
SUM(quantity * unit_price) AS total_amount, -- 주문금액
COUNT(1) AS order_count, -- 주문수
COUNT(DISTINCT customer_id) AS customer_count, -- 고객수
SUM(quantity * unit_price) / COUNT(1) AS avg_order_value -- 객단가
FROM fact_orders
GROUP BY ALL;
💡 SQL 읽는 법 -
COUNT(1): 그룹에 속한 행 수를 셉니다. 주문 1건이 1행이므로 주문수가 됩니다.COUNT(*)와 같습니다. -COUNT(DISTINCT customer_id): 그룹 안에서 중복을 뺀customer_id의 개수를 셉니다. 1월에는C01(도토리)이 두 번 주문해서 주문수는 4건, 고객수는 3명입니다. -GROUP BY ALL: SELECT 목록에서 집계 함수가 아닌 식(여기서는order_month)을 모두 그룹 기준으로 삼는 Databricks SQL 문법입니다.GROUP BY 1과 같습니다.
| order_month | total_amount | order_count | customer_count | avg_order_value |
|---|---|---|---|---|
| 2026-01-01 | 305,000 | 4 | 3 | 76,250 |
| 2026-02-01 | 630,000 | 5 | 5 | 126,000 |
| 2026-03-01 | 755,000 | 5 | 5 | 151,000 |
월 단위로 볼 때는 문제가 없습니다. 이제 "1분기 전체"가 궁금해져 이 뷰를 다시 집계합니다.
SELECT SUM(total_amount), SUM(order_count), SUM(customer_count), AVG(avg_order_value)
FROM monthly_summary;
| total_amount | order_count | customer_count | avg_order_value |
|---|---|---|---|
| 1,690,000 | 14 | 13 | 117,750 |
주문금액과 주문수는 맞지만, 고객수와 객단가가 틀렸습니다.
- 고객수: 실제 고객은 6명입니다. 매달 주문한 고객을 달마다 한 번씩 세어 3 + 5 + 5 = 13이 되었습니다.
- 객단가: 실제 객단가는 1,690,000 ÷ 14 = 120,714원입니다. 월별 객단가의 단순 평균은 주문 수가 다른 달을 같은 무게로 섞으므로 다른 값이 나옵니다.
게다가 이 뷰에는 주문 상태가 없어서 "상태별 주문금액"은 아예 물을 수 없습니다. 실제로 order_status로 묶으면 UNRESOLVED_COLUMN 오류가 납니다. 새로운 단위가 필요할 때마다 요약 뷰를 하나씩 더 만들어야 하는 구조입니다.
4.2 Metric View 정의하기
같은 지표를 Metric View로 정의합니다. CREATE VIEW ... WITH METRICS LANGUAGE YAML 뒤에 $$로 감싼 YAML을 적습니다.
CREATE OR REPLACE VIEW orders_metrics
WITH METRICS
LANGUAGE YAML
AS $$
version: 1.1
comment: "도토리마켓 주문 지표 (단일 테이블)"
source: <catalog>.metric_view_demo.fact_orders
dimensions:
- name: order_month
display_name: 주문월
expr: "CAST(DATE_TRUNC('MONTH', order_date) AS DATE)"
- name: order_status
display_name: 주문상태
expr: status
measures:
- name: total_amount
display_name: 주문금액
expr: SUM(quantity * unit_price)
- name: order_count
display_name: 주문수
expr: COUNT(1)
- name: customer_count
display_name: 고객수
expr: COUNT(DISTINCT customer_id)
- name: avg_order_value
display_name: 객단가
expr: MEASURE(total_amount) / MEASURE(order_count)
$$;
YAML의 각 부분이 1장의 개념 중 어디에 해당하는지 정리하면 다음과 같습니다.
| YAML | 개념 | 의미 |
|---|---|---|
source: ...fact_orders |
팩트 테이블 | 메저를 계산할 원본 행 |
dimensions → order_month |
디멘전 | 주문일을 월 단위로 자른 값으로 나눔 |
dimensions → order_status |
디멘전 | 완료/취소로 나눔 |
measures → total_amount |
메저 (합계) | 수량 × 단가의 합 |
measures → customer_count |
메저 (고유 개수) | 중복 없는 고객 수 |
measures → avg_order_value |
메저 (비율) | 다른 메저를 조합: 주문금액 ÷ 주문수 |
눈여겨볼 점이 네 가지 있습니다.
GROUP BY가 없습니다. 어떤 단위로 나눌지는 조회하는 사람이 정합니다.- 객단가를
AVG가 아닌MEASURE(total_amount) / MEASURE(order_count)로 정의했습니다. 어떤 단위로 조회하든 "그 단위의 주문금액 ÷ 그 단위의 주문수"로 계산되므로 항상 정확합니다. MEASURE()는 두 곳에서 씁니다. 정의할 때는 객단가처럼 다른 메저를 조합하는 데 쓰고, 조회할 때는SELECT MEASURE(total_amount)처럼 메저 값을 꺼내는 데 씁니다.- YAML 문법과 겹치는 식은 큰따옴표로 감쌉니다. 식 안에
:(콜론+공백)이 있으면 YAML이 키 구분자로 해석합니다. 예를 들어CASE WHEN status = '완료' THEN '상태: 완료' END를 그대로 쓰면METRIC_VIEW_INVALID_VIEW_DEFINITION오류가 납니다. 실습의DATE_TRUNC(...)식은 감싸지 않아도 되지만, 식을 늘 큰따옴표로 감싸 두면 이런 실수를 피할 수 있습니다. 감싸는 대상은expr:값 전체이고, SQL 문자열의 작은따옴표는 그대로 둡니다.
expr: CASE WHEN status = '완료' THEN '상태: 완료' END # 오류
expr: "CASE WHEN status = '완료' THEN '상태: 완료' END" # 정상
💡 이름은 영문, 화면 표시는 한글로.
name에 한글을 바로 쓰면 SQL에서 매번 백틱으로 감싸야 합니다(MEASURE(`주문금액`)). 감싸지 않으면INVALID_IDENTIFIER오류가 납니다. 실습에서는name을 영문 snake_case로 짓고, AI/BI 대시보드·Genie에 보일 한글 이름은display_name에 적었습니다.
DESCRIBE TABLE EXTENDED orders_metrics로 확인하면 Type이 METRIC_VIEW이고, 메저 컬럼의 타입이 bigint measure·double measure처럼 measure로 표시됩니다.
4.3 MEASURE()로 조회하기
Metric View는 두 가지 규칙으로 조회합니다.
- 디멘전은 일반 컬럼처럼
SELECT에 적습니다. - 메저는 반드시
MEASURE(메저이름)로 감쌉니다. - 디멘전과 함께 조회하면
GROUP BY가 필요합니다.GROUP BY order_month처럼 적어도 되지만, 고른 디멘전을 모두 묶는GROUP BY ALL이 간편합니다.
월별로 조회하기
SELECT order_month,
MEASURE(total_amount) AS total_amount,
MEASURE(order_count) AS order_count,
MEASURE(customer_count) AS customer_count,
MEASURE(avg_order_value) AS avg_order_value
FROM orders_metrics
GROUP BY ALL
ORDER BY ALL;
| order_month | total_amount | order_count | customer_count | avg_order_value |
|---|---|---|---|---|
| 2026-01-01 | 305,000 | 4 | 3 | 76,250 |
| 2026-02-01 | 630,000 | 5 | 5 | 126,000 |
| 2026-03-01 | 755,000 | 5 | 5 | 151,000 |
일반 뷰의 월별 결과와 같습니다. 차이는 단위를 바꿀 때 드러납니다.
전체(분기)로 조회하기 — 디멘전을 빼기만 합니다.
SELECT MEASURE(total_amount), MEASURE(order_count),
MEASURE(customer_count), MEASURE(avg_order_value)
FROM orders_metrics;
| total_amount | order_count | customer_count | avg_order_value |
|---|---|---|---|
| 1,690,000 | 14 | 6 | 120,714.29 |
고객수 6명, 객단가 120,714원으로 정확합니다. 월별 결과를 다시 더한 것이 아니라 14개 원본 행에서 바로 계산했기 때문입니다.
주문상태별로 조회하기 — 일반 뷰로는 물을 수 없던 질문입니다.
SELECT order_status, MEASURE(total_amount), MEASURE(order_count),
MEASURE(customer_count), MEASURE(avg_order_value)
FROM orders_metrics
GROUP BY ALL;
| order_status | total_amount | order_count | customer_count | avg_order_value |
|---|---|---|---|---|
| 완료 | 1,435,000 | 11 | 6 | 130,454.55 |
| 취소 | 255,000 | 3 | 3 | 85,000 |
월 × 상태로 조회하기 — 디멘전을 두 개 고르면 두 기준으로 동시에 나뉩니다.
| order_month | order_status | total_amount | order_count | customer_count | avg_order_value |
|---|---|---|---|---|---|
| 2026-01-01 | 완료 | 280,000 | 3 | 3 | 93,333.33 |
| 2026-01-01 | 취소 | 25,000 | 1 | 1 | 25,000 |
| 2026-02-01 | 완료 | 480,000 | 4 | 4 | 120,000 |
| 2026-02-01 | 취소 | 150,000 | 1 | 1 | 150,000 |
| 2026-03-01 | 완료 | 675,000 | 4 | 4 | 168,750 |
| 2026-03-01 | 취소 | 80,000 | 1 | 1 | 80,000 |
Metric View 하나로 네 가지 단위의 질문에 모두 답했습니다. 일반 뷰였다면 요약 뷰가 네 개 필요했고, 그중 두 개는 고객수와 객단가를 틀리게 보여 줬을 것입니다.
4.4 조회할 때 지켜야 할 규칙
Metric View를 처음 조회할 때 자주 만나는 오류를 실습 환경에서 직접 재현했습니다.
| 시도 | 결과 | 바른 방법 |
|---|---|---|
SELECT * FROM orders_metrics |
METRIC_VIEW_MISSING_MEASURE_FUNCTION |
디멘전과 MEASURE(...)를 명시 |
SELECT order_month, total_amount ... |
METRIC_VIEW_MISSING_MEASURE_FUNCTION |
메저는 MEASURE(total_amount) |
디멘전 + MEASURE()인데 GROUP BY 없음 |
MISSING_GROUP_BY |
GROUP BY ALL 추가 |
WHERE MEASURE(total_amount) > 500000 |
오류 | 메저 조건은 HAVING에 |
SELECT SUM(quantity) ... (원본 컬럼) |
UNRESOLVED_COLUMN |
정의한 디멘전·메저만 조회 가능 |
디멘전 조건은 WHERE에, 메저 조건은 HAVING에 둡니다.
-- 완료 주문 기준, 주문금액이 50만 원을 넘는 달
SELECT order_month, MEASURE(total_amount) AS total_amount
FROM orders_metrics
WHERE order_status = '완료' -- 디멘전 조건
GROUP BY ALL
HAVING MEASURE(total_amount) > 500000; -- 메저 조건
| order_month | total_amount |
|---|---|
| 2026-03-01 | 675,000 |
5. 스타 스키마와 고급 메저로 확장하기
이제 디멘전 테이블을 붙이고, 실무에서 자주 쓰는 메저 유형을 더합니다. 아래가 5장 전체에서 쓰는 sales_metrics 정의입니다. 각 부분은 이어지는 절에서 하나씩 설명합니다.
CREATE OR REPLACE VIEW sales_metrics
WITH METRICS
LANGUAGE YAML
AS $$
version: 1.1
comment: "도토리마켓 판매 지표 (스타 스키마)"
source: <catalog>.metric_view_demo.fact_orders
joins: # 5.1 디멘전 테이블 조인
- name: dim_customer
source: <catalog>.metric_view_demo.dim_customer
'on': source.customer_id = dim_customer.customer_id
- name: dim_product
source: <catalog>.metric_view_demo.dim_product
'on': source.product_id = dim_product.product_id
dimensions:
- name: order_month
display_name: 주문월
expr: "CAST(DATE_TRUNC('MONTH', source.order_date) AS DATE)"
- name: order_status
display_name: 주문상태
expr: source.status
- name: region
display_name: 지역
synonyms: [권역, 시도] # 5.5 메타데이터
expr: dim_customer.region
- name: customer_grade
display_name: 고객등급
expr: dim_customer.grade
- name: category
display_name: 카테고리
expr: dim_product.category
measures:
- name: total_amount
display_name: 주문금액
expr: SUM(source.quantity * source.unit_price)
- name: order_count
display_name: 주문수
expr: COUNT(1)
- name: customer_count
display_name: 고객수
expr: COUNT(DISTINCT source.customer_id)
- name: completed_amount # 5.2 필터 메저
display_name: 매출
synonyms: [매출액, 판매액, 실적]
comment: "취소를 제외한 완료 주문 금액"
expr: SUM(source.quantity * source.unit_price) FILTER (WHERE source.status = '완료')
- name: canceled_count
display_name: 취소주문수
expr: COUNT(1) FILTER (WHERE source.status = '취소')
- name: cancel_rate # 5.3 비율 메저
display_name: 취소율
comment: "전체 주문 중 취소 주문 비율 (0~1)"
format:
type: percentage
decimal_places: {type: exact, places: 1}
expr: MEASURE(canceled_count) / MEASURE(order_count)
- name: avg_order_value
display_name: 객단가
expr: MEASURE(total_amount) / MEASURE(order_count)
- name: cumulative_sales # 5.4 window 메저
display_name: 누적매출
expr: SUM(source.quantity * source.unit_price) FILTER (WHERE source.status = '완료')
window:
- order: order_month
range: cumulative
semiadditive: last
- name: prev_month_sales
display_name: 전월매출
expr: SUM(source.quantity * source.unit_price) FILTER (WHERE source.status = '완료')
window:
- order: order_month
range: trailing 1 month
semiadditive: last
- name: mom_growth
display_name: 전월대비증감률
expr: (MEASURE(completed_amount) - MEASURE(prev_month_sales)) / MEASURE(prev_month_sales)
$$;
5.1 디멘전 테이블 조인
joins에 디멘전 테이블을 나열하면 앞서 본 스타 스키마가 그대로 Metric View에 들어옵니다.
| 항목 | 작성법 | 주의 |
|---|---|---|
| 팩트 테이블 참조 | source.컬럼 |
조인이 있으면 팩트 컬럼에도 source.를 붙이면 읽기 쉬움 |
| 디멘전 참조 | 조인이름.컬럼 (예: dim_customer.region) |
조인 이름이 팩트 컬럼명의 접두어가 되지 않게 (dim_ 접두어 권장) |
| 조인 조건 | 'on': source.fk = 조인이름.pk |
on은 YAML에서 불리언 true로 해석될 수 있어 따옴표 필수 |
| 관계 | 팩트 N : 디멘전 1 | N:N이면 엔진이 첫 번째 일치 행만 사용해 의도와 다른 결과가 나옴 → N:1로 설계 |
조인을 정의해 두면 조회하는 사람은 조인을 의식할 필요가 없습니다. 상품 테이블의 category와 고객 테이블의 customer_grade처럼 서로 다른 디멘전 테이블의 디멘전도 고르기만 하면 됩니다.
SELECT category, customer_grade,
MEASURE(completed_amount) AS completed_amount,
MEASURE(customer_count) AS customer_count
FROM sales_metrics
GROUP BY ALL
ORDER BY ALL;
| category | customer_grade | completed_amount | customer_count |
|---|---|---|---|
| 생활 | VIP | 50,000 | 2 |
| 생활 | 일반 | 175,000 | 3 |
| 전자 | VIP | 750,000 | 2 |
| 전자 | 일반 | 300,000 | 2 |
| 패션 | VIP | 80,000 | 1 |
| 패션 | 일반 | 80,000 | 2 |
상품 디멘전(카테고리)과 고객 디멘전(등급)을 한 번에 교차했지만, 쿼리에는 JOIN이 한 줄도 없습니다.
ℹ️ 이 표의 두 메저는 기준이 다릅니다.
completed_amount(매출)는 완료 주문만,customer_count(고객수)는 취소 주문까지 셉니다. 그래서 "생활 × VIP"는 매출이 50,000원(C04의 완료 주문)인데 고객은 2명(C04와 취소 주문만 있는C01)입니다. 완료 주문 고객만 세려면COUNT(DISTINCT source.customer_id) FILTER (WHERE source.status = '완료')같은 메저를 따로 정의합니다.
5.2 필터 메저: 완료 주문 매출
"매출"은 보통 취소를 뺀 금액입니다. 조회할 때마다 WHERE status = '완료'를 붙이게 하면 누군가는 빠뜨립니다. SQL의 FILTER (WHERE ...) 절로 조건을 메저 정의 안에 넣습니다.
- name: completed_amount
display_name: 매출
expr: SUM(source.quantity * source.unit_price) FILTER (WHERE source.status = '완료')
필터 메저는 조건이 메저 안에 있으므로 조건이 서로 다른 메저 여러 개를 한 쿼리에서 나란히 볼 수 있습니다.
SELECT region,
MEASURE(total_amount) AS total_amount, -- 전체 주문금액
MEASURE(completed_amount) AS completed_amount, -- 완료 주문만
MEASURE(order_count) AS order_count,
MEASURE(canceled_count) AS canceled_count, -- 취소 주문만
MEASURE(cancel_rate) AS cancel_rate
FROM sales_metrics
GROUP BY ALL
ORDER BY completed_amount DESC;
| region | total_amount | completed_amount | order_count | canceled_count | cancel_rate |
|---|---|---|---|---|---|
| 부산 | 730,000 | 730,000 | 4 | 0 | 0.0 |
| 서울 | 780,000 | 605,000 | 7 | 2 | 0.2857 |
| 대구 | 180,000 | 100,000 | 3 | 1 | 0.3333 |
5.3 비율 메저: 취소율
취소율은 MEASURE()로 다른 메저를 조합해 정의합니다. 객단가와 같은 방식입니다.
- name: cancel_rate
expr: MEASURE(canceled_count) / MEASURE(order_count)
위 표에서 지역별 취소율은 0%, 28.6%, 33.3%입니다. 전체 취소율을 구할 때 이 셋을 평균하면 (0 + 0.2857 + 0.3333) ÷ 3 = 20.6%가 나옵니다. Metric View에서 디멘전 없이 조회하면 다음과 같습니다.
SELECT MEASURE(cancel_rate) FROM sales_metrics; -- 0.2142857...
정답은 취소 3건 ÷ 전체 14건 = 21.4%입니다. 지역마다 주문 수가 다른데 단순 평균은 그 차이를 무시하기 때문에 값이 어긋납니다. 비율 메저를 분자 메저 ÷ 분모 메저로 정의해 두면 어느 단위로 조회하든 이런 실수가 생기지 않습니다.
| 비율 메저 정의 방식 | 예 | 재집계 안전성 |
|---|---|---|
| 한 식에 직접 작성 | COUNT(1) FILTER (WHERE ...) * 1.0 / COUNT(1) |
안전 |
기본 메저를 MEASURE()로 조합 |
MEASURE(canceled_count) / MEASURE(order_count) |
안전 + 분자·분모를 다른 지표에서도 재사용 |
5.4 window 메저: 누적 매출과 전월 대비
누적 합계나 전월 대비처럼 다른 기간의 값을 참조하는 지표는 window 블록으로 정의합니다.
- name: cumulative_sales
expr: SUM(...) FILTER (WHERE source.status = '완료')
window:
- order: order_month # 어떤 디멘전 순서로
range: cumulative # 처음부터 현재까지
semiadditive: last # order_month 없이 조회하면 마지막 값을 사용
| 필드 | 의미 | 실습 값 |
|---|---|---|
order |
창(window)을 정렬할 디멘전 | order_month |
range |
창의 범위 | cumulative: 처음 ~ 현재trailing 1 month: 현재를 제외한 직전 1개월current: 현재 기간만leading N 단위: 현재 이후 N개 단위all: 전체 기간 |
semiadditive |
order 디멘전 없이 조회할 때 쓸 값 |
last: 마지막 기간 값 / first: 첫 기간 값 |
prev_month_sales(전월매출)는 trailing 1 month로 직전 달 값을 가져옵니다. mom_growth(전월대비증감률)는 window 없이 MEASURE()로 두 메저를 조합합니다.
월별로 조회하기
SELECT order_month,
MEASURE(completed_amount) AS completed_amount,
MEASURE(cumulative_sales) AS cumulative_sales,
MEASURE(prev_month_sales) AS prev_month_sales,
MEASURE(mom_growth) AS mom_growth
FROM sales_metrics
GROUP BY ALL
ORDER BY ALL;
| order_month | completed_amount | cumulative_sales | prev_month_sales | mom_growth |
|---|---|---|---|---|
| 2026-01-01 | 280,000 | 280,000 | NULL | NULL |
| 2026-02-01 | 480,000 | 760,000 | 280,000 | 0.714 |
| 2026-03-01 | 675,000 | 1,435,000 | 480,000 | 0.406 |
- 누적매출은 280,000 → 280,000 + 480,000 → 그 합 + 675,000으로 쌓입니다.
- 1월은 전월 데이터가 없어 전월매출과 증감률이
NULL입니다. - 2월 증감률 = (480,000 − 280,000) ÷ 280,000 = 71.4%.
다른 디멘전을 함께 고르면 그 디멘전별로 따로 누적됩니다.
SELECT order_month, region,
MEASURE(completed_amount) AS completed_amount,
MEASURE(cumulative_sales) AS cumulative_sales
FROM sales_metrics
GROUP BY ALL
ORDER BY ALL;
| order_month | region | completed_amount | cumulative_sales |
|---|---|---|---|
| 2026-01-01 | 부산 | 80,000 | 80,000 |
| 2026-01-01 | 서울 | 200,000 | 200,000 |
| 2026-02-01 | 대구 | 100,000 | 100,000 |
| 2026-02-01 | 부산 | 300,000 | 380,000 |
| 2026-02-01 | 서울 | 80,000 | 280,000 |
| 2026-03-01 | 대구 | NULL | 100,000 |
| 2026-03-01 | 부산 | 350,000 | 730,000 |
| 2026-03-01 | 서울 | 325,000 | 605,000 |
3월 대구 행을 보면 window 메저의 동작이 잘 드러납니다. 대구의 3월 주문은 취소 1건뿐이라 그달 매출은 NULL이지만, 누적매출은 2월까지 쌓인 100,000원을 그대로 보여 줍니다.
order_month 없이 조회하면 semiadditive가 적용됩니다.
SELECT region, MEASURE(completed_amount), MEASURE(cumulative_sales)
FROM sales_metrics
GROUP BY ALL;
| region | completed_amount | cumulative_sales |
|---|---|---|
| 대구 | 100,000 | 100,000 |
| 부산 | 730,000 | 730,000 |
| 서울 | 605,000 | 605,000 |
월 디멘전이 없으면 누적매출을 월별로 합치지 않고, semiadditive: last에 따라 마지막 달의 누적값을 씁니다. 잔액·재고처럼 기간을 더하면 안 되는 지표(semi-additive)에 쓰는 장치입니다.
💡 공식 문서에 따르면
trailing 3처럼 단위 없이 숫자 범위를 쓰려면order디멘전이 연속된 정수여야 합니다. 실습처럼 날짜 디멘전에month·day단위를 붙이면 이 제약을 신경 쓰지 않아도 됩니다.
5.5 사람과 AI를 위한 메타데이터
Metric View의 디멘전과 메저에는 계산식 외에 설명 정보를 붙일 수 있습니다. AI/BI 대시보드와 Genie가 이 정보를 사용합니다.
| 필드 | 용도 | 실습 예 |
|---|---|---|
comment |
지표 설명 | completed_amount: "취소를 제외한 완료 주문 금액" |
display_name |
화면 표시 이름 | completed_amount → 매출 |
synonyms |
다른 표현 (최대 10개). Genie가 질문 속 표현을 지표와 연결할 때 사용 | 매출액, 판매액, 실적 |
format |
표시 형식 (type 필수) |
cancel_rate: 백분율, 소수점 1자리 |
5.6 materialization
데이터가 많아져 조회가 느려지면 materialization 설정으로 자주 쓰는 집계를 미리 계산해 둘 수 있습니다. 이 글의 실습 범위는 아니므로 개념만 소개합니다.
| 유형 | 미리 계산하는 것 | 적합한 경우 |
|---|---|---|
aggregated |
지정한 디멘전·메저 조합의 집계 결과 | 자주 조회하는 조합이 정해져 있을 때 |
unaggregated |
조인·필터까지 적용한 원본 수준 데이터 | 조인이 많거나 원본 뷰가 무거울 때 |
version: 1.1
source: <catalog>.metric_view_demo.fact_orders
joins: ... # 5장 정의 그대로
dimensions: ... # 5장 정의 그대로
measures: ... # 5장 정의 그대로
materialization:
schedule: every 1 hour # 갱신 주기
mode: relaxed
materialized_views:
- name: monthly_region # 자주 쓰는 조합만 미리 집계
type: aggregated
dimensions: [order_month, region]
measures: [total_amount, completed_amount, order_count]
- name: full_model # 조인까지 적용한 원본 수준 데이터
type: unaggregated
cluster_by:
auto: true
공식 문서에 따르면 조회 SQL을 바꿀 필요는 없습니다. 엔진이 쿼리에 맞는 사전 집계를 자동으로 고릅니다. 갱신은 관리형 Lakeflow 파이프라인이 일정에 따라 수행하며(가능하면 증분 갱신), 파이프라인 사용 요금이 발생합니다. Databricks Runtime 17.3 이상과 서버리스 파이프라인이 필요하고, 행 수준 보안·컬럼 마스크가 걸린 Metric View에는 쓸 수 없습니다.
6. Catalog Explorer·AI/BI 대시보드·Genie에서 활용하기
5장에서 만든 sales_metrics 정의 하나를 Catalog Explorer와 편집기에서 살펴보고, AI/BI 대시보드와 Genie에서 그대로 씁니다.
6.1 Catalog Explorer
Catalog Explorer에서 Metric View를 열면 일반 테이블과 달리 Measures와 Fields(디멘전)가 나뉘어 표시됩니다.

- 각 항목은
display_name(예: 주문금액)이 위에,name(예:total_amount)이 아래에 표시됩니다. comment를 적은 매출(completed_amount)에는 설명이 함께 나옵니다.- 화면 폭이 넓으면 오른쪽 정보 패널에 Source(
fact_orders | (+ 2 joins)처럼 원본 테이블과 조인 수)와 Related assets(이 Metric View를 쓰는 Genie Agent)도 표시됩니다.
6.2 Metric View 편집기
상단의 Edit를 누르면 Metric View 편집기가 열립니다. UI 모드에서는 Source·조인·메저·필드를 목록으로 보고 추가·수정할 수 있습니다.

오른쪽 Preview에는 원본 행 단위의 디멘전 값이 표시되고, 메저 칸은 -로 비어 있습니다. 메저 값은 디멘전으로 묶어 조회할 때 계산된다는 점과 같은 맥락입니다.
<> 버튼으로 전환하면 YAML 원문을 직접 편집할 수 있습니다. SQL로 만든 정의가 그대로 보입니다.

ℹ️ 편집기 오른쪽에는 Genie Code 패널이 함께 열려, 자연어로 조인이나 메저를 추가하도록 요청할 수도 있습니다. 실습에서는 사용하지 않았습니다.
6.3 AI/BI 대시보드
대시보드 데이터셋에서 Metric View를 MEASURE()로 조회하면 됩니다. 실습 대시보드에는 데이터셋 두 개를 만들었습니다.
-- 데이터셋 1: 월별 매출
SELECT order_month,
MEASURE(completed_amount) AS completed_amount,
MEASURE(cumulative_sales) AS cumulative_sales
FROM <catalog>.metric_view_demo.sales_metrics
GROUP BY ALL ORDER BY ALL;
-- 데이터셋 2: 지역별 매출
SELECT region,
MEASURE(completed_amount) AS completed_amount,
MEASURE(cancel_rate) AS cancel_rate
FROM <catalog>.metric_view_demo.sales_metrics
GROUP BY ALL;

총 매출 143.5만 원, 지역별 매출(부산 > 서울 > 대구), 월별 매출과 누적 매출 추이가 5장의 SQL 결과와 같습니다. 대시보드 작성자는 매출에서 취소를 빼야 한다는 사실이나 누적 계산 방법을 몰라도 됩니다. 공식은 이미 Metric View에 들어 있습니다.
6.4 Genie
Genie Agent를 만들 때 데이터 소스로 sales_metrics를 추가하고 질문했습니다.
지역별 매출액과 취소율을 알려줘

Genie가 생성한 SQL(Show code)은 다음과 같습니다.
SELECT `region`,
MEASURE(`completed_amount`) AS `completed_amount`,
MEASURE(`cancel_rate`) AS `cancel_rate`
FROM `<catalog>`.`metric_view_demo`.`sales_metrics`
WHERE `region` IS NOT NULL
GROUP BY ALL
ORDER BY `completed_amount` DESC
- 질문의 "매출액"이
total_amount(주문금액)가 아니라 동의어로 등록된completed_amount(매출)로 연결되었습니다. - 취소율은 Genie가 직접 나눗셈을 만들지 않고 정의된
MEASURE(cancel_rate)를 그대로 썼습니다. - 답변 수치(부산 730,000원·0%, 서울 605,000원·28.6%, 대구 100,000원·33.3%)는 5.2절의 결과와 같습니다.
Genie가 테이블을 직접 조회하면 "매출"을 계산할 때마다 취소 포함 여부를 추측해야 합니다. Metric View를 소스로 주면 Genie는 정의된 지표를 고르는 일만 하게 되어 답이 일관됩니다.
7. 실습 객체 삭제
실습이 끝나면 아래 순서로 정리합니다. AI/BI 대시보드와 Genie Agent는 각 화면의 메뉴에서 삭제합니다.
DROP VIEW IF EXISTS sales_metrics;
DROP VIEW IF EXISTS orders_metrics;
DROP VIEW IF EXISTS monthly_summary;
DROP TABLE IF EXISTS fact_orders;
DROP TABLE IF EXISTS dim_customer;
DROP TABLE IF EXISTS dim_product;
DROP SCHEMA IF EXISTS metric_view_demo;
정리
- 스타 스키마는 사건을 기록한 팩트 테이블과 그 사건을 설명하는 디멘전 테이블을 키로 연결한 구조입니다. 분석 질문은 메저(무엇을 셀까)와 디멘전(어떻게 나눌까)으로 나뉩니다.
- 일반 뷰는 만들 때 집계 단위가 고정되어, 요약 결과를 다시 합산하면 고유 개수와 비율 지표가 틀립니다(실습: 고객수 13 vs 6, 객단가 117,750 vs 120,714).
- Metric View는 집계 결과가 아니라 디멘전·메저의 정의를 보관하고, 조회할 때 고른 단위로 원본에서 계산합니다. 조회는
MEASURE()와GROUP BY ALL로 합니다. joins로 스타 스키마를,FILTER로 조건부 메저를,MEASURE()조합으로 비율을,window로 누적·전월 대비를 정의할 수 있습니다.- 한 번 정의한 지표를 Catalog Explorer, AI/BI 대시보드, Genie가 그대로 씁니다.
display_name·synonyms·comment는 사람과 AI가 지표를 정확히 고르게 돕습니다.
참고
'Databricks > Semantic Layer' 카테고리의 다른 글
| OntoBricks로 이해하는 온톨로지 ③ 구성 — 설정부터 공개까지 (0) | 2026.10.15 |
|---|---|
| OntoBricks로 이해하는 온톨로지 ② 이해 — OntoBricks란 (0) | 2026.10.15 |
| OntoBricks로 이해하는 온톨로지 ① 개념 — 온톨로지와 지식 그래프 첫걸음 (0) | 2026.10.15 |
| OntoBricks로 이해하는 온톨로지 ④ 데모 — GraphQL·자연어·MCP 질의 (0) | 2026.10.15 |
댓글