1. 관계 테이블 방식 (Normalized Approach)
구조
-- 기본 users 테이블
CREATE TABLE users (
id INTEGER PRIMARY KEY,
email TEXT,
nickname TEXT
);
-- 별도의 favorites 관계 테이블
CREATE TABLE favorites (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
board_id INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (board_id) REFERENCES boards(id),
UNIQUE(user_id, board_id)
);
장점
- 데이터 정규화: 표준적인 데이터베이스 설계 원칙 준수
- 무결성 보장: 외래키 제약조건으로 데이터 일관성 유지
- 확장성: 추가 정보(생성일, 우선순위 등) 쉽게 추가 가능
- 인덱싱 최적화: 각 컬럼에 인덱스 적용 용이
- 쿼리 최적화: JOIN을 통한 효율적인 데이터 조회
- 유지보수성: 명확한 테이블 구조로 코드 이해도 향상
단점
- 테이블 관리: 추가 테이블 생성 및 관리 필요
- 쿼리 복잡성: JOIN 연산으로 인한 복잡한 쿼리
- 성능 오버헤드: 관계 테이블 조회로 인한 약간의 성능 저하
- 개발 시간: 초기 설계 및 구현 시간 증가
2. 단일 테이블 방식 (Denormalized Approach)
구조
-- users 테이블에 favorites 컬럼 추가
CREATE TABLE users (
id INTEGER PRIMARY KEY,
email TEXT,
nickname TEXT,
favorites JSON -- 또는 TEXT로 comma-separated values 저장
);
-- 데이터 예시: [{"board_id": 1, "added_at": "2024-01-01"}, {"board_id": 3, "added_at": "2024-01-02"}]
장점
- 간단한 구현: 별도 테이블 생성 불필요
- 빠른 개발: 적은 코드량으로 빠른 구현 가능
- 성능: 단일 테이블 조회로 인한 빠른 응답 속도
- 직관적: 하나의 테이블에서 모든 정보 관리
- 관리 용이: 테이블 수가 적어 관리가 쉬움
단점
- 데이터 정규화 위반: 같은 정보가 여러 곳에 중복 저장될 수 있음
- 쿼리 제한: 복잡한 조회 및 집계 연산 어려움
- 확장성 부족: 구조 변경 시 전체 테이블 수정 필요
- 데이터 무결성: 제약조건으로 관리하기 어려움
- 인덱싱 제한: JSON/TEXT 컬럼의 인덱싱이 제한적
3. 실질적인 코드 비교
관계 테이블 방식
// 즐겨찾기 추가
const addFavorite = async (userId, boardId) => {
const stmt = db.prepare(`
INSERT OR IGNORE INTO favorites (user_id, board_id)
VALUES (?, ?)
`);
return stmt.run(userId, boardId);
};
// 즐겨찾기한 게시판의 글 조회
const getFavoriteBoardPosts = async (userId) => {
const stmt = db.prepare(`
SELECT p.*, u.nickname as author
FROM posts p
JOIN boards b ON p.board_id = b.id
JOIN favorites f ON b.id = f.board_id
JOIN users u ON p.user_id = u.id
WHERE f.user_id = ?
ORDER BY p.created_at DESC
`);
return stmt.all(userId);
};
단일 테이블 방식
// 즐겨찾기 추가
const addFavorite = async (userId, boardId) => {
const user = db.prepare('SELECT favorites FROM users WHERE id = ?').get(userId);
let favorites = user.favorites ? JSON.parse(user.favorites) : [];
if (!favorites.find(fav => fav.board_id === boardId)) {
favorites.push({ board_id: boardId, added_at: new Date().toISOString() });
db.prepare('UPDATE users SET favorites = ? WHERE id = ?')
.run(JSON.stringify(favorites), userId);
}
};
// 즐겨찾기한 게시판의 글 조회
const getFavoriteBoardPosts = async (userId) => {
const stmt = db.prepare(`
SELECT p.*, u.nickname as author
FROM posts p
JOIN boards b ON p.board_id = b.id
JOIN users usr ON usr.id = ?
WHERE b.id IN (
SELECT json_extract(value, '$.board_id')
FROM json_each(usr.favorites)
)
JOIN users u ON p.user_id = u.id
ORDER BY p.created_at DESC
`);
return stmt.all(userId);
};
4. 선택 기준
관계 테이블 방식 추천 상황
- 장기적 프로젝트 운영
- 복잡한 기능 확장 계획
- 팀 개발 환경
- 데이터 무결성 중요
- 표준적인 데이터베이스 설계 요구
단일 테이블 방식 추천 상황
- 빠른 MVP 개발
- 단순한 요구사항
- 혼자 개발하는 경우
- 성능 최적화가 중요한 경우
- 유지보수 시간 제한
5. 결론
두 방식 모두 각자의 장점이 있으며, 프로젝트의 특성과 요구사항에 따라 선택해야 합니다.
- 규모가 크고 장기적인 프로젝트: 관계 테이블 방식
- 빠른 개발과 단순한 요구사항: 단일 테이블 방식
저는 단일테이블 방식으로 구현하려고 했는데 ai가 저렇게 알려줘서 고민이 생겼네요