포스트

[Python 100일 챌린지] Day 72 - SQLite 시작하기

[Python 100일 챌린지] Day 72 - SQLite 시작하기

Python으로 데이터베이스를 다뤄봐요! 🗄️✨

어제 데이터베이스 개념을 배웠으니, 이제 실제로 Python으로 SQLite를 다뤄볼 차례예요! 별도 설치 없이, Python 코드만으로 데이터베이스를 만들고 관리할 수 있답니다. 마치 엑셀을 코드로 다루는 것처럼 간단하지만 훨씬 강력해요! 😊

(35분 완독 ⭐⭐⭐)

🎯 오늘의 학습 목표

📚 사전 지식


🎯 학습 목표 1: SQLite 특징과 장점 이해하기

한 줄 설명

SQLite = 서버 없이 파일 하나로 동작하는 초경량 DB 📁⚡

설치도, 설정도, 서버도 필요 없는 초간단 데이터베이스예요!

1.1 SQLite가 특별한 이유

1
2
3
4
5
6
7
8
9
10
11
12
13
# 다른 데이터베이스들은...
"""
MySQL, PostgreSQL:
1. 별도 서버 설치 필요
2. 서버 실행 필요
3. 포트 설정, 사용자 권한 관리
4. 네트워크 설정
5. 복잡한 초기 설정
"""

# SQLite는?
import sqlite3
conn = sqlite3.connect("my_database.db")  # 끝! 😮

이게 전부예요! 단 한 줄로 데이터베이스가 생성돼요! 🎉

1.2 SQLite 주요 특징

graph TD
    A[SQLite 특징] --> B[서버리스]
    A --> C[파일 기반]
    A --> D[제로 설정]
    A --> E[경량]
    A --> F[크로스 플랫폼]

    B --> B1[서버 설치/실행 불필요]
    C --> C1[DB 전체가 파일 하나]
    D --> D1[별도 설정 없이 바로 사용]
    E --> E1[몇 백 KB 수준]
    F --> F1[Windows, Mac, Linux 모두 지원]

1.3 파일 기반의 장점

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import sqlite3
import os

# 1. 데이터베이스 생성
conn = sqlite3.connect("my_app.db")
conn.close()

# 2. 파일 확인
print(f"✅ 파일 생성됨: {os.path.exists('my_app.db')}")
print(f"📦 파일 크기: {os.path.getsize('my_app.db')} bytes")

# 3. 파일 복사 = 데이터베이스 백업!
import shutil
shutil.copy("my_app.db", "my_app_backup.db")
print("💾 백업 완료!")

# 4. 파일 삭제 = 데이터베이스 제거
os.remove("my_app_backup.db")
print("🗑️ 백업 파일 삭제됨")

출력:

1
2
3
4
✅ 파일 생성됨: True
📦 파일 크기: 0 bytes
💾 백업 완료!
🗑️ 백업 파일 삭제됨

💡 핵심: DB 파일을 복사하면 전체 백업, USB에 담으면 이동 가능!

1.4 언제 SQLite를 쓸까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ✅ SQLite가 완벽한 경우
적합한_사용처 = [
    "개인 프로젝트, 학습용",
    "모바일 앱 (안드로이드, iOS)",
    "소규모 웹사이트 (~10만 방문자/일)",
    "데스크톱 애플리케이션",
    "IoT 기기, 임베디드 시스템",
    "프로토타입, MVP 개발"
]

# ⚠️ SQLite가 부적합한 경우
부적합한_사용처 = [
    "대규모 웹사이트 (수백만 동시 사용자)",
    "높은 동시 쓰기 작업 (초당 수천 건)",
    "네트워크를 통한 다중 접근",
    "수백 GB 이상의 대용량 데이터"
]

print("SQLite는 중소규모 프로젝트에 완벽해요! 🎯")

