포스트

[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()
# 💨 모든 데이터 사라짐!

🧪 연습 문제

도서 관리 문제는 LibrarySystem의 테이블 생성 → 한 권 추가 → 대출 → 반납 순서로 확인하세요. 이체 문제는 두 계좌의 잔액을 이체 전후로 적어두고, 정상 이체와 잔액 부족·존재하지 않는 계좌를 구분해 비교합니다. 실패한 이체에서 양쪽 잔액이 함께 원상태로 유지되는지가 rollback()을 이해하는 기준입니다.

library.db와 bank.db는 파일로 남으므로 재실행 결과는 이전 실행의 데이터에도 영향을 받습니다. 초기 예시와 숫자가 다르면 먼저 같은 파일을 다시 열었는지 확인하고, 새 실습은 별도 디렉터리에서 진행하면 기존 기록과 혼동하지 않을 수 있어요.

문제 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 라이센스를 따릅니다.