Databricks - Spark SQL 1

no-glass-otacku·2026년 6월 18일

MS data school

목록 보기
18/25

Databricks 데이터 수집과 테이블 다루기

Spark SQL로 파일 직접 쿼리하는 것부터 Delta 테이블 생성, 외부 소스 연결, 쓰기 방법까지 정리


읽기 전에 — 가장 중요한 개념 구분

이걸 모르면 수업 내내 헷갈린다.

Databricks의 테이블 종류

① 외부 테이블 (= 외부 소스 테이블)
   → 데이터는 밖(CSV, JSON, 외부 DB 등)에 그대로
   → Databricks는 "저기 가면 데이터 있어"라는 정보만 저장
   → USING + LOCATION 키워드가 있으면 외부 테이블

② Delta 테이블
   → 데이터가 Databricks 안으로 들어온 상태
   → 버전 관리, 빠른 쿼리, 자동 최신화 등 모든 기능 사용 가능
   → CTAS (CREATE TABLE AS SELECT) 로 만드는 게 일반적

"JSON 파일에서 외부 테이블을 생성하라" = 데이터는 JSON 파일에 그대로 두고, 그 파일을 테이블처럼 읽을 수 있도록 Databricks에 등록하라는 뜻. Delta 테이블 아님.


Delta 테이블 vs 외부 테이블 vs JDBC

코드로 구분하기

-- 관리형 Delta 테이블
-- USING도 없고 LOCATION도 없음
CREATE TABLE item_lookup AS
SELECT * FROM parquet.`/path`

-- 외부 테이블 (파일)
-- USING + LOCATION 있음
CREATE TABLE sales_csv
USING CSV
OPTIONS (header="true", delimiter="|")
LOCATION "/path/to/file"

-- JDBC (외부 DB)
-- USING JDBC + url + dbtable
CREATE TABLE users_jdbc
USING JDBC
OPTIONS (
  url = "jdbc:mysql://서버주소:3306",
  dbtable = "users"
)

한눈에 비교

관리형 Delta외부 테이블JDBC
데이터 위치Databricks 안파일 (S3 등)외부 DB 서버
코드 특징USING/LOCATION 없음USING+LOCATIONUSING JDBC+url
최신 데이터자동REFRESH 필요자동
성능빠름보통DB 크기에 따라 다름

문제 풀 때 판단하는 순서

"어떤 테이블 만들어라" 문제가 나오면

1. "관리형" or "Delta" 언급 → USING/LOCATION 없는 CTAS
2. "외부" or 파일 경로 + 옵션 필요 → USING + LOCATION
3. DB 연결 (MySQL, PostgreSQL 등) → USING JDBC + OPTIONS

1. 파일 직접 쿼리하기 (테이블 없이 그냥 읽기)

Databricks에서는 테이블을 만들지 않아도 파일을 바로 쿼리할 수 있다.

-- 단일 파일 쿼리
SELECT * FROM json.`/path/to/001.json`

-- 디렉터리 전체 쿼리 (같은 형식/스키마인 경우)
SELECT * FROM json.`/path/to/directory`

-- Parquet도 동일하게
SELECT * FROM parquet.`/path/to/file`