1.5 실제 사용 사례

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# SQLite를 사용하는 유명 프로그램들
실제_사례 = {
    "모바일": [
        "안드로이드 OS (연락처, 메시지 저장)",
        "iOS (Safari 북마크, 앱 데이터)",
        "WhatsApp, Skype"
    ],
    "브라우저": [
        "Chrome (히스토리, 쿠키)",
        "Firefox (북마크, 설정)",
        "Safari (웹 데이터)"
    ],
    "": [
        "Apple Mail",
        "Dropbox",
        "Adobe Lightroom"
    ],
    "개발도구": [
        "Git (객체 DB)",
        "VS Code (확장 프로그램 데이터)"
    ]
}

print("SQLite는 이미 여러분 컴퓨터에서 수십 개 돌아가고 있어요! 😮")

놀라운 사실: 스마트폰 하나에 수백 개의 SQLite DB가 있어요!


🎯 학습 목표 2: 데이터베이스 연결과 커서 사용하기

한 줄 설명

연결(Connection) = DB 파일 열기, 커서(Cursor) = 작업 도구 🔌🖱️

데이터베이스와 대화하려면 연결과 커서가 필요해요!

2.1 연결(Connection) 객체

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import sqlite3

# 1. 데이터베이스 연결
conn = sqlite3.connect("example.db")
# - 파일이 없으면 자동 생성
# - 파일이 있으면 기존 DB 열기

# 2. 메모리 DB (임시, 프로그램 종료 시 사라짐)
memory_conn = sqlite3.connect(":memory:")

# 3. 연결 확인
print(f"연결 성공: {conn}")
print(f"메모리 DB: {memory_conn}")

# 4. 연결 종료
conn.close()
memory_conn.close()
print("✅ 연결 종료됨")

출력 예시:

1
2
3
연결 성공: <sqlite3.Connection object at 0x...>
메모리 DB: <sqlite3.Connection object at 0x...>
✅ 연결 종료됨

💡 Connection = 데이터베이스 파일에 대한 입구!

2.2 커서(Cursor) 객체

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import sqlite3

conn = sqlite3.connect("test.db")

# 커서 생성 (SQL 명령을 실행하는 도구)
cursor = conn.cursor()

# 커서로 할 수 있는 일들
cursor.execute("SELECT 1 + 1")  # SQL 실행
result = cursor.fetchone()       # 결과 가져오기
print(f"1 + 1 = {result[0]}")    # 출력: 1 + 1 = 2

cursor.close()
conn.close()

커서가 뭐예요? 🤔

  • 데이터베이스와 대화하는 “포인터”예요
  • SQL 명령을 실행하고 결과를 가져와요
  • 엑셀에서 셀을 선택하는 마우스 커서와 비슷해요!

2.3 연결과 커서의 관계

graph LR
    A[Python 프로그램] -->|1. connect| B[Connection 객체]
    B -->|2. cursor| C[Cursor 객체]
    C -->|3. execute| D[SQL 명령]
    D -->|4. 결과| E[데이터베이스]
    E -->|5. fetch| C
    C -->|6. 반환| A
    B -->|7. commit/close| E

2.4 기본 사용 패턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import sqlite3

# 기본 패턴 (항상 이렇게!)
def database_operation():
    # 1. 연결
    conn = sqlite3.connect("my_database.db")

    try:
        # 2. 커서 생성
        cursor = conn.cursor()

        # 3. SQL 실행
        cursor.execute("SELECT 'Hello, SQLite!'")
        result = cursor.fetchone()
        print(result[0])

        # 4. 변경사항 저장
        conn.commit()

    except sqlite3.Error as e:
        # 에러 발생 시 롤백
        conn.rollback()
        print(f"❌ 에러: {e}")

    finally:
        # 5. 항상 연결 종료
        conn.close()

database_operation()

출력:

1
Hello, SQLite!

2.5 Context Manager 사용 (권장!)

1
2
3
4
5
6
7
8
9
10
11
import sqlite3

# with 문 사용 (자동으로 commit/close)
with sqlite3.connect("my_database.db") as conn:
    cursor = conn.cursor()
    cursor.execute("SELECT 'Hello!'")
    result = cursor.fetchone()
    print(result[0])
    # conn.commit()과 conn.close() 자동!

print("✅ 안전하게 종료됨")

장점:

  • commit(), close() 자동 호출
  • 에러 발생 시 자동 rollback()
  • 코드가 깔끔해요!

