4강 — 데이터베이스(DB) 기초
🎯 학습 목표
- 변수·CSV 파일에 데이터를 담을 때의 한계를 설명하고, 왜 데이터베이스가 필요한지 이해할 수 있다
- 관계형 DB와 NoSQL의 차이를 알고, SQL이 왜 한 번 배우면 평생 쓰는 기술인지 설명할 수 있다
- SQLite의 특징(파일 하나=DB, 설치 불필요, 파이썬 기본 내장)을 이해하고 테이블·컬럼·로우·스키마·기본키·외래키 개념을 설명할 수 있다
- 파이썬
sqlite3로 DB에 연결하고 CRUD(조회·추가·수정·삭제)를 직접 수행할 수 있다
📖 해설
1. 우리가 만든 데이터, 어디에 저장하고 있었나요?
지난 시간(35차시)에 우리는 리뷰 분석 대시보드 앱을 설계했습니다. 버튼을 누르면 리뷰를 분석하고 결과를 차트로 보여주는 앱이었죠. 기술 스택도 정했습니다. SQLite + FastAPI + Streamlit, 그리고 데이터 흐름은 "리뷰 수집 → DB 저장 → 분석 → 시각화" 순서였습니다. 오늘은 이 흐름 중에서 'DB 저장' 부분을 배웁니다.
먼저 질문 하나 드리겠습니다. 지금까지 우리는 데이터를 어디에 저장했나요? Colab에서 df 같은 변수를 만들어서 데이터프레임 형태로 담았습니다. 그런데 여기엔 큰 문제가 있었습니다. 세션이 끊기면 다 사라진다는 점입니다. Colab은 보통 90분이 세션 시간인데, 이 시간이 지나면 우리가 저장해 둔 변수들이 전부 날아갑니다. 그러면 처음부터 코드를 다시 실행해야 하죠.
학습할 때는 그럭저럭 견딜 수 있습니다. 그런데 앱에서는 이러면 안 됩니다. 사용자가 오늘 저장한 리뷰가 다음 날 사라지면 어떻게 될까요? 그건 앱이라고 부를 수도 없습니다. 그래서 앱에서는 데이터를 데이터베이스에 영구적으로, 그리고 체계적으로 저장합니다.
2. 변수·파일 저장의 네 가지 한계
"그럼 CSV 파일로 저장하면 되지 않나요?"라고 생각할 수 있습니다. 하지만 변수든 CSV 파일이든, 앱에서 쓰기에는 네 가지 한계가 있습니다.
- 휘발성 (변수) — 변수는 프로그램이 종료되면 사라집니다. 메모리에만 있기 때문입니다.
- 검색이 어려움 (CSV) — CSV는 파일로 저장은 됩니다. 하지만 "긍정 리뷰만 찾아줘"를 하려면 파일 전체를 처음부터 끝까지 다 읽어야 합니다. CSV는 기록(저장)에 특화된 형태라, 데이터가 많아지면 검색이 점점 느려집니다.
- 수정이 복잡함 — CSV에서 딱 한 줄만 바꾸고 싶어도, 파일 전체를 읽어 들이고 수정한 뒤 다시 통째로 저장해야 합니다.
- 동시 접근 불가 — 두 사용자가 동시에 같은 파일을 수정하면 내용이 꼬여 버립니다.
이 네 가지 한계를 한 번에 해결해 주는 것이 바로 데이터베이스입니다.
3. 데이터베이스란 무엇인가
데이터베이스는 데이터를 체계적으로 저장하고 관리하는 시스템입니다. 위에서 본 네 가지 한계를 정확히 거꾸로 뒤집어 보면, 데이터베이스가 무엇을 해 주는지 한눈에 보입니다.
| 변수·파일의 한계 | 데이터베이스의 해결 |
|---|---|
| 휘발성 (종료되면 사라짐) | 영구 저장 — 프로그램을 종료해도, 컴퓨터를 껐다 켜도 데이터가 유지됩니다 |
| 검색이 느림 | 빠른 검색 — "긍정 리뷰만 보여줘" 하면 조건에 맞는 것만 빠르게 찾아 줍니다 |
| 수정이 복잡함 | 효율적 수정 — 바꾸고 싶은 한 줄만 정확히 바꿉니다. 전체를 다시 쓸 필요가 없습니다 |
| 동시 접근 불가 | 동시 접근 — 여러 사용자·프로그램이 동시에 써도 문제없습니다 |
비유하자면 도서관과 같습니다. 도서관은 책이 분야별로 체계적으로 분류되어 있고, 검색 시스템으로 원하는 책을 빠르게 찾을 수 있죠. 데이터베이스가 데이터에 대해 바로 그런 역할을 합니다.
4. DB의 두 종류: 관계형 vs NoSQL
데이터베이스에도 종류가 있습니다. MySQL, MongoDB, Oracle, PostgreSQL 같은 이름을 한 번쯤 들어 보셨을 겁니다. 이들은 크게 두 갈래로 나뉩니다.
관계형 데이터베이스 (RDBMS) - 표(테이블) 형태로 데이터를 저장합니다. 엑셀 시트를 떠올리시면 됩니다. - 테이블끼리 관계로 연결할 수 있습니다. 예를 들어 '리뷰 테이블'과 '분석 결과 테이블'을 이어 붙일 수 있죠. - SQL이라는 공통 언어로 조작합니다. - 예: MySQL, PostgreSQL, SQLite
NoSQL 데이터베이스 - 여기서 'No'는 "SQL이 아니다(부정)"라는 뜻이 아니라 "Not Only SQL" — 기존 SQL 방식 외에 다른 무언가를 더 제공한다는 의미입니다. - 표가 아니라 문서(document)나 키-값(key-value) 같은 다양한 형태로 저장하는 경우가 많습니다. - 구조가 유연하고(스키마가 고정되어 있지 않음), 제품마다 고유한 문법을 씁니다. - 예: MongoDB, Redis, Firebase
우리는 이 중 관계형 DB인 SQLite를 배웁니다. 왜냐하면 리뷰 데이터가 "ID, 내용, 작성자, 감성…" 처럼 칸이 정해진 표 형태에 딱 맞기 때문입니다.
5. SQL은 데이터베이스의 공용어
관계형 DB를 다루려면 SQL(Structured Query Language, 구조화된 질의 언어)을 알아야 합니다. 어렵게 생각할 것 없습니다. SQL은 한마디로 데이터베이스와 대화하는 언어입니다. "긍정 리뷰만 보여줘"를 SQL로 요청하면, 데이터베이스가 알아서 찾아 줍니다.
SQL의 가장 큰 장점은 표준화된 문법이라는 점입니다. MySQL에서 배운 SQL을 PostgreSQL에서도, SQLite에서도 거의 그대로 쓸 수 있습니다. (제품마다 세부 문법이 조금씩 다를 수는 있지만, 기본 골격은 표준을 따릅니다.) 그래서 SQL 하나만 제대로 배워 두면 어떤 관계형 DB든 다룰 수 있습니다.
게다가 SQL은 영어 문장처럼 읽힙니다. 아래 문장을 한번 소리 내어 읽어 보세요.
SELECT * FROM reviews WHERE sentiment = '긍정';
"reviews(테이블)에서 / sentiment(감성)가 '긍정'인 것을 / 전부(*) 선택해라" — 무슨 뜻인지 느낌이 오시죠? 이게 SQL의 매력입니다. 영어가 국제 공용어이듯, SQL은 데이터베이스의 공용어입니다. 한 번 배워 두면 개발하는 내내 평생 쓰게 되는, 정말 중요한 기술입니다.
6. 왜 SQLite를 쓸까
관계형 DB 중에서도 우리는 SQLite를 씁니다. SQLite는 파일 기반의 가볍고 작은 관계형 데이터베이스입니다. 입문자에게, 그리고 우리 앱 규모에 딱 맞는 네 가지 이유가 있습니다.
- 파일 하나가 곧 데이터베이스 —
reviews.db파일 하나에 모든 데이터가 담깁니다. 복사하기도, 백업하기도 쉽습니다. (다른 컴퓨터로 옮기려면 그 파일 하나만 보내면 됩니다.) - 서버 설치가 필요 없음 — MySQL이나 PostgreSQL은 별도의 데이터베이스 서버를 설치하고 설정해야 합니다. SQLite는 그런 과정이 전혀 없습니다.
- 파이썬에 기본 내장 —
import sqlite3한 줄이면 끝입니다. 따로 설치할 필요가 없습니다. - 실제로 많이 씀 — 스마트폰 앱, 웹 브라우저 같은 곳에서 SQLite를 아주 많이 사용합니다. 장난감이 아니라 실전에서 쓰는 도구입니다.
물론 한계도 있습니다. 나중에 사용자가 수천 명, 수만 명으로 늘어나면 그때는 PostgreSQL 같은 서버형 DB로 전환을 고려하면 됩니다. 지금 우리 앱 규모에는 SQLite가 정확히 맞습니다.
7. 테이블 구조 이해하기
SQLite로 데이터를 담으려면 먼저 테이블 개념을 알아야 합니다. 테이블은 데이터를 담는 표입니다. 엑셀 시트와 거의 같다고 보면 됩니다. 우리는 리뷰를 저장하는 reviews 테이블, 분석 결과를 저장하는 analysis_results 테이블을 만들 예정입니다.
| 용어 | 무엇인가 | 예 |
|---|---|---|
| 테이블 (Table) | 데이터를 담는 표. 엑셀 시트와 비슷 | reviews 테이블 |
| 컬럼 (Column) | 세로 줄. 데이터의 속성(항목) | id, 리뷰 내용, 작성자, 감성 |
| 로우 (Row) | 가로 줄. 실제 데이터 한 세트(한 건) | 리뷰 하나하나 |
| 스키마 (Schema) | 테이블의 설계도. 어떤 컬럼이 있고 각 컬럼이 어떤 타입인지 정의 | "id는 정수, content는 문자열…" |
엑셀로 치면 맨 윗줄의 항목 이름이 컬럼, 그 아래 한 줄 한 줄이 로우, 그리고 "이 표에는 어떤 칸이 있어야 한다"는 설계도가 스키마입니다.
8. 데이터 타입: 칸마다 어떤 종류가 들어가는가
컬럼을 만들 때는 그 칸에 어떤 종류의 데이터가 들어갈지(데이터 타입)를 함께 지정합니다. SQLite의 데이터 타입은 단순합니다. 크게 네 가지만 알면 됩니다.
| 타입 | 의미 | 예 | 어디에 쓰나 |
|---|---|---|---|
| INTEGER | 정수 | 1, 2, 100, -5 | id, 플레이 시간 |
| TEXT | 문자열 | "재밌는 게임이에요", "긍정" | 리뷰 내용, 감성 라벨 |
| REAL | 소수점 숫자 | 3.14, 98.5 | 점수, 비율 |
| BLOB | 바이너리 데이터(이미지 등) | (이미지 파일 등) | 우리는 거의 안 씀 |
SQLite의 타입이 이렇게 단순한 게 오히려 장점입니다. 복잡한 데이터는 그냥 TEXT로 저장하면 됩니다. 예를 들어 JSON 같은 구조화된 데이터도 문자열(TEXT)로 저장해 두고, 꺼낼 때 다시 파싱하면 됩니다.
9. 기본키(PRIMARY KEY): 각 행을 식별하는 고유 번호
테이블 설계에서 가장 중요한 개념이 기본키(PRIMARY KEY)입니다. 기본키는 각 로우(행)를 유일하게 구분하는 고유한 값입니다.
비유하자면 주민등록번호입니다. 모든 사람의 주민등록번호는 서로 다르죠. 그래서 주민등록번호 하나로 특정 사람을 콕 집어낼 수 있습니다. 학번도 마찬가지입니다. 같은 학번을 가진 학생은 없으니까요. 우리 테이블에서는 id를 기본키로 씁니다. 리뷰마다 고유한 번호가 붙는 거죠.
여기에 AUTOINCREMENT를 붙이면, 번호가 자동으로 1, 2, 3… 하고 증가하며 부여됩니다. 우리가 직접 번호를 매기지 않아도 됩니다. 새 리뷰를 넣을 때마다 DB가 알아서 다음 번호를 붙여 줍니다.
기억할 원칙 하나. 모든 테이블에는 기본키가 있어야 합니다. 그래야 특정 데이터를 정확하고 빠르게 찾을 수 있습니다.
10. 외래키(FOREIGN KEY): 테이블끼리 연결하기
외래키(FOREIGN KEY)는 다른 테이블의 기본키를 참조해서 두 테이블을 연결하는 방법입니다. 예를 들어 보겠습니다.
reviews테이블에는 리뷰 원본이 저장되어 있고,id가 기본키입니다.analysis_results테이블에는 분석 결과가 저장되어 있습니다.
이때 "이 분석 결과가 어떤 리뷰의 것인지" 알아야 합니다. 그래서 analysis_results 테이블에 review_id라는 컬럼을 두고, 이것이 reviews 테이블의 id를 가리키게(참조하게) 합니다. 이 review_id가 바로 외래키입니다. review_id가 1이면 "이건 reviews 테이블에서 id가 1인 리뷰의 분석 결과다"라는 뜻이 됩니다.
이렇게 테이블을 연결해 두면 관련 데이터를 함께 조회할 수 있습니다. 마치 레고 블록처럼, '리뷰 블록'과 '분석 결과 블록'을 끼워 맞춰 원하는 데이터를 조립하는 셈이죠.
외래키는 처음 배울 때 다소 어렵게 느껴질 수 있는 개념입니다. 지금은 완벽히 이해하려 애쓰기보다, "외래키라는 게 있고, 이게 두 테이블을 이어 주는 역할을 한다" 정도만 확실히 잡고 가시면 됩니다. 다음 시간 프로젝트에서 직접 만들어 보면서 자연스럽게 익히게 됩니다.
테이블 설계의 핵심 두 가지: 기본키로 각 행을 식별하고(모든 테이블에 필수), 외래키로 테이블끼리 연결한다. 잘 설계된 테이블은 중복이 없고, 찾기 쉽고, 수정도 쉽습니다.
11. (참고) 정규화 — 우리는 지금 하지 않습니다
한 가지 개념을 짚고 넘어가겠습니다. 정규화(Normalization)는 데이터 중복을 줄이고 무결성(데이터가 서로 모순되지 않고 일관됨)을 높이기 위해 테이블을 체계적으로 나누는 기법입니다. 사용자가 수백만 명이고 데이터가 엄청나게 많은 대규모 시스템에서는, 데이터 일관성과 저장 효율을 위해 꼭 필요합니다.
다만 우리는 직접 정규화를 하지 않을 예정입니다. 우리 앱 규모(리뷰 수십~수백 개)에서는 오버 스펙이기 때문입니다. 지금 만드는 두 개의 테이블 구조(reviews + analysis_results)로 충분합니다. 정규화까지 하면 테이블이 복잡해지고 시간도 많이 걸려서, 앱의 요구 사항에 맞게 단순하게 가는 편이 낫습니다. "정규화라는 게 있고, 큰 시스템에서 쓰는 거구나" 정도로 알아 두시면 됩니다.
12. 데이터베이스에서 할 수 있는 일: CRUD
데이터베이스는 저장만 하면 끝일까요? 아닙니다. 데이터베이스의 모든 작업은 네 가지 기본 동작으로 이루어집니다. 이 네 가지를 영어 머리글자로 묶어 CRUD라고 부릅니다. 이것만 알면 데이터베이스로 사실상 무엇이든 할 수 있습니다.
| 글자 | 작업 | SQL 명령어 | 우리 앱에서 언제 |
|---|---|---|---|
| C | Create (추가) | INSERT |
새 리뷰를 저장할 때 |
| R | Read (조회) | SELECT |
리뷰 목록을 볼 때 — 가장 핵심 |
| U | Update (수정) | UPDATE |
분석 결과를 고칠 때 |
| D | Delete (삭제) | DELETE |
오래된 리뷰를 지울 때 |
13. SELECT — 조회 (가장 많이 쓰는 명령어)
CRUD 중에서도 단연 가장 많이 쓰는 것이 조회(SELECT)입니다. 세 가지 패턴만 보면 감이 옵니다.
-- ① 전체 조회: reviews 테이블의 모든 데이터를 가져온다 (*는 '전부'라는 뜻)
SELECT * FROM reviews;
-- ② 특정 컬럼만: 내용(content)과 감성(sentiment)만 가져온다
SELECT content, sentiment FROM reviews;
-- ③ 조건 추가(WHERE): 감성이 '긍정'인 것만 가져온다
SELECT * FROM reviews WHERE sentiment = '긍정';
*는 "모든 컬럼", 컬럼 이름을 직접 쓰면 "그 컬럼만", WHERE를 붙이면 "조건에 맞는 것만"입니다. 영어로 읽으면 그대로 뜻이 됩니다 — "reviews에서 전부 선택해."
14. INSERT / UPDATE / DELETE — 추가·수정·삭제
나머지 세 명령어도 함께 보겠습니다.
-- INSERT (추가): reviews 테이블에 새 리뷰를 넣는다
-- 어떤 컬럼에 넣을지 적고, VALUES로 값을 지정한다
INSERT INTO reviews (content, sentiment) VALUES ('재밌는 게임!', '긍정');
-- UPDATE (수정): 5번 리뷰의 감성을 '부정'으로 바꾼다
-- SET으로 바꿀 값을, WHERE로 어떤 행을 바꿀지 지정한다
UPDATE reviews SET sentiment = '부정' WHERE id = 5;
-- DELETE (삭제): 5번 리뷰를 지운다
DELETE FROM reviews WHERE id = 5;
여기서 반드시 기억해야 할 경고가 있습니다.
UPDATE와 DELETE에서
WHERE를 빼면 전체에 적용됩니다.DELETE FROM reviews;라고만 쓰면 리뷰가 전부 사라집니다.UPDATE reviews SET sentiment = '긍정';이라고만 쓰면 모든 리뷰가 긍정으로 바뀝니다. 실수로 데이터를 지우면 복구가 어렵습니다. 수정·삭제할 때는 항상WHERE조건이 있는지 한 번 더 확인하세요.
15. 파이썬에서 SQLite 연결하기
SQL 명령어는 알았습니다. 그럼 이걸 파이썬에서 어떻게 실행할까요? 데이터베이스들은 각각 파이썬 모듈을 가지고 있어서, 그걸 import해서 씁니다. SQLite는 sqlite3 모듈을 쓰는데, 파이썬에 기본 내장되어 있어 설치 없이 바로 쓸 수 있습니다.
연결의 기본 흐름은 세 단계입니다. ① 모듈 불러오기 → ② DB 파일에 연결하기(없으면 자동 생성) → ③ 커서(cursor) 만들기. 여기서 커서는 SQL 명령어를 실제로 데이터베이스에 전달하고 실행하는 도구라고 생각하면 됩니다.
# sqlite3 모듈 불러오기 (파이썬 기본 내장, 설치 필요 없음)
import sqlite3
# DB 파일에 연결합니다. 파일이 없으면 자동으로 만들어 줍니다.
# 우리는 'reviews.db'라는 이름으로 만들 예정입니다.
conn = sqlite3.connect('reviews.db')
# 커서를 만듭니다. 커서를 통해 SQL 명령어를 실행합니다.
cursor = conn.cursor()
print("데이터베이스 연결 성공!")
이 코드를 실행하면, 같은 폴더에 reviews.db 파일이 새로 생긴 것을 확인할 수 있습니다. 그 파일 하나가 곧 우리의 데이터베이스입니다.
16. 테이블 만들기 (CREATE TABLE)
이제 데이터를 담을 테이블을 만들어 봅니다. CREATE TABLE 명령어로 테이블의 구조(스키마)를 정의합니다. 컬럼 이름과 데이터 타입을 함께 적어 주면 됩니다.
# 테이블 생성 (이미 있으면 에러가 나지 않도록 IF NOT EXISTS 사용)
cursor.execute('''
CREATE TABLE IF NOT EXISTS reviews (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- 고유 번호, 자동으로 1씩 증가
content TEXT NOT NULL, -- 리뷰 내용(문자열), 빈 값 금지
sentiment TEXT -- 감성 라벨(긍정/부정/중립)
)
''')
# 변경사항을 실제로 저장합니다. 이걸 안 하면 저장이 안 됩니다!
conn.commit()
print("reviews 테이블 생성 완료!")
여기서 쓰인 SQL 키워드를 정리하면 이렇습니다.
| 키워드 | 의미 |
|---|---|
INTEGER / TEXT |
정수형 / 문자열형 |
PRIMARY KEY |
기본키 (각 행을 구분하는 고유 식별자) |
AUTOINCREMENT |
자동으로 1씩 증가하며 번호 부여 |
NOT NULL |
빈 값을 허용하지 않음 (반드시 값이 있어야 함) |
그리고 절대 잊으면 안 되는 한 줄이 conn.commit()입니다. SQLite에서는 execute()로 명령을 보낸 뒤 commit()을 해야 비로소 변경 사항이 데이터베이스 파일에 실제로 반영됩니다. 이걸 빼먹으면 분명히 코드를 돌렸는데 데이터가 저장되지 않는 황당한 일이 생깁니다. (조회만 하는 SELECT는 commit이 필요 없지만, INSERT·UPDATE·DELETE처럼 데이터를 바꾸는 작업은 반드시 commit해야 합니다.)
17. 데이터 추가하기 (INSERT) — 그리고 ?의 정체
이제 리뷰 데이터를 넣어 봅니다. 한 건씩 넣을 수도 있지만, 여러 건을 한꺼번에 넣을 때는 executemany가 편리합니다.
# 추가할 리뷰 데이터 (내용, 감성) 묶음
reviews_data = [
("정말 재밌는 게임이에요! 강추!", "긍정"),
("버그가 너무 많아요. 실망입니다.", "부정"),
("그냥 그래요. 할만해요.", "중립"),
("최고의 게임! 100시간 플레이함", "긍정"),
("환불하고 싶어요.", "부정"),
]
# executemany: 여러 건을 한 번에 추가
# 물음표(?)는 '값이 들어갈 자리'이고, 실제 값은 뒤에서 채워 넣습니다.
cursor.executemany(
'INSERT INTO reviews (content, sentiment) VALUES (?, ?)',
reviews_data
)
conn.commit() # 추가했으니 반드시 저장
print(f"{len(reviews_data)}개 리뷰 추가 완료!")
여기서 VALUES (?, ?)의 물음표(?)가 중요합니다. 이건 "값이 들어갈 빈 자리"이고, 실제 값은 뒤에 따로 전달합니다. 왜 굳이 이렇게 할까요? 만약 값을 문자열에 직접 끼워 넣으면 SQL 인젝션(SQL Injection)이라는 보안 공격에 취약해지기 때문입니다.
# 나쁜 예 — 절대 이렇게 하면 안 됩니다 (SQL 인젝션에 취약)
content = "리뷰 내용"
cursor.execute(f"INSERT INTO reviews (content) VALUES ('{content}')")
# 좋은 예 — ?를 쓰고 값을 따로 전달 (안전)
cursor.execute("INSERT INTO reviews (content) VALUES (?)", (content,))
악의적인 사용자가 입력값에 SQL 명령어를 몰래 끼워 넣어 DB를 조작하거나 빼내는 게 SQL 인젝션입니다. ?를 쓰면 입력값을 '데이터'로만 취급하기 때문에 이런 공격을 막을 수 있습니다. 값을 넣을 때는 항상 ?를 쓰는 습관을 들이세요. (참고: ? 자리가 하나뿐일 때 (content,)처럼 쉼표를 붙이는 이유는, 파이썬에서 원소가 하나인 튜플을 만들려면 쉼표가 필요하기 때문입니다.)
18. 데이터 조회하기 (SELECT) — fetchall로 결과 받기
조회는 다른 명령어와 다른 점이 하나 있습니다. INSERT·UPDATE·DELETE는 그냥 실행하면 끝이지만, SELECT는 요청한 결과를 돌려받아야 합니다. 그 결과를 가져오는 메서드가 fetchall()(전체 결과를 리스트로 가져옴)입니다.
# 모든 리뷰 조회
cursor.execute('SELECT * FROM reviews')
rows = cursor.fetchall() # 결과 전체를 리스트로 받아옴
print("=== 전체 리뷰 목록 ===")
for row in rows:
# row는 (id, content, sentiment) 형태의 튜플 → 순서대로 인덱스로 꺼냅니다
print(f"ID: {row[0]} | 내용: {row[1][:20]}... | 감성: {row[2]}")
조건을 거는 조회도 똑같습니다. SQL에 WHERE를 붙이고, 조건 값은 역시 ?로 안전하게 전달합니다.
# 조건부 조회: 긍정 리뷰만 보기
cursor.execute('SELECT * FROM reviews WHERE sentiment = ?', ('긍정',))
positive_reviews = cursor.fetchall()
print("=== 긍정 리뷰만 ===")
for row in positive_reviews:
print(f"ID: {row[0]} | 내용: {row[1]}")
한 걸음 더 나아가, 집계도 SQL로 바로 할 수 있습니다. GROUP BY로 묶고 COUNT(*)로 개수를 세면, 감성별 리뷰 수를 한 줄의 SQL로 구할 수 있습니다. (파이썬에서 직접 세는 것보다 훨씬 간결합니다.)
# 집계: 감성별 리뷰 수 세기 (GROUP BY로 묶고 COUNT로 셈)
cursor.execute('''
SELECT sentiment, COUNT(*) as count
FROM reviews
GROUP BY sentiment
''')
stats = cursor.fetchall()
print("=== 감성별 리뷰 수 ===")
for sentiment, count in stats:
print(f"{sentiment}: {count}개")
| 문법 | 의미 | 예 |
|---|---|---|
SELECT * |
모든 컬럼 | SELECT * FROM reviews |
SELECT 컬럼명 |
특정 컬럼만 | SELECT content FROM reviews |
WHERE 조건 |
조건 필터 | WHERE sentiment = '긍정' |
GROUP BY |
그룹으로 묶기 | GROUP BY sentiment |
COUNT(*) |
개수 세기 | SELECT COUNT(*) FROM reviews |
19. 데이터 수정·삭제하기 (UPDATE / DELETE)
수정과 삭제도 패턴은 같습니다. SQL을 execute로 실행하고, 데이터를 바꿨으니 commit()으로 저장합니다. 반드시 WHERE로 대상을 지정하는 것을 잊지 마세요.
# 수정(UPDATE): 3번 리뷰의 감성을 '긍정'으로 바꾸기
cursor.execute(
'UPDATE reviews SET sentiment = ? WHERE id = ?',
('긍정', 3) # 값은 항상 ?로 안전하게 전달
)
conn.commit()
print("3번 리뷰 감성 수정 완료!")
# 수정 결과 확인 (한 건만 가져올 때는 fetchone)
cursor.execute('SELECT * FROM reviews WHERE id = 3')
print("수정 후:", cursor.fetchone()) # fetchone: 결과 한 줄만 가져옴
# 삭제(DELETE): 5번 리뷰 지우기 — 삭제 전후 개수를 비교해 봅니다
cursor.execute('SELECT COUNT(*) FROM reviews')
before = cursor.fetchone()[0]
print(f"삭제 전 리뷰 수: {before}개")
cursor.execute('DELETE FROM reviews WHERE id = ?', (5,))
conn.commit()
cursor.execute('SELECT COUNT(*) FROM reviews')
after = cursor.fetchone()[0]
print(f"삭제 후 리뷰 수: {after}개")
print("5번 리뷰 삭제 완료!")
다시 한번 강조합니다.
WHERE없는 UPDATE/DELETE는 전체에 적용됩니다. 한 줄만 바꾸려다 전체 데이터를 날릴 수 있으니, 실행 전에WHERE를 꼭 확인하세요.
20. 연결 종료, 그리고 전체 패턴
작업이 끝나면 연결을 닫아 줍니다. 자원을 정리하고 파일을 안전하게 마무리하는 단계입니다.
# 연결 종료
conn.close()
print("데이터베이스 연결 종료!")
지금까지 배운 것을 하나의 패턴으로 묶으면 이렇게 됩니다. 앞으로 모든 DB 작업이 이 흐름을 따릅니다.
import sqlite3
# 1. 연결 (없으면 파일 자동 생성)
conn = sqlite3.connect('database.db')
cursor = conn.cursor()
# 2. SQL 실행
cursor.execute('SQL 문장')
# 3. 결과 가져오기 (SELECT일 때만) — fetchall(전체) / fetchone(한 건)
rows = cursor.fetchall()
# 4. 변경사항 저장 (INSERT/UPDATE/DELETE일 때 필수)
conn.commit()
# 5. 연결 종료
conn.close()
정리하면, CRUD 작업별로 무엇을 쓰는지는 다음 한 장이면 충분합니다.
| 작업 | SQL 명령어 | 파이썬에서 |
|---|---|---|
| Create (추가) | INSERT INTO |
cursor.execute() + conn.commit() |
| Read (조회) | SELECT |
cursor.execute() + cursor.fetchall() |
| Update (수정) | UPDATE ... SET |
cursor.execute() + conn.commit() |
| Delete (삭제) | DELETE FROM |
cursor.execute() + conn.commit() |
🔑 핵심 용어
| 용어 | 쉬운 설명 |
|---|---|
| 데이터베이스(DB) | 데이터를 체계적으로 저장·관리하는 시스템. 영구 저장·빠른 검색·효율적 수정·동시 접근을 해결 |
| 관계형 DB(RDBMS) | 표(테이블) 형태로 저장하고 SQL로 다루는 DB. MySQL·PostgreSQL·SQLite 등 |
| NoSQL | "Not Only SQL". 문서·키값 등 유연한 형태로 저장하는 DB. MongoDB·Redis·Firebase 등 |
| SQL | 데이터베이스와 대화하는 표준 언어. 한 번 배우면 어떤 관계형 DB든 쓸 수 있음 |
| SQLite | 파일 하나가 곧 DB인 가벼운 관계형 DB. 서버 설치 불필요, 파이썬 기본 내장 |
| 테이블 / 컬럼 / 로우 | 데이터를 담는 표 / 세로 줄(속성) / 가로 줄(데이터 한 건) |
| 스키마(Schema) | 테이블의 설계도. 어떤 컬럼이 어떤 타입으로 있는지 정의 |
| 데이터 타입 | INTEGER(정수)·TEXT(문자열)·REAL(소수)·BLOB(바이너리) |
| 기본키(PRIMARY KEY) | 각 행을 유일하게 구분하는 고유값. 보통 id. AUTOINCREMENT로 자동 증가 |
| 외래키(FOREIGN KEY) | 다른 테이블의 기본키를 참조해 두 테이블을 연결하는 컬럼 |
| 정규화 | 중복을 줄이고 무결성을 높이려 테이블을 나누는 기법(대규모용, 우리는 생략) |
| CRUD | 데이터베이스의 4대 작업: Create(INSERT)·Read(SELECT)·Update(UPDATE)·Delete(DELETE) |
| 커서(cursor) | SQL 명령어를 DB에 전달·실행하는 도구 |
| commit() | 변경 사항을 DB 파일에 실제로 저장하는 명령. 빼먹으면 저장 안 됨 |
| SQL 인젝션 | 입력값에 SQL을 몰래 끼워 넣는 보안 공격. ? 사용으로 방어 |
🎮 게임 기획자 시점
지금까지 우리는 리뷰를 분석하고 결과를 손에 쥐었지만, 그건 전부 Colab 세션이 끝나면 사라지는 '일회용' 데이터였습니다. DB는 이 분석 결과를 자산으로 바꿔 줍니다. 수집한 리뷰와 감성·키워드·트렌드 분석 결과를 DB에 쌓아 두면, 패치 전후 감성 변화를 시기별로 비교하고, 특정 기간의 부정 리뷰만 골라 보고, 누적된 데이터로 대시보드를 그릴 수 있습니다. 이게 곧 운영 데이터 관리의 출발점입니다.
특히 두 테이블(reviews / analysis_results)을 외래키로 연결하는 설계는 게임 운영에서 자주 보는 구조입니다. '원본 데이터'와 '그에 대한 가공·판정 결과'를 분리해 두면, 나중에 분석 기준이 바뀌어도 원본은 그대로 두고 결과만 다시 채울 수 있죠. 또 WHERE로 조건을 거는 SELECT는, 기획자가 "지난 업데이트 이후 부정 리뷰만", "플레이 시간 100시간 이상 유저의 리뷰만" 같은 질문을 데이터에 직접 던지는 도구가 됩니다. SQL 한 줄이 곧 운영 질문 하나인 셈입니다.
⚠️ 흔한 오해 / 짚고 갈 점
commit()을 빼먹는 실수가 가장 흔합니다. 코드가 에러 없이 돌았는데 데이터가 안 보인다면 십중팔구 commit을 안 한 겁니다. INSERT·UPDATE·DELETE 뒤에는 반드시conn.commit().WHERE없는 UPDATE/DELETE는 전체에 적용됩니다.DELETE FROM reviews;는 모든 리뷰를 지웁니다. 수정·삭제 전WHERE를 꼭 확인하세요.- NoSQL의 'No'는 "SQL 반대"가 아니라 "Not Only SQL"입니다. SQL을 안 쓴다는 뜻이 아니라, SQL 외의 방식도 제공한다는 의미입니다.
- 값을 문자열에 직접 끼워 넣지 마세요. SQL 인젝션 위험이 있습니다. 항상
?자리표시자를 쓰고 값은 따로 전달합니다. - 명령어를 통째로 외울 필요는 없습니다. "이런 게 있구나" 정도로 익히고, 실제로는 검색이나 AI의 도움을 받아 그때그때 작성하면 됩니다. 쓰다 보면 자연스럽게 손에 익습니다.
- SQLite가 작아서 장난감 같다고 오해하기 쉽습니다. 스마트폰 앱·브라우저에서 실제로 쓰는 검증된 DB입니다. 다만 사용자가 수천·수만으로 커지면 PostgreSQL 등으로 전환을 고려합니다.
✅ 30초 정리
- 변수는 휘발되고 CSV는 검색·수정·동시 접근이 어렵습니다. 데이터베이스가 이 한계를 영구 저장·빠른 검색·효율적 수정·동시 접근으로 해결합니다.
- 우리는 관계형 DB인 SQLite를 씁니다. 파일 하나가 곧 DB이고, 설치 없이
import sqlite3로 바로 쓰며, SQL은 한 번 배우면 어디든 통하는 표준 언어입니다. - DB의 모든 작업은 CRUD(SELECT·INSERT·UPDATE·DELETE)이며, 파이썬에서는 연결→execute→(SELECT면 fetch)→commit→close 흐름으로 처리합니다. 기본키로 행을 식별하고 외래키로 테이블을 연결합니다.
▶️ 다음 강 연결
이번 시간엔 DB의 개념과 CRUD 사용법을 익혔습니다. 다음 5강 DB 구축 프로젝트에서는 오늘 배운 것을 직접 만들어 봅니다. 리뷰 원본을 담는 reviews 테이블과 분석 결과를 담는 analysis_results 테이블을 외래키로 연결해 설계·생성하고, 샘플 리뷰 데이터를 넣은 뒤 WHERE·GROUP BY로 다양하게 조회하고 집계하며, 두 테이블을 잇는 JOIN 조회까지 실습합니다.