원본 테이블 raw_daily_price
데이터 품질을 판단하기 전에 수집한 데이터를 최대한 그대로 저장하는 공간
- 문자열 위주로 저장
- 제약조건 최소화
- 수집시간·출처 기록
52000
N/A
-
예를 들어 close 값이 위처럼 섞여 있어도 일단 그대로 저장한다.
왜냐하면 원본 저장 단계의 목적은 “이 값이 올바른가?”를 판단하는 것이 아니라 “수집한 것을 잃지 않고 보관하는 것”이기 때문이다.
왜 모든 컬럼을 문자열로 받을까?
close VARCHAR(30)
date VARCHAR(20)
원본 데이터를 문자열로 저장하면 예상하지 못한 형식의 값이 들어와도 데이터를 잃지 않고 보관할 수 있다.
52000
N/A
-
1,250
예를 들어 숫자가 들어올 거라고 예상했는데 위의 값이 들어온다고 가정해보자.
INT NOT NULL로 만들어두면 N/A 같은 값 때문에 저장 자체가 실패한다.
하지만 VARCHAR라면 전부 저장할 수 있다.
왜 원본을 꼭 저장해야 할까?
가장 중요한 이유는 정제 로직이 틀릴 수도 있기 때문이다.
"1,250" → 125
예를 들어 정제 과정에서 실수로 잘못 변환했다고 해보자.
원본이 없다면 무엇이 잘못됐는지 확인하거나 다시 처리하기 어렵다.
반대로 원본이 남아 있다면 데이터를 다시 처리할 수 있다.
원본 데이터
↓
정제 로직 수정
↓
다시 정제
제약조건이 강한 테이블에 원본을 바로 넣으면?
정제되지 않은 데이터를 강한 타입과 제약조건이 있는 테이블에 바로 넣으면 저장 실패가 발생할 수 있다.
CREATE TABLE demo_strict(
code VARCHAR(20),
date DATE,
close INT NOT NULL
);
여기서는 close가 반드시 정수여야 한다.
그런데 원본 데이터에 N/A나 -가 있으면 저장할 수 없다.
try:
...
except:
conn.rollback()
이런 경우에는 try-except 로 실패를 처리할 수 있다.
즉, 수집 단계부터 강한 제약을 걸면 비정상 데이터 하나 때문에 원본 자체를 잃을 수 있다.
rollback()
rollback은 현재 트랜잭션에서 실행한 변경사항을 취소하고 이전 상태로 되돌리는 것이다.
conn.rollback()
예를 들어 INSERT 중 문제가 발생했다면 아래처럼 진행이 된다.
INSERT 시도
↓
에러 발생
↓
rollback()
↓
이번 변경 취소
정제 테이블 daily_price
원본을 안전하게 저장한 뒤에는 정제 과정을 거쳐 실제 분석이나 서비스에서 사용할 테이블로 옮긴다.
이 테이블에서는 데이터의 의미에 맞게 타입과 제약조건을 설정한다.
code VARCHAR(20) NOT NULL
date DATE NOT NULL
close BIGINT
volume BIGINT
changeRate DECIMAL(6, 2)
raw_daily_price
↓
데이터 정제
↓
daily_price
원본 테이블 vs 정제 테이블