💡 실무 팁: 항상 with 문을 사용하세요! 연결을 안전하게 관리해줘요!


🎯 학습 목표 3: sqlite3 모듈 핵심 메서드 익히기

한 줄 설명

execute, commit, fetch = SQL 실행의 3단 콤보 🥊

데이터베이스 작업의 핵심 메서드들을 마스터해봐요!

3.1 execute() - SQL 실행

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import sqlite3

conn = sqlite3.connect("test.db")
cursor = conn.cursor()

# 1. 단일 SQL 실행
cursor.execute("""
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY,
        name TEXT,
        age INTEGER
    )
""")

# 2. 파라미터와 함께 실행 (안전한 방법!)
cursor.execute(
    "INSERT INTO users (id, name, age) VALUES (?, ?, ?)",
    (1, "김철수", 25)
)

# 3. 이름 기반 파라미터
cursor.execute(
    "INSERT INTO users (id, name, age) VALUES (:id, :name, :age)",
    {"id": 2, "name": "이영희", "age": 30}
)

conn.commit()
conn.close()

파라미터 사용이 중요한 이유:

1
2
3
4
5
6
7
8
# ❌ 위험한 방법 (SQL Injection 공격 가능!)
user_input = "Alice'; DROP TABLE users; --"
cursor.execute(f"INSERT INTO users (name) VALUES ('{user_input}')")
# 💥 테이블이 삭제될 수 있어요!

# ✅ 안전한 방법
cursor.execute("INSERT INTO users (name) VALUES (?)", (user_input,))
# 😊 자동으로 이스케이프 처리!

3.2 executemany() - 여러 행 삽입

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import sqlite3

conn = sqlite3.connect("test.db")
cursor = conn.cursor()

# 테이블 생성
cursor.execute("""
    CREATE TABLE IF NOT EXISTS products (
        id INTEGER PRIMARY KEY,
        name TEXT,
        price REAL
    )
""")

# 여러 데이터 한 번에 삽입
products = [
    (1, "노트북", 1200000),
    (2, "마우스", 15000),
    (3, "키보드", 50000),
    (4, "모니터", 300000)
]

cursor.executemany(
    "INSERT OR REPLACE INTO products VALUES (?, ?, ?)",
    products
)

conn.commit()
print(f"{len(products)}개 상품 추가됨")

conn.close()

출력:

1
✅ 4개 상품 추가됨

💡 성능 팁: 100개 이상 데이터는 executemany()가 훨씬 빨라요!

3.3 fetch 메서드들 - 결과 가져오기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import sqlite3

conn = sqlite3.connect("test.db")
cursor = conn.cursor()

# 테스트 데이터
cursor.execute("CREATE TABLE IF NOT EXISTS numbers (value INTEGER)")
cursor.executemany("INSERT INTO numbers VALUES (?)", [(i,) for i in range(1, 6)])
conn.commit()

# 1. fetchone() - 한 개만
cursor.execute("SELECT * FROM numbers")
first = cursor.fetchone()
print(f"첫 번째: {first}")  # (1,)

# 2. fetchmany(n) - n개만
cursor.execute("SELECT * FROM numbers")
some = cursor.fetchmany(3)
print(f"3개: {some}")  # [(1,), (2,), (3,)]

# 3. fetchall() - 모두
cursor.execute("SELECT * FROM numbers")
all_rows = cursor.fetchall()
print(f"모두: {all_rows}")  # [(1,), (2,), (3,), (4,), (5,)]

conn.close()

출력:

1
2
3
첫 번째: (1,)
3개: [(1,), (2,), (3,)]
모두: [(1,), (2,), (3,), (4,), (5,)]

3.4 commit() vs rollback()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import sqlite3

conn = sqlite3.connect("test.db")
cursor = conn.cursor()

cursor.execute("CREATE TABLE IF NOT EXISTS accounts (id INTEGER, balance REAL)")
cursor.execute("INSERT INTO accounts VALUES (1, 10000)")
conn.commit()