경로는 반드시 백틱(`) 으로 감싸야 한다. 작은따옴표(')가 아님에 주의.

문법 구조: 파일형식.경로

json.`/path`      -- JSON 파일
parquet.`/path`   -- Parquet 파일
csv.`/path`       -- CSV 파일

뷰와 CTE로 더 편리하게 참조하기

-- 임시 뷰로 등록 (세션 끝나면 사라짐)
CREATE OR REPLACE TEMP VIEW kafka_events AS
SELECT * FROM json.`${DA.paths.kafka_events}`

-- CTE로 사용
WITH events AS (
  SELECT * FROM json.`/path/events`
)
SELECT * FROM events WHERE topic = 'clickstream'

raw 파일 내용 확인 (디버깅용)

-- 텍스트로 읽기
SELECT * FROM text.`/path/to/file`

-- 바이너리로 읽기 (파일 메타데이터 포함)
SELECT * FROM binaryFile.`/path/to/file`

2. 외부 소스 테이블 (= 외부 테이블)

외부 테이블이 뭔데? (≠Delta 테이블)

비유로 먼저 이해하자.

도서관 책 대출 시스템

책(데이터)은 도서관 서가(외부 경로)에 그대로 있음
대출 카드(외부 테이블 정의)만 시스템에 등록
"이 책은 3층 A구역 12번 선반에 있어" 라는 정보만 저장

→ 읽을 때마다 서가에서 꺼내서 읽음
→ 책 자체를 복사해서 들고오지 않음

"외부 테이블"이라는 걸 코드에서 어떻게 알아보나?

딱 두 가지 키워드가 있으면 외부 테이블이다.

CREATE TABLE sales_csv
  (order_id LONG, email STRING, ...)
USING CSV          -- ① Delta가 아닌 형식 (CSV, JSON, JDBC 등)
OPTIONS (
  header = "true",
  delimiter = "|"
)
LOCATION "/path"   -- ② 데이터가 외부 경로에 있음

반대로 Delta 테이블은 이렇게 생겼다:

-- USING도 없고 LOCATION도 없음
CREATE TABLE sales AS
SELECT * FROM parquet.`/path`
-- 데이터가 Databricks 안으로 들어옴

CSV 파일로 외부 테이블 만들기

CSV 파일을 직접 쿼리하면 이런 문제가 생긴다:

헤더가 데이터 행으로 들어옴
모든 컬럼이 하나로 합쳐짐  ← 구분자가 | 인데 Spark가 모름
마지막 컬럼이 잘림

그래서 옵션을 줘서 외부 테이블로 등록한다:

CREATE TABLE sales_csv
  (order_id LONG, email STRING, transactions_timestamp LONG,
   total_item_quantity INTEGER, purchase_revenue_in_usd DOUBLE,
   unique_items INTEGER, items STRING)
USING CSV
OPTIONS (
  header = "true",     -- 첫 행은 헤더야
  delimiter = "|"      -- | 로 구분돼 있어
)
LOCATION "${DA.paths.sales_csv}"

이렇게 하면 SELECT * FROM sales_csv 로 깔끔하게 읽을 수 있다.

왜 외부 소스를 써? 그냥 데이터 가져오면 되는 거 아니야?

현실에서 "그냥 가져오는 것"이 안 되는 경우가 많다:

상황이유
실시간으로 계속 바뀌는 데이터복사하는 순간 이미 낡은 데이터
10TB짜리 대용량 데이터복사 자체가 시간/비용 낭비
다른 팀 소유 데이터복사 권한이 없음
매일 새 파일이 쌓이는 경우LOCATION만 걸면 자동으로 읽힘

실무에서는 외부 소스로 원본 연결 → 가공 후 Delta로 저장 흐름이 일반적. 이게 메달리온 아키텍처의 Bronze 단계가 하는 일이다.

외부 테이블의 치명적 한계 — 캐시 문제

-- 외부에서 파일에 데이터 추가됨
SELECT COUNT(*) FROM sales_csv  -- → 숫자 그대로 (캐시 때문에 못 봄)

REFRESH TABLE sales_csv  -- 수동으로 캐시 갱신 필요

SELECT COUNT(*) FROM sales_csv  -- → 이제 최신 숫자

Delta 테이블이면 자동으로 최신 데이터를 읽지만, 외부 테이블은 수동 갱신이 필요하다.


3. JDBC — 외부 DB 연결

JDBC가 뭔데?

우리 집(Databricks)에서
이웃 집(MySQL, PostgreSQL, SQLite 등 외부 DB)에 있는 데이터를
전화선(JDBC 연결)으로 당겨와서 읽는 것

쉽게 말해 Databricks 밖에 있는 데이터베이스 서버에 연결하는 방법이다.

외부 DB가 왜 따로 있냐?

회사에서 이런 상황이 흔하다:

영업팀이 오래전부터 쓰던 Oracle DB
  → 주문 데이터, 고객 데이터가 거기 다 있음
  → Databricks로 이사오기엔 너무 크고 복잡함
  → 그냥 거기 두고 Databricks에서 연결해서 읽음

JDBC로 외부 DB 연결하기

CREATE TABLE users_jdbc
USING JDBC                          -- JDBC 방식으로 연결
OPTIONS (
  url = "jdbc:sqlite:/path/to/db",  -- DB 서버 주소
  dbtable = "users"                 -- 읽어올 테이블 이름
)

주소 형식: jdbc:DB종류://서버주소:포트

jdbc:mysql://192.168.0.1:3306       -- MySQL 연결
jdbc:postgresql://myserver:5432     -- PostgreSQL 연결
jdbc:sqlite:/path/to/file.db        -- SQLite 연결 (파일 기반)

만들고 나면 그냥 일반 테이블처럼 쓸 수 있다:

SELECT * FROM users_jdbc  -- 외부 DB에서 데이터 가져옴

단, 데이터가 Databricks 안으로 복사되는 게 아니라 쿼리할 때마다 외부 DB에서 가져오는 것이라 대용량이면 느릴 수 있다.


4. Delta 테이블 생성

CTAS (Create Table As Select)

"SELECT 결과물로 Delta 테이블 만들기"

CREATE OR REPLACE TABLE sales AS
SELECT * FROM parquet.`/path/sales-historical`

스키마(컬럼 구조)를 직접 안 써도 SELECT 결과에서 자동으로 추론한다.

Parquet, JSON처럼 스키마가 명확한 파일에서 특히 유용하다.

CSV처럼 옵션이 필요한 파일은 CTAS 전에 임시 뷰를 먼저 만들어야 한다:

-- 1단계: CSV 읽기 옵션을 담은 임시 뷰 생성
CREATE OR REPLACE TEMP VIEW sales_tmp
  (order_id LONG, email STRING, ...)
USING CSV
OPTIONS (path = "/path", header = "true", delimiter = "|");

-- 2단계: 임시 뷰로 Delta 테이블 생성
CREATE TABLE sales_delta AS
  SELECT * FROM sales_tmp;

CRAS vs INSERT OVERWRITE

-- CRAS: 스키마도 바꿀 수 있는 강력한 덮어쓰기
CREATE OR REPLACE TABLE events AS SELECT ...

-- INSERT OVERWRITE: 스키마 변경 불가, 더 안전한 덮어쓰기
INSERT OVERWRITE sales SELECT * FROM parquet.`/path`

생성된 열 (Generated Column)

CREATE OR REPLACE TABLE purchase_dates (
  id STRING,
  transaction_timestamp LONG,
  price STRING,
  date DATE GENERATED ALWAYS AS (
    cast(cast(transaction_timestamp/1e6 AS TIMESTAMP) AS DATE))
    COMMENT "generated based on transactions_timestamp column"
)

다른 열의 값을 기반으로 자동 계산되는 열. 삽입 시 직접 값을 안 써도 Delta가 알아서 채워준다.

테이블 제약 조건

-- 조건을 위반하는 데이터가 들어오면 쓰기 에러 발생
ALTER TABLE purchase_dates ADD CONSTRAINT valid_date CHECK (date > '2020-01-01');

Clone — 테이블 복제

Shallow Clone (얕은 복제)

CREATE OR REPLACE TABLE purchases_shallow SHALLOW CLONE purchases

비유:

도서관 책은 그대로 두고
"이 책은 3층 A구역 12번에 있어" 라는 메모(Delta Log)만 복사

읽을 때 → 메모 보고 원본 위치 찾아가서 읽음
수정할 때 → 원본은 건드리지 않고, 내 공간에 새 파일 생성
원본 삭제되면 → 메모는 있는데 책이 없으니 에러

수정해도 원본이 안 바뀌는 이유:
Spark는 파일을 절대 덮어쓰지 않는다. 수정 = 기존 파일 건드리는 게 아니라 새 파일을 만드는 것. 그 새 파일이 원본 경로가 아닌 Clone 자체 경로에 생기기 때문에 원본은 그대로다.

Deep Clone (깊은 복제)

CREATE OR REPLACE TABLE purchases_clone DEEP CLONE purchases

비유:

책을 통째로 복사해서 내 책상에 올려두기

원본이 어떻게 되든 내 복사본은 독립적으로 존재
원본 삭제돼도 내 복사본은 멀쩡함
단, 복사하는 데 시간이 오래 걸림

한눈에 비교

Shallow CloneDeep Clone
데이터 복사❌ 원본 참조✅ 완전 복사
속도빠름 (로그만 복사)느림 (데이터 전체 복사)
원본 삭제되면망가짐독립 유지
원본이 바뀌면영향 받을 수 있음영향 없음
용도테스트, 임시 실험완전한 백업, 마이그레이션

5. 테이블에 쓰기 — 4가지 방법

INSERT OVERWRITE — 전체 덮어쓰기

INSERT OVERWRITE sales
SELECT * FROM parquet.`/path`

기존 데이터를 통째로 교체. 스키마(컬럼 구조)가 바뀌면 에러남. 기존 테이블 구조를 지키면서 데이터만 갈아끼울 때.

INSERT INTO — 행 추가

INSERT INTO sales
SELECT * FROM parquet.`/path`

기존 데이터 유지하고 새 행만 추가. 중복 방지 기능 없음. 같은 셀 두 번 실행하면 데이터 두 번 들어감.

MERGE INTO — 똑똑한 병합

MERGE INTO users a
USING users_update b
ON a.user_id = b.user_id
WHEN MATCHED AND a.email IS NULL AND b.email IS NOT NULL THEN
  UPDATE SET email = b.email, updated = b.updated
WHEN NOT MATCHED THEN INSERT *

조건에 따라 UPDATE / INSERT / DELETE를 한 트랜잭션으로 처리.

같은 user_id가 있고 email이 비어있으면  → UPDATE
같은 user_id가 없으면                   → INSERT

INSERT INTO의 중복 문제를 해결할 수 있다. WHEN NOT MATCHED만 쓰면 중복 없는 삽입만 수행:

MERGE INTO events a
USING events_update b
ON a.user_id = b.user_id AND a.event_timestamp = b.event_timestamp
WHEN NOT MATCHED AND b.traffic_source = 'email' THEN
  INSERT *

COPY INTO — 증분 로드

COPY INTO sales
FROM "/path/sales-30m"
FILEFORMAT = PARQUET

이미 가져온 파일은 건너뛰고 새 파일만 가져온다. 매일 새 파일이 쌓이는 폴더에서 반복 실행할 때 유용.

한눈에 비교

방법기존 데이터중복 방지용도
INSERT OVERWRITE전체 교체-정기적으로 전체 갱신
INSERT INTO유지+추가단순 추가
MERGE INTO조건부 처리업서트, 정교한 동기화
COPY INTO유지+추가✅ (파일 단위)외부 파일 증분 수집

전체 흐름 정리

외부 데이터 원본                외부 테이블 등록              Delta 테이블 생성
──────────────                 ────────────────              ────────────────
CSV 파일                        USING CSV                     CTAS로 변환
JSON 파일          →            USING JSON          →         Delta 최적화 적용
외부 DB (MySQL 등)              USING JDBC                    ↓
                                LOCATION 지정                 쓰기
                                (데이터는 밖에 그대로)         (MERGE / COPY INTO)

  [Bronze: 원본 연결]             [Bronze 단계]                [Silver/Gold 단계]

실무에서는 외부 테이블(Bronze) → CTAS로 Delta 변환(Silver) → 정제/집계(Gold) 순서로 메달리온 아키텍처를 구성한다.

profile
이제 개발해야지...

0개의 댓글