원본 테이블 = 택배가 도착한 창고
정제 테이블 = 검사하고 정리해서 진열한 상품
금액에 float를 사용하지 않는 이유
close BIGINT
주가처럼 정수 단위의 금액이라면 BIGINT 같은 정수형을 사용할 수 있다.
float는 이진 부동소수점 방식이라 특정 소수를 정확하게 표현하지 못할 수 있다.
0.1 + 0.2
예를 들어 개념적으로 0.1 + 0.2가 정확히 0.3으로 표현되지 않는 문제가 생길 수 있다.
따라서 정확한 금액 계산이 필요할 때는 FLOAT를 무조건 사용하는 것은 피하는 것이 좋다.
다만, 금액 자체가 정수라면 BIGINT가 자연스럽다.
비율에 DECIMAL
changeRate DECIMAL(6, 2)
DECIMAL은 소수점 자릿수를 정확하게 관리할 수 있는 자료형이다.
DECIMAL(6, 2)
전체 최대 6자리
소수점 아래 2자리
예를 들어 1234.56같은 값을 저장할 수 있습니다.
비율이나 금액처럼 정확한 소수 계산이 중요한 데이터에 적합하다.
날짜는 DATE
date DATE NOT NULL
날짜를 문자열로 저장할 수도 있지만 DATE로 저장해야 날짜 비교·정렬·계산을 제대로 활용할 수 있다.
날짜 비교
기간 검색
정렬
YEAR(), MONTH() 등 날짜 함수
코드는 왜 VARCHAR?
code VARCHAR(20)
종목 코드처럼 숫자로 보이는 값이라도 실제로는 계산하기 위한 숫자가 아니라 식별자다.
예를 들어 001234라는 숫자를 저장하면 1234가 되어 앞의 0이 사라질 수 있다.
따라서 계산하지 않는 식별번호는 앞자리 0을 보존하기 위해 VARCHAR(문자열)로 저장하는 것이 적합하다.
UNIQUE KEY
UNIQUE KEY uk_code_date (code, date)
UNIQUE(code, date)는 같은 종목의 같은 날짜 데이터가 중복 저장되는 것을 막는다.
G0001 / 2026-09-01
G0001 / 2026-09-01 ← 중복 X
code 하나만 보면 여러 날짜가 있기 때문에 중복될 수 있고, date 하나만 봐도 여러 종목이 있으므로 중복될 수 있다.
code + date
하지만 code + date 조합을 사용함으로써 데이터가 중복 저장되는 것을 막는다.
collected_at과 updated_at
collected_at은 수집 시점, updated_at은 데이터가 마지막으로 변경된 시점을 기록한다.
collected_at DATETIME DEFAULT CURRENT_TIMESTAMP
원본에는 collected_at을 두어 언제 수집했는지 기록한다.
updated_at DATETIME
DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP
정제 테이블에는 updated_at을 사용하여 처음 저장된 시간뿐 아니라 데이터가 수정된 시간도 자동으로 기록할 수 있다.
전체 흐름
원본은 일단 받아두고, 판단은 정제 단계에서 한다.
외부 데이터 수집
↓
raw_daily_price
일단 원본 그대로 저장
↓
검증 / 정제 / 형변환
↓
daily_price
서비스에 사용할 깨끗한 데이터 저장
- 원본 테이블 : 수집 데이터를 잃지 않도록 최소한의 제약으로 그대로 저장한다.
- 문자열 저장 : 예상하지 못한 값도 받을 수 있도록 원본 단계에서는 문자열을 주로 사용한다.
- 원본 보존 : 정제 로직이 잘못되어도 원본으로 돌아가 다시 처리할 수 있다.
- 정제 테이블 : 검증이 끝난 데이터를 실제 의미에 맞는 자료형으로 저장한다.
- rollback() : 문제가 생긴 트랜잭션의 변경사항을 취소한다.
- BIGINT : 정수 단위의 큰 숫자나 금액을 정확하게 저장한다.
- DECIMAL : 정확한 소수 자릿수가 필요한 비율 등에 사용한다.
- DATE : 날짜 비교·정렬·계산을 위해 날짜 전용 타입을 사용한다.
- VARCHAR 코드 : 계산용 숫자가 아닌 식별자는 앞자리 0 보존을 위해 문자열로 저장한다.
- UNIQUE(code, date) : 동일 종목·동일 날짜 데이터의 중복 저장을 막는다.
'- > Pandas' 카테고리의 다른 글
| Pandas: 멱등성과 UPSERT (0) | 2026.09.03 |
|---|---|
| Pandas: 대량 데이터 DB 적재와 성능 (0) | 2026.09.03 |
| Pandas와 DB 연동 + DB 작업 시 조심할 점 3가지 (0) | 2026.09.03 |
| Pandas: DataFrame 결합(Merge) 기본 및 키 정 (0) | 2026.08.30 |
| Pandas: 데이터 결측 처리 전략 및 시계열 보간 (0) | 2026.08.30 |