# 시나리오: 돈 이체
try:
    # A 계좌에서 5000원 빼기
    cursor.execute("UPDATE accounts SET balance = balance - 5000 WHERE id = 1")

    # 오류 발생!
    raise Exception("네트워크 오류!")

    # B 계좌에 5000원 추가 (실행 안 됨)
    cursor.execute("UPDATE accounts SET balance = balance + 5000 WHERE id = 2")

    conn.commit()  # 모두 성공했을 때만

except Exception as e:
    print(f"⚠️ 에러 발생: {e}")
    conn.rollback()  # 모든 변경사항 취소!
    print("🔄 롤백 완료 (이체 취소)")

# 확인
cursor.execute("SELECT balance FROM accounts WHERE id = 1")
print(f"잔액: {cursor.fetchone()[0]}")  # 10000원 유지

conn.close()

출력:

1
2
3
⚠️ 에러 발생: 네트워크 오류!
🔄 롤백 완료 (이체 취소)
잔액: 10000.0원

💡 핵심: commit()은 저장, rollback()은 취소!

3.5 row_factory - 결과를 딕셔너리로!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import sqlite3

conn = sqlite3.connect("test.db")

# Row 팩토리 설정
conn.row_factory = sqlite3.Row

cursor = conn.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER, name TEXT, age INTEGER)")
cursor.execute("INSERT INTO users VALUES (1, 'Alice', 25)")
conn.commit()

# 결과를 딕셔너리처럼 사용!
cursor.execute("SELECT * FROM users")
row = cursor.fetchone()

print(f"ID: {row['id']}")      # 딕셔너리처럼
print(f"이름: {row['name']}")
print(f"나이: {row['age']}")

# 또는 인덱스로도
print(f"첫 번째 값: {row[0]}")

conn.close()

출력:

1
2
3
4
ID: 1
이름: Alice
나이: 25
첫 번째 값: 1

🎯 학습 목표 4: DB 브라우저 도구 활용하기

한 줄 설명

DB Browser for SQLite = 데이터베이스를 눈으로 보기 👁️🗄️

GUI 도구로 데이터를 시각적으로 확인하고 관리할 수 있어요!

4.1 DB Browser 소개

DB Browser for SQLite는 무료 오픈소스 SQLite 관리 도구예요!

주요 기능:

  • 📊 테이블 구조 시각화
  • 🔍 데이터 검색 및 필터링
  • ✏️ 데이터 직접 편집
  • 📝 SQL 쿼리 실행
  • 📈 데이터베이스 통계

4.2 설치 방법

1
2
3
4
5
6
7
8
# macOS (Homebrew)
brew install --cask db-browser-for-sqlite

# Windows (Chocolatey)
choco install sqlitebrowser

# 또는 공식 사이트에서 다운로드
# https://sqlitebrowser.org/

설치 후 확인:

  1. 프로그램 실행
  2. File > Open Database
  3. Python으로 만든 .db 파일 열기

4.3 Python과 DB Browser 함께 사용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import sqlite3

# 1. Python으로 데이터베이스 생성
conn = sqlite3.connect("school.db")
cursor = conn.cursor()

# 2. 테이블 생성
cursor.execute("""
    CREATE TABLE IF NOT EXISTS students (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        grade INTEGER,
        score REAL
    )
""")

# 3. 샘플 데이터 추가
students = [
    ("김철수", 1, 85.5),
    ("이영희", 2, 92.0),
    ("박민수", 1, 78.5),
    ("최지영", 3, 95.0),
    ("정다은", 2, 88.5)
]

cursor.executemany(
    "INSERT INTO students (name, grade, score) VALUES (?, ?, ?)",
    students
)

conn.commit()
conn.close()

print("✅ school.db 생성 완료!")
print("👉 DB Browser로 열어보세요!")

DB Browser에서 확인하기:

  1. Browse Data 탭 클릭
  2. students 테이블 선택
  3. 데이터 확인!

4.4 유용한 대체 도구들

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 터미널에서 SQLite 직접 사용
"""
$ sqlite3 my_database.db

sqlite> .tables              # 테이블 목록
sqlite> .schema students     # 테이블 구조
sqlite> SELECT * FROM students;
sqlite> .quit
"""

# Python 대화형 쉘에서 확인
import sqlite3
conn = sqlite3.connect("my_database.db")
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
print(cursor.fetchall())  # 테이블 목록

