Spark SQL을 활용한 데이터 분석 실무에서는 단순한 테이블 조회를 넘어서 복잡한 구조의 데이터를 다루는 일이 자주 발생합니다. 이번 글에서는 Databricks 강의 중 5 - Complex Transformations 섹션을 바탕으로 JSON 파싱, 배열 처리, 구조체 해제, 조인, 피벗 테이블 등 Spark SQL에서 자주 활용되는 고급 기능들을 체계적으로 정리합니다.
🧷 0. Nested JSON 이란?
Nested JSON은 JSON 데이터 내부에 또 다른 JSON 객체(혹은 배열)가 중첩된 구조를 말합니다.
예를 들어, 다음은 Nested JSON의 전형적인 예입니다:
{ "user_id": "123", "device": "Linux", "ecommerce": { "purchase_revenue_in_usd": 1075.5, "items": [ { "item_id": "M_STAN_K", "quantity": 1 } ] } }
- ecommerce는 또 다른 JSON 구조(= STRUCT)입니다.
- items는 배열 안에 STRUCT들이 들어있는 형태입니다.
👉 이처럼 한 컬럼 안에 복합적인 데이터 구조가 들어있다면 일반 SQL처럼 단순히 조회할 수 없습니다. Spark SQL에서는 . 또는 : 같은 특수 문법을 사용해 이러한 구조를 정제(flatten) 할 수 있습니다.
🔹 1. "." 및 ":" 구문의 차이와 사용법
✅ . 구문
- 대상: STRUCT 타입
- 설명: 구조체 내부의 필드를 접근할 때 사용
- 예시:
SELECT ecommerce.purchase_revenue_in_usd FROM parsed_events;
parsed_events 테이블에서 ecommerce가 STRUCT 타입인 경우, 내부 필드를 .으로 접근합니다.
✅ : 구문
- 대상: JSON 문자열(JSON string)
- 설명: 문자열로 저장된 JSON에서 key에 직접 접근할 때 사용
- 예시:
SELECT value:event_name FROM events_strings WHERE value:event_name = "finalize";
🧩 2. JSON 문자열 → STRUCT로 파싱
Spark SQL은 from_json() 함수를 통해 문자열(JSON)을 STRUCT로 파싱할 수 있습니다. 먼저 스키마를 추출해야 합니다.
스키마 추출 및 적용
SELECT schema_of_json('{ ... JSON 예시 ... }') AS schema;
이후 파싱:
CREATE OR REPLACE TEMP VIEW parsed_events AS SELECT from_json(value, '<복잡한 STRUCT 스키마>') AS json FROM events_strings;
파싱된 STRUCT는 json.*으로 해제 가능합니다.
🔄 3. 배열 다루기 (explode / size)
Spark SQL에서 배열 컬럼을 다룰 때는 다음과 같은 함수들을 사용합니다.
explode(array) | 배열을 행으로 펼침 |
size(array) | 배열 길이 반환 |
CREATE OR REPLACE TEMP VIEW exploded_events AS SELECT *, explode(items) AS item FROM parsed_events; SELECT * FROM exploded_events WHERE size(items) > 2;
🧵 4. 중첩 함수 조합 (collect_set, flatten, array_distinct)
특정 사용자의 장바구니 이력을 수집하려면 collect_set() 등 여러 함수를 함께 사용합니다.
SELECT user_id, array_distinct(flatten(collect_set(items.item_id))) AS cart_history FROM exploded_events GROUP BY user_id ORDER BY user_id;
- collect_set() : 중복 없이 아이템 수집
- flatten() : 중첩 배열 평탄화
- array_distinct() : 최종 중복 제거
📌 event_name 이력까지 보고 싶다면:
SELECT user_id, collect_set(event_name) AS event_history, array_distinct(flatten(collect_set(items.item_id))) AS cart_history FROM exploded_events GROUP BY user_id ORDER BY user_id;
🔗 5. 테이블 조인 (Join)
Spark SQL은 SQL 표준의 JOIN을 모두 지원합니다 (INNER, LEFT, RIGHT 등).
CREATE OR REPLACE TEMP VIEW item_purchases AS SELECT * FROM (SELECT *, explode(items) AS item FROM sales) a INNER JOIN item_lookup b ON a.item.item_id = b.item_id;
아이템 정보와 매핑되는 이름 또는 추가 데이터를 조인할 때 유용합니다.
📊 6. 피벗 테이블 (Pivot)
PIVOT 구문은 행 데이터를 열로 변형해 집계 형태로 보여줄 수 있도록 합니다.
사용 예시
SELECT * FROM item_purchases PIVOT ( sum(item.quantity) FOR item_id IN ( 'P_FOAM_K', 'M_STAN_Q', 'M_STAN_K', ... ) );
- sum(item.quantity) : 집계 함수
- FOR item_id : 어떤 값을 기준으로 피벗할지 지정
- IN (...) : 열로 만들 항목들 지정
이 결과는 각 아이템 ID 별 구매 수량을 컬럼으로 정리한 테이블을 생성합니다.
🧠 마무리 요약
JSON 파싱 | schema_of_json, from_json | 문자열 → STRUCT 변환 |
구조체 필드 접근 | . | STRUCT 내부 접근 |
JSON 키 접근 | : | JSON 문자열 내부 접근 |
배열 분해 | explode, size | 배열을 행으로, 길이 확인 |
이력 수집 | collect_set, flatten, array_distinct | 배열 수집 및 정리 |
테이블 연결 | JOIN | 데이터셋 병합 |
요약 분석 | PIVOT | 행 → 열 변환하여 집계 |