4.5 실전 예제: 데이터 시각화와 분석

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import sqlite3
from datetime import datetime, timedelta
import random

# 샘플 판매 데이터 생성
conn = sqlite3.connect("sales.db")
cursor = conn.cursor()

cursor.execute("""
    CREATE TABLE IF NOT EXISTS sales (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        date TEXT,
        product TEXT,
        quantity INTEGER,
        price REAL
    )
""")

# 최근 30일 판매 데이터
products = ["노트북", "마우스", "키보드", "모니터"]
start_date = datetime.now() - timedelta(days=30)

sales_data = []
for i in range(100):
    date = (start_date + timedelta(days=random.randint(0, 29))).strftime("%Y-%m-%d")
    product = random.choice(products)
    quantity = random.randint(1, 5)
    price = random.randint(10000, 1000000)
    sales_data.append((date, product, quantity, price))

cursor.executemany(
    "INSERT INTO sales (date, product, quantity, price) VALUES (?, ?, ?, ?)",
    sales_data
)

# 분석 쿼리
print("📊 판매 분석 리포트\n")

# 1. 총 매출
cursor.execute("SELECT SUM(quantity * price) FROM sales")
total = cursor.fetchone()[0]
print(f"총 매출: {total:,}")

# 2. 상품별 판매량
cursor.execute("""
    SELECT product, SUM(quantity) as total_qty
    FROM sales
    GROUP BY product
    ORDER BY total_qty DESC
""")
print("\n상품별 판매량:")
for row in cursor.fetchall():
    print(f"  {row[0]}: {row[1]}")

# 3. 일별 매출 (상위 5일)
cursor.execute("""
    SELECT date, SUM(quantity * price) as daily_sales
    FROM sales
    GROUP BY date
    ORDER BY daily_sales DESC
    LIMIT 5
""")
print("\n매출 상위 5일:")
for row in cursor.fetchall():
    print(f"  {row[0]}: {row[1]:,}")

conn.commit()
conn.close()

print("\n✅ sales.db 생성 완료!")
print("👉 DB Browser로 열어서 더 자세히 분석해보세요!")

출력 예시:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
📊 판매 분석 리포트

총 매출: 45,678,000원

상품별 판매량:
  노트북: 62개
  모니터: 58개
  키보드: 55개
  마우스: 51개

매출 상위 5일:
  2025-05-05: 3,456,000원
  2025-04-28: 2,987,000원
  2025-05-10: 2,654,000원
  2025-04-22: 2,345,000원
  2025-05-01: 2,123,000원

✅ sales.db 생성 완료!
👉 DB Browser로 열어서 더 자세히 분석해보세요!

💡 실전 팁 & 주의사항

✅ 꼭 기억하세요!

  1. 항상 with 문 사용
1
2
3
4
5
6
7
8
9
# ✅ 권장
with sqlite3.connect("db.db") as conn:
    cursor = conn.cursor()
    # 작업 수행

# ❌ 비권장 (close 잊어먹을 수 있음)
conn = sqlite3.connect("db.db")
# 작업 수행
conn.close()  # 이거 까먹으면?
  1. 파라미터 바인딩 필수
1
2
3
4
5
6
# ✅ 안전
name = user_input
cursor.execute("SELECT * FROM users WHERE name = ?", (name,))

# ❌ 위험 (SQL Injection)
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")
  1. commit 필수 (SELECT 제외)
1
2
3
# INSERT, UPDATE, DELETE 후엔 꼭 commit!
cursor.execute("INSERT INTO users VALUES (1, 'Alice')")
conn.commit()  # ← 이거 없으면 저장 안 됨!

⚠️ 흔한 실수

  1. 커서 재사용 문제
1
2
3
4
5
6
7
8
9
10
11
# ❌ 잘못된 예
cursor.execute("SELECT * FROM users")
for row in cursor:
    cursor.execute("INSERT INTO logs VALUES (?)", (row[0],))
    # 💥 에러! cursor를 두 번 사용 중

# ✅ 올바른 예
cursor.execute("SELECT * FROM users")
users = cursor.fetchall()  # 먼저 전부 가져오기
for row in users:
    cursor.execute("INSERT INTO logs VALUES (?)", (row[0],))
  1. 파일 경로 실수
1
2
3
4
5
6
7
# 현재 위치 확인
import os
print(f"현재 디렉토리: {os.getcwd()}")

# 상대 경로 vs 절대 경로
conn1 = sqlite3.connect("test.db")  # 현재 디렉토리
conn2 = sqlite3.connect("/Users/name/test.db")  # 절대 경로
  1. 메모리 DB 휘발성
1
2
3
4
5
# ⚠️ 메모리 DB는 프로그램 종료 시 사라져요!
conn = sqlite3.connect(":memory:")
# 데이터 추가...
conn.close()
# 💨 모든 데이터 사라짐!

🧪 연습 문제

문제 1: 도서 관리 시스템

도서관의 책 정보를 관리하는 프로그램을 작성하세요.

요구사항:

  • books 테이블 생성 (id, title, author, year, available)
  • 책 추가 기능
  • 대출 가능한 책 목록 조회
  • 저자로 책 검색
💡 힌트

단계별 힌트:

  1. CREATE TABLE books (id INTEGER PRIMARY KEY AUTOINCREMENT, ...)
  2. available은 BOOLEAN 대신 INTEGER (0/1) 사용
  3. INSERT INTO books (title, author, year, available) VALUES (?, ?, ?, ?)
  4. SELECT * FROM books WHERE available = 1
  5. SELECT * FROM books WHERE author LIKE ?

핵심 SQL:

1
2
3
4
5
6
7
CREATE TABLE books (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    author TEXT NOT NULL,
    year INTEGER,
    available INTEGER DEFAULT 1
)
정답 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import sqlite3

class LibrarySystem:
    def __init__(self, db_name="library.db"):
        self.conn = sqlite3.connect(db_name)
        self.conn.row_factory = sqlite3.Row
        self.cursor = self.conn.cursor()
        self._create_table()

    def _create_table(self):
        """도서 테이블 생성"""
        self.cursor.execute("""
            CREATE TABLE IF NOT EXISTS books (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                title TEXT NOT NULL,
                author TEXT NOT NULL,
                year INTEGER,
                available INTEGER DEFAULT 1
            )
        """)
        self.conn.commit()

    def add_book(self, title, author, year):
        """책 추가"""
        self.cursor.execute(
            "INSERT INTO books (title, author, year) VALUES (?, ?, ?)",
            (title, author, year)
        )
        self.conn.commit()
        print(f"'{title}' 추가됨")

    def list_available_books(self):
        """대출 가능한 책 목록"""
        self.cursor.execute(
            "SELECT * FROM books WHERE available = 1 ORDER BY title"
        )
        books = self.cursor.fetchall()

        if not books:
            print("📚 대출 가능한 책이 없습니다.")
            return

        print("\n📚 대출 가능한 책:")
        for book in books:
            print(f"  [{book['id']}] {book['title']} - {book['author']} ({book['year']})")

    def search_by_author(self, author):
        """저자로 검색"""
        self.cursor.execute(
            "SELECT * FROM books WHERE author LIKE ? ORDER BY year",
            (f"%{author}%",)
        )
        books = self.cursor.fetchall()

        if not books:
            print(f"🔍 '{author}' 저자의 책을 찾을 수 없습니다.")
            return

        print(f"\n🔍 '{author}' 검색 결과:")
        for book in books:
            status = "대출 가능" if book['available'] else "대출 중"
            print(f"  [{book['id']}] {book['title']} ({book['year']}) - {status}")

    def borrow_book(self, book_id):
        """책 대출"""
        self.cursor.execute(
            "UPDATE books SET available = 0 WHERE id = ? AND available = 1",
            (book_id,)
        )
        if self.cursor.rowcount > 0:
            self.conn.commit()
            print(f"📖 책을 대출했습니다.")
        else:
            print(f"❌ 대출할 수 없습니다. (이미 대출 중이거나 존재하지 않는 책)")

    def return_book(self, book_id):
        """책 반납"""
        self.cursor.execute(
            "UPDATE books SET available = 1 WHERE id = ? AND available = 0",
            (book_id,)
        )
        if self.cursor.rowcount > 0:
            self.conn.commit()
            print(f"📚 책을 반납했습니다.")
        else:
            print(f"❌ 반납할 수 없습니다. (이미 반납되었거나 존재하지 않는 책)")

    def close(self):
        """연결 종료"""
        self.conn.close()

# 사용 예시
library = LibrarySystem()

# 책 추가
library.add_book("파이썬 기초", "김철수", 2023)
library.add_book("데이터베이스 입문", "이영희", 2024)
library.add_book("파이썬 심화", "김철수", 2024)

# 대출 가능한 책 목록
library.list_available_books()

# 저자로 검색
library.search_by_author("김철수")

# 책 대출
library.borrow_book(1)
library.list_available_books()

# 책 반납
library.return_book(1)
library.list_available_books()

library.close()

출력:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
✅ '파이썬 기초' 추가됨
✅ '데이터베이스 입문' 추가됨
✅ '파이썬 심화' 추가됨

📚 대출 가능한 책:
  [2] 데이터베이스 입문 - 이영희 (2024)
  [1] 파이썬 기초 - 김철수 (2023)
  [3] 파이썬 심화 - 김철수 (2024)

🔍 '김철수' 검색 결과:
  [1] 파이썬 기초 (2023) - 대출 가능
  [3] 파이썬 심화 (2024) - 대출 가능

📖 책을 대출했습니다.

📚 대출 가능한 책:
  [2] 데이터베이스 입문 - 이영희 (2024)
  [3] 파이썬 심화 - 김철수 (2024)

📚 책을 반납했습니다.

📚 대출 가능한 책:
  [2] 데이터베이스 입문 - 이영희 (2024)
  [1] 파이썬 기초 - 김철수 (2023)
  [3] 파이썬 심화 - 김철수 (2024)

문제 2: 트랜잭션 연습

은행 계좌 이체 시스템을 만들어, commit()rollback()을 연습하세요.

요구사항:

  • A 계좌에서 B 계좌로 송금
  • 잔액 부족 시 롤백
  • 성공 시 커밋
💡 힌트

단계별 힌트:

  1. accounts 테이블 생성 (id, name, balance)
  2. try-except로 트랜잭션 처리
  3. 잔액 확인 후 부족하면 Exception 발생
  4. 성공 시 commit(), 실패 시 rollback()

핵심 로직:

1
2
3
4
5
6
7
try:
    # A 계좌 잔액 확인
    # A 계좌에서 출금
    # B 계좌에 입금
    conn.commit()
except:
    conn.rollback()
정답 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import sqlite3

class BankSystem:
    def __init__(self, db_name="bank.db"):
        self.conn = sqlite3.connect(db_name)
        self.cursor = self.conn.cursor()
        self._create_table()
        self._init_accounts()

    def _create_table(self):
        """계좌 테이블 생성"""
        self.cursor.execute("""
            CREATE TABLE IF NOT EXISTS accounts (
                id INTEGER PRIMARY KEY,
                name TEXT NOT NULL,
                balance REAL NOT NULL
            )
        """)
        self.conn.commit()

    def _init_accounts(self):
        """초기 계좌 생성"""
        self.cursor.execute("SELECT COUNT(*) FROM accounts")
        if self.cursor.fetchone()[0] == 0:
            accounts = [
                (1, "김철수", 100000),
                (2, "이영희", 50000),
                (3, "박민수", 200000)
            ]
            self.cursor.executemany(
                "INSERT INTO accounts VALUES (?, ?, ?)",
                accounts
            )
            self.conn.commit()
            print("✅ 초기 계좌 생성됨")

    def show_balances(self):
        """모든 계좌 잔액 조회"""
        self.cursor.execute("SELECT * FROM accounts ORDER BY id")
        accounts = self.cursor.fetchall()

        print("\n💰 계좌 잔액:")
        for acc in accounts:
            print(f"  [{acc[0]}] {acc[1]}: {acc[2]:,}")

    def transfer(self, from_id, to_id, amount):
        """계좌 이체"""
        print(f"\n🔄 {from_id}번 → {to_id}번 계좌로 {amount:,}원 이체 시도...")

        try:
            # 1. 출금 계좌 잔액 확인
            self.cursor.execute(
                "SELECT name, balance FROM accounts WHERE id = ?",
                (from_id,)
            )
            from_account = self.cursor.fetchone()

            if not from_account:
                raise Exception(f"{from_id}번 계좌가 존재하지 않습니다")

            if from_account[1] < amount:
                raise Exception(f"잔액 부족 (잔액: {from_account[1]:,}원)")

            # 2. 입금 계좌 확인
            self.cursor.execute(
                "SELECT name FROM accounts WHERE id = ?",
                (to_id,)
            )
            to_account = self.cursor.fetchone()

            if not to_account:
                raise Exception(f"{to_id}번 계좌가 존재하지 않습니다")

            # 3. 출금
            self.cursor.execute(
                "UPDATE accounts SET balance = balance - ? WHERE id = ?",
                (amount, from_id)
            )

            # 4. 입금
            self.cursor.execute(
                "UPDATE accounts SET balance = balance + ? WHERE id = ?",
                (amount, to_id)
            )

            # 5. 커밋
            self.conn.commit()
            print(f"✅ 이체 성공!")
            print(f"   {from_account[0]}{to_account[0]}: {amount:,}")

        except Exception as e:
            # 롤백
            self.conn.rollback()
            print(f"❌ 이체 실패: {e}")
            print(f"🔄 모든 변경사항 롤백됨")

    def close(self):
        """연결 종료"""
        self.conn.close()

# 사용 예시
bank = BankSystem()

# 초기 잔액 확인
bank.show_balances()

# 정상 이체
bank.transfer(1, 2, 30000)
bank.show_balances()

# 잔액 부족 이체 시도
bank.transfer(2, 3, 100000)
bank.show_balances()

# 존재하지 않는 계좌로 이체 시도
bank.transfer(1, 99, 10000)
bank.show_balances()

bank.close()

출력:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
✅ 초기 계좌 생성됨

💰 계좌 잔액:
  [1] 김철수: 100,000원
  [2] 이영희: 50,000원
  [3] 박민수: 200,000원

🔄 1번 → 2번 계좌로 30,000원 이체 시도...
✅ 이체 성공!
   김철수 → 이영희: 30,000원

💰 계좌 잔액:
  [1] 김철수: 70,000원
  [2] 이영희: 80,000원
  [3] 박민수: 200,000원

🔄 2번 → 3번 계좌로 100,000원 이체 시도...
❌ 이체 실패: 잔액 부족 (잔액: 80,000원)
🔄 모든 변경사항 롤백됨

💰 계좌 잔액:
  [1] 김철수: 70,000원
  [2] 이영희: 80,000원
  [3] 박민수: 200,000원

🔄 1번 → 99번 계좌로 10,000원 이체 시도...
❌ 이체 실패: 99번 계좌가 존재하지 않습니다
🔄 모든 변경사항 롤백됨

💰 계좌 잔액:
  [1] 김철수: 70,000원
  [2] 이영희: 80,000원
  [3] 박민수: 200,000원

📝 오늘 배운 내용 정리

  1. SQLite 특징: 서버 불필요, 파일 기반, Python 내장
  2. Connection & Cursor: 연결 = DB 파일 열기, 커서 = 작업 도구
  3. 핵심 메서드: execute(), executemany(), fetchone/many/all(), commit(), rollback()
  4. DB Browser: GUI 도구로 데이터베이스 시각화 및 관리

🔗 관련 자료


📚 이전 학습

Day 71: 데이터베이스와 SQL 소개 ⭐⭐⭐

어제는 데이터베이스의 개념과 필요성, 관계형 DB의 기초를 배웠어요!

📚 다음 학습

Day 73: SQL 기본 - CREATE, INSERT ⭐⭐⭐

내일은 본격적으로 SQL 명령어를 배워서 테이블을 만들고 데이터를 추가해요!


“늦었다고 생각할 때가 가장 빠른 순간이에요!” 🚀

Day 72/100 Phase 8: 데이터베이스와 SQL #100DaysOfPython
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.