diff --git a/project/CALLCENTER_APP/.idea/.gitignore b/project/CALLCENTER_APP/.idea/.gitignore new file mode 100644 index 0000000..93bca08 --- /dev/null +++ b/project/CALLCENTER_APP/.idea/.gitignore @@ -0,0 +1,10 @@ +# 디폴트 무시된 파일 +/shelf/ +/workspace.xml +# 에디터 기반 HTTP 클라이언트 요청 +/httpRequests/ +# 쿼리 파일을 포함한 무시된 디폴트 폴더 +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/project/CALLCENTER_APP/.idea/CALLCENTER_APP.iml b/project/CALLCENTER_APP/.idea/CALLCENTER_APP.iml new file mode 100644 index 0000000..97aee2d --- /dev/null +++ b/project/CALLCENTER_APP/.idea/CALLCENTER_APP.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/project/CALLCENTER_APP/.idea/dataSources.xml b/project/CALLCENTER_APP/.idea/dataSources.xml new file mode 100644 index 0000000..9961c60 --- /dev/null +++ b/project/CALLCENTER_APP/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/db/callcenter.db + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/project/CALLCENTER_APP/.idea/inspectionProfiles/profiles_settings.xml b/project/CALLCENTER_APP/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/project/CALLCENTER_APP/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/project/CALLCENTER_APP/.idea/modules.xml b/project/CALLCENTER_APP/.idea/modules.xml new file mode 100644 index 0000000..a31569c --- /dev/null +++ b/project/CALLCENTER_APP/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/project/CALLCENTER_APP/.idea/pyLspTools.xml b/project/CALLCENTER_APP/.idea/pyLspTools.xml new file mode 100644 index 0000000..e202fc5 --- /dev/null +++ b/project/CALLCENTER_APP/.idea/pyLspTools.xml @@ -0,0 +1,34 @@ + + + + + + \ No newline at end of file diff --git a/project/CALLCENTER_APP/.idea/vcs.xml b/project/CALLCENTER_APP/.idea/vcs.xml new file mode 100644 index 0000000..b2bdec2 --- /dev/null +++ b/project/CALLCENTER_APP/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/project/CALLCENTER_APP/backend/ai/embedding.py b/project/CALLCENTER_APP/backend/ai/embedding.py new file mode 100644 index 0000000..4f1a98a --- /dev/null +++ b/project/CALLCENTER_APP/backend/ai/embedding.py @@ -0,0 +1,9 @@ +from langchain_ibm import WatsonxEmbeddings +from backend.config.settings import settings + +watson_embedding = WatsonxEmbeddings( + model_id="ibm/granite-embedding-278m-multilingual", + url=f"{settings.watsonx_url}", + api_key=f"{settings.watsonx_api_key}", + project_id=f"{settings.watsonx_project_id}", +) \ No newline at end of file diff --git a/project/CALLCENTER_APP/backend/ai/llm.py b/project/CALLCENTER_APP/backend/ai/llm.py new file mode 100644 index 0000000..5f52f60 --- /dev/null +++ b/project/CALLCENTER_APP/backend/ai/llm.py @@ -0,0 +1,17 @@ +from langchain_ibm import ChatWatsonx +from backend.config.settings import settings +from langchain_openai import ChatOpenAI + +watson_llm = ChatWatsonx( + model_id="ibm/granite-4-h-small", + url=f"{settings.watsonx_url}", + api_key=f"{settings.watsonx_api_key}", + project_id=f"{settings.watsonx_project_id}", + max_tokens=2000, +) + +hugging_llm = ChatOpenAI( + base_url="https://router.huggingface.co/v1", + api_key=f"{settings.hf_token}", + model_name="Qwen/Qwen3-8B:nscale", +) \ No newline at end of file diff --git a/project/CALLCENTER_APP/backend/config/settings.py b/project/CALLCENTER_APP/backend/config/settings.py new file mode 100644 index 0000000..1d35237 --- /dev/null +++ b/project/CALLCENTER_APP/backend/config/settings.py @@ -0,0 +1,12 @@ +from pydantic import Field +from pydantic_settings import BaseSettings, SettingsConfigDict + +class Settings(BaseSettings): + model_config = SettingsConfigDict(env_file="backend/.env", extra="ignore") + # 사용할 모델 + watsonx_api_key: str = Field(alias="WATSONX_API_KEY") + watsonx_project_id: str = Field(alias="WATSONX_PROJECT_ID") + watsonx_url: str = Field(alias="WATSONX_URL") + hf_token: str = Field(alias="HF_TOKEN") + +settings = Settings() \ No newline at end of file diff --git a/project/CALLCENTER_APP/backend/main.py b/project/CALLCENTER_APP/backend/main.py new file mode 100644 index 0000000..64b0b27 --- /dev/null +++ b/project/CALLCENTER_APP/backend/main.py @@ -0,0 +1,34 @@ +from fastapi import FastAPI +from starlette.staticfiles import StaticFiles +from contextlib import asynccontextmanager + +from backend.repository.db_init import Base, SessionLocal, engine +from backend.routers.call_router import router as call_router +from backend.repository.seed import seed_customers + +@asynccontextmanager +async def lifespan(app: FastAPI): + print("서버 시작") + + # Base 에 등록된 모든 모델에 테이블 자동 생성 + Base.metadata.create_all(bind=engine) + print("[DB] 테이블 생성 완료 (또는 이미 존재)") + + db = SessionLocal() + try: + # 기본 user 데이터 삽입 + seed_customers(db) + finally: + db.close() + + yield + print("서버 종료") + +# app = FastAPI() +app = FastAPI(title="상담 LLM", version="1.0", lifespan=lifespan) + +# static 폴더 지정 +app.mount("/static", StaticFiles(directory="backend/static"), name="static") + +# 라우터 등록 +app.include_router(call_router) \ No newline at end of file diff --git a/project/CALLCENTER_APP/backend/prompts/all_prompt.py b/project/CALLCENTER_APP/backend/prompts/all_prompt.py new file mode 100644 index 0000000..757b8fb --- /dev/null +++ b/project/CALLCENTER_APP/backend/prompts/all_prompt.py @@ -0,0 +1,28 @@ +# 요약 프롬프트 생성 +SUMMARY_SYSTEM_PROMPT = """ +당신은 콜센터 상담 기록 분석 전문가입니다. + +다음 항목을 추출하세요. + +1. summary + - 상담 내용을 한 문장으로 요약 + +2. keywords + - 상담 내용에서 중요한 키워드 3~5개 추출 + +3. category + - 상담 유형 + - 예: 장애신고, 기술지원, 요금문의, 해지문의, 서비스변경 + +4. sentiment + - 상담 내용의 감정 분석 결과 = 예: 긍정(positive), 부정(negative), 중립(neutral) 중 하나 + +5. action_items: + - 상담 후 필요한 후속 조치 + +6. customer_issue + - 고객이 겪은 문제 + +7. resolution + - 상담사가 제공한 해결 방법 +""" \ No newline at end of file diff --git a/project/CALLCENTER_APP/backend/repository/db_init.py b/project/CALLCENTER_APP/backend/repository/db_init.py new file mode 100644 index 0000000..a9dec18 --- /dev/null +++ b/project/CALLCENTER_APP/backend/repository/db_init.py @@ -0,0 +1,23 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import DeclarativeBase, sessionmaker +from pathlib import Path + +Path("db").mkdir(parents=True, exist_ok=True) + +# echo sql 구문 출력시키는 +engine = create_engine('sqlite:///db/callcenter.db', echo=True) + +# 세션 팩토리 생성 +SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False) + +# Base = declarative_Base() +class Base(DeclarativeBase): + pass + +# session 을 다른 모듈에서 사용할 수 있도록 제공 +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() \ No newline at end of file diff --git a/project/CALLCENTER_APP/backend/repository/models.py b/project/CALLCENTER_APP/backend/repository/models.py new file mode 100644 index 0000000..b2c10ce --- /dev/null +++ b/project/CALLCENTER_APP/backend/repository/models.py @@ -0,0 +1,30 @@ +from backend.repository.db_init import Base +from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy import DateTime, func, String, ForeignKey +from datetime import datetime + +class Customer(Base): + __tablename__ = 'customers' + + customer_id:Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + name:Mapped[str] = mapped_column(String(50), nullable=False) + phone:Mapped[str] = mapped_column(String(20), nullable=False) + + def __str__(self): + return f" 내 요금제 메뉴에서 변경 가능합니다. + +Q. 데이터 사용량은 어디서 확인하나요? + +A. 고객 포털 > 데이터 사용량 메뉴에서 확인 가능합니다. \ No newline at end of file diff --git a/project/CALLCENTER_APP/data/transcript.txt b/project/CALLCENTER_APP/data/transcript.txt new file mode 100644 index 0000000..17eb993 --- /dev/null +++ b/project/CALLCENTER_APP/data/transcript.txt @@ -0,0 +1,5 @@ +상담사: 안녕하세요. 고객센터입니다. 무엇을 도와드릴까요?\n고객: 몇 시간 전부터 인터넷 속도가 너무 느려서 답답합니다.\n상담사: 고객 확인을 위해 성함을 알려주시겠습니까?\n고객: 네, 홍길동입니다.\n상담사: 감사합니다. 본인 확인을 위해 PIN 번호도 알려주시겠습니까?\n고객: 지금은 PIN 번호를 가지고 있지 않습니다.\n상담사: 괜찮습니다. 우선 제가 회선 상태를 확인하고 조정해보겠습니다. 잠시만 기다려 주세요.\n(잠시 후)\n상담사: 회선 설정을 일부 조정했습니다. 현재 인터넷 속도를 다시 확인해보시겠습니까?\n고객: 잠시만요... 네, 훨씬 빨라졌네요.\n상담사: 다행입니다. 상담 종료 후 설문조사가 발송될 예정입니다. 설문은 인터넷 서비스 품질이 아니라 상담 서비스에 대한 평가입니다.\n고객: 네, 알겠습니다.\n상담사: 이용해 주셔서 감사합니다. 좋은 하루 보내세요.\n고객: 감사합니다. + +---- + +상담사: 안녕하세요. 인터넷 서비스 고객센터입니다. 성함과 PIN 번호를 알려주시겠습니까?\n고객: 홍길동이고 PIN은 1234입니다.\n상담사: 감사합니다. 어떤 문제로 연락주셨나요?\n고객: 어제부터 인터넷 속도가 너무 느립니다.\n상담사: 불편을 드려 죄송합니다. 연결 상태를 확인해보겠습니다.\n(확인 중)\n상담사: 현재 고객님 지역의 회선 장애가 의심됩니다. 관련 부서에 장애 내용을 접수하겠습니다.\n고객: 네, 빨리 해결되면 좋겠네요.\n상담사: 최대한 신속히 처리하겠습니다. 신고해 주셔서 감사합니다. \ No newline at end of file diff --git a/project/CALLCENTER_APP/data/transcript1.txt b/project/CALLCENTER_APP/data/transcript1.txt new file mode 100644 index 0000000..119ba55 --- /dev/null +++ b/project/CALLCENTER_APP/data/transcript1.txt @@ -0,0 +1,27 @@ +상담사: 안녕하세요. 고객센터입니다. 무엇을 도와드릴까요? + +고객: 몇 시간 전부터 인터넷 속도가 너무 느려서 답답합니다. + +상담사: 고객 확인을 위해 성함을 알려주시겠습니까? + +고객: 네, 홍길동입니다. + +상담사: 감사합니다. 본인 확인을 위해 PIN 번호도 알려주시겠습니까? + +고객: 지금은 PIN 번호를 가지고 있지 않습니다. + +상담사: 괜찮습니다. 우선 제가 회선 상태를 확인하고 조정해보겠습니다. 잠시만 기다려 주세요. + +(잠시 후) + +상담사: 회선 설정을 일부 조정했습니다. 현재 인터넷 속도를 다시 확인해보시겠습니까? + +고객: 잠시만요... 네, 훨씬 빨라졌네요. + +상담사: 다행입니다. 상담 종료 후 설문조사가 발송될 예정입니다. 설문은 인터넷 서비스 품질이 아니라 상담 서비스에 대한 평가입니다. + +고객: 네, 알겠습니다. + +상담사: 이용해 주셔서 감사합니다. 좋은 하루 보내세요. + +고객: 감사합니다. \ No newline at end of file diff --git a/project/CALLCENTER_APP/data/transcript2.txt b/project/CALLCENTER_APP/data/transcript2.txt new file mode 100644 index 0000000..0274846 --- /dev/null +++ b/project/CALLCENTER_APP/data/transcript2.txt @@ -0,0 +1,17 @@ +상담사: 안녕하세요. 인터넷 서비스 고객센터입니다. 성함과 PIN 번호를 알려주시겠습니까? + +고객: 홍길동이고 PIN은 1234입니다. + +상담사: 감사합니다. 어떤 문제로 연락주셨나요? + +고객: 어제부터 인터넷 속도가 너무 느립니다. + +상담사: 불편을 드려 죄송합니다. 연결 상태를 확인해보겠습니다. + +(확인 중) + +상담사: 현재 고객님 지역의 회선 장애가 의심됩니다. 관련 부서에 장애 내용을 접수하겠습니다. + +고객: 네, 빨리 해결되면 좋겠네요. + +상담사: 최대한 신속히 처리하겠습니다. 신고해 주셔서 감사합니다. \ No newline at end of file diff --git a/project/CALLCENTER_APP/data/transcript3.txt b/project/CALLCENTER_APP/data/transcript3.txt new file mode 100644 index 0000000..72f4306 --- /dev/null +++ b/project/CALLCENTER_APP/data/transcript3.txt @@ -0,0 +1,29 @@ +상담사: 안녕하세요. 무엇을 도와드릴까요? + +고객: 어제부터 인터넷이 계속 끊어집니다. + +상담사: 공유기를 재부팅해 보셨나요? + +고객: 네. 이미 해봤는데 똑같습니다. + +상담사: 현재 공유기 상태 표시등은 어떻게 되어 있나요? + +고객: 전원등은 켜져 있고 인터넷 표시등은 깜빡입니다. + +상담사: 공유기 케이블 연결 상태를 확인해 주시겠습니까? + +고객: 네. 확인했는데 이상 없습니다. + +상담사: 그렇다면 공유기 초기화를 진행해보겠습니다. 초기화 방법을 알고 계신가요? + +고객: 아니요. + +상담사: 공유기 뒷면의 리셋 버튼을 10~15초 정도 눌러주세요. + +고객: 네. 지금 재시작 중입니다. + +(몇 분 후) + +고객: 이제 정상적으로 작동하는 것 같습니다. + +상담사: 다행입니다. 다른 문제가 있으시면 언제든 연락 주세요. \ No newline at end of file diff --git a/project/CALLCENTER_APP/data/transcript4.txt b/project/CALLCENTER_APP/data/transcript4.txt new file mode 100644 index 0000000..fa4fd3b --- /dev/null +++ b/project/CALLCENTER_APP/data/transcript4.txt @@ -0,0 +1,21 @@ +상담사: 안녕하세요. 고객센터입니다. 성함과 PIN 번호를 확인하겠습니다. + +고객: 홍길동이며 PIN은 1234입니다. + +상담사: 감사합니다. 어떤 문제가 있으신가요? + +고객: 인터넷이 느리고 가끔 끊깁니다. + +상담사: 불편을 드려 죄송합니다. 연결 상태를 확인하겠습니다. + +(잠시 후) + +상담사: 회선에 일시적인 문제가 있었으며 현재 초기화 처리를 완료했습니다. 다시 확인해보시겠습니까? + +고객: 네. 정상적으로 작동합니다. + +상담사: 다행입니다. 상담 후 설문조사가 발송될 예정이며 상담 서비스에 대한 평가입니다. + +고객: 알겠습니다. + +상담사: 감사합니다. 좋은 하루 보내세요. \ No newline at end of file diff --git a/project/CALLCENTER_APP/db/callcenter.db b/project/CALLCENTER_APP/db/callcenter.db new file mode 100644 index 0000000..aea8cd2 Binary files /dev/null and b/project/CALLCENTER_APP/db/callcenter.db differ diff --git a/project/CALLCENTER_APP/vectordb/8aa1a33e-b040-4aa6-8954-320af0dc9c67/data_level0.bin b/project/CALLCENTER_APP/vectordb/8aa1a33e-b040-4aa6-8954-320af0dc9c67/data_level0.bin new file mode 100644 index 0000000..337c499 Binary files /dev/null and b/project/CALLCENTER_APP/vectordb/8aa1a33e-b040-4aa6-8954-320af0dc9c67/data_level0.bin differ diff --git a/project/CALLCENTER_APP/vectordb/8aa1a33e-b040-4aa6-8954-320af0dc9c67/header.bin b/project/CALLCENTER_APP/vectordb/8aa1a33e-b040-4aa6-8954-320af0dc9c67/header.bin new file mode 100644 index 0000000..dff34e7 Binary files /dev/null and b/project/CALLCENTER_APP/vectordb/8aa1a33e-b040-4aa6-8954-320af0dc9c67/header.bin differ diff --git a/project/CALLCENTER_APP/vectordb/8aa1a33e-b040-4aa6-8954-320af0dc9c67/length.bin b/project/CALLCENTER_APP/vectordb/8aa1a33e-b040-4aa6-8954-320af0dc9c67/length.bin new file mode 100644 index 0000000..0307c63 Binary files /dev/null and b/project/CALLCENTER_APP/vectordb/8aa1a33e-b040-4aa6-8954-320af0dc9c67/length.bin differ diff --git a/project/CALLCENTER_APP/vectordb/8aa1a33e-b040-4aa6-8954-320af0dc9c67/link_lists.bin b/project/CALLCENTER_APP/vectordb/8aa1a33e-b040-4aa6-8954-320af0dc9c67/link_lists.bin new file mode 100644 index 0000000..e69de29 diff --git a/project/CALLCENTER_APP/vectordb/chroma.sqlite3 b/project/CALLCENTER_APP/vectordb/chroma.sqlite3 new file mode 100644 index 0000000..0380404 Binary files /dev/null and b/project/CALLCENTER_APP/vectordb/chroma.sqlite3 differ diff --git a/project/CREDIT_APP/.idea/dataSources.xml b/project/CREDIT_APP/.idea/dataSources.xml index b70d3a3..d853b46 100644 --- a/project/CREDIT_APP/.idea/dataSources.xml +++ b/project/CREDIT_APP/.idea/dataSources.xml @@ -15,5 +15,12 @@ jdbc:sqlite:$PROJECT_DIR$/db/users.db $ProjectFileDir$ + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/db/demo.db + $ProjectFileDir$ + \ No newline at end of file diff --git a/project/CREDIT_APP/.idea/db-forest-config.xml b/project/CREDIT_APP/.idea/db-forest-config.xml index 90ec0d8..1fa70d9 100644 --- a/project/CREDIT_APP/.idea/db-forest-config.xml +++ b/project/CREDIT_APP/.idea/db-forest-config.xml @@ -5,6 +5,7 @@ ---------------------------------------- 1:0:d34d9830-06d9-4e92-bd10-84567e5a4809 2:0:2d5d9d87-b30f-47d4-9d9d-61542cb64ea0 + 3:0:fab7f51e-43ea-4d26-b7f0-6be132dd06fb . \ No newline at end of file diff --git a/project/CREDIT_APP/db/demo.db b/project/CREDIT_APP/db/demo.db new file mode 100644 index 0000000..5b0589c Binary files /dev/null and b/project/CREDIT_APP/db/demo.db differ diff --git a/project/CREDIT_APP/db/users.db b/project/CREDIT_APP/db/users.db index 18cac5c..065e0cf 100644 Binary files a/project/CREDIT_APP/db/users.db and b/project/CREDIT_APP/db/users.db differ diff --git a/project/CREDIT_APP/sql.ipynb b/project/CREDIT_APP/sql.ipynb index 52625e7..0fcbf34 100644 --- a/project/CREDIT_APP/sql.ipynb +++ b/project/CREDIT_APP/sql.ipynb @@ -17,29 +17,19 @@ "metadata": { "collapsed": true, "ExecuteTime": { - "end_time": "2026-06-16T08:48:53.375278031Z", - "start_time": "2026-06-16T08:48:51.509377417Z" + "end_time": "2026-06-17T03:03:17.531519156Z", + "start_time": "2026-06-17T03:03:17.519312421Z" } }, - "source": "!pip install sqlalchemy", - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: sqlalchemy in ./.venv/lib/python3.12/site-packages (2.0.51)\n", - "Requirement already satisfied: greenlet>=1 in ./.venv/lib/python3.12/site-packages (from sqlalchemy) (3.5.1)\n", - "Requirement already satisfied: typing-extensions>=4.6.0 in ./.venv/lib/python3.12/site-packages (from sqlalchemy) (4.15.0)\n" - ] - } - ], - "execution_count": 1 + "source": "# !pip install sqlalchemy", + "outputs": [], + "execution_count": 36 }, { "metadata": { "ExecuteTime": { - "end_time": "2026-06-16T08:57:27.860045705Z", - "start_time": "2026-06-16T08:57:27.561065535Z" + "end_time": "2026-06-17T03:03:17.555640871Z", + "start_time": "2026-06-17T03:03:17.532414273Z" } }, "cell_type": "code", @@ -49,13 +39,19 @@ "from sqlalchemy import Column, Integer, String, create_engine\n", "from sqlalchemy.orm import declarative_base, sessionmaker\n", "\n", + "# 1.x 버전 코드\n", "# 데이터베이스 연결\n", - "engine = create_engine('sqlite:///db/users.db')\n", + "# 애플리케이션 당 하나만 만들어 사용\n", + "# echo=True : 실행 SQL 을 콘솔에 추력\n", + "engine = create_engine('sqlite:///db/users.db', echo=True)\n", "# engine = create_engine('sqlite:///users.db')\n", "\n", "# 모든 모델 크래스의 부모 클래스가 될 Base 객체 생성\n", "Base = declarative_base()\n", + "\n", + "# 데이터베이스랑 관련있는 클래스 : 모델 클래스\n", "class User(Base):\n", + " # 테이블 이름 지정\n", " __tablename__ = 'users'\n", "\n", " id = Column(Integer, primary_key=True)\n", @@ -65,20 +61,749 @@ " def __str__(self):\n", " return f\"\"\n", "\n", - "# 테이블 생성\n", + "# 테이블 생성 ( 없는 경우만 새로 생성됨 )\n", "Base.metadata.create_all(engine)" ], "id": "7a41f863de1efd48", - "outputs": [], - "execution_count": 2 + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-17 12:03:17,542 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,543 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"users\")\n", + "2026-06-17 12:03:17,545 INFO sqlalchemy.engine.Engine [raw sql] ()\n", + "2026-06-17 12:03:17,548 INFO sqlalchemy.engine.Engine COMMIT\n" + ] + } + ], + "execution_count": 37 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.576534965Z", + "start_time": "2026-06-17T03:03:17.557316858Z" + } + }, + "cell_type": "code", + "source": [ + "# 모든 테이블 삭제\n", + "Base.metadata.drop_all(engine)" + ], + "id": "28ae00aa8564cd68", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-17 12:03:17,561 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,563 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"users\")\n", + "2026-06-17 12:03:17,563 INFO sqlalchemy.engine.Engine [raw sql] ()\n", + "2026-06-17 12:03:17,565 INFO sqlalchemy.engine.Engine \n", + "DROP TABLE users\n", + "2026-06-17 12:03:17,566 INFO sqlalchemy.engine.Engine [no key 0.00051s] ()\n", + "2026-06-17 12:03:17,571 INFO sqlalchemy.engine.Engine COMMIT\n" + ] + } + ], + "execution_count": 38 }, { "metadata": {}, + "cell_type": "markdown", + "source": "#### text() : 원시 SQL 실행", + "id": "d4b2b34ebf185234" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.599302675Z", + "start_time": "2026-06-17T03:03:17.577699609Z" + } + }, "cell_type": "code", + "source": [ + "from sqlalchemy import text\n", + "\n", + "engine = create_engine('sqlite:///db/demo.db', echo=True)\n", + "with engine.connect() as conn:\n", + " conn.execute(text(\"\"\"\n", + " CREATE TABLE IF NOT EXISTS users (\n", + " id INTEGER PRIMARY KEY AUTOINCREMENT,\n", + " name TEXT NOT NULL,\n", + " age INTEGER\n", + " )\n", + " \"\"\"\n", + " ))\n", + "\n", + " conn.commit()\n", + " # 삽입\n", + " conn.execute(text(\"\"\"\n", + " INSERT INTO users (name, age) VALUES (:name, :age)\"\"\"),\n", + " [{\"name\" : \"Alice\", \"age\" : 23}, {\"name\":\"Bob\", \"age\" : 24}])\n", + "\n", + " conn.commit()\n", + "\n", + " # 조회\n", + " result = conn.execute(text(\"\"\"\n", + " SELECT * FROM users\n", + " \"\"\"))\n", + "\n", + " for row in result:\n", + " print(row.id, row.name, row.age)" + ], + "id": "de1ebcd8f162fd42", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-17 12:03:17,581 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,581 INFO sqlalchemy.engine.Engine \n", + " CREATE TABLE IF NOT EXISTS users (\n", + " id INTEGER PRIMARY KEY AUTOINCREMENT,\n", + " name TEXT NOT NULL,\n", + " age INTEGER\n", + " )\n", + " \n", + "2026-06-17 12:03:17,582 INFO sqlalchemy.engine.Engine [generated in 0.00126s] ()\n", + "2026-06-17 12:03:17,583 INFO sqlalchemy.engine.Engine COMMIT\n", + "2026-06-17 12:03:17,584 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,585 INFO sqlalchemy.engine.Engine \n", + " INSERT INTO users (name, age) VALUES (?, ?)\n", + "2026-06-17 12:03:17,585 INFO sqlalchemy.engine.Engine [generated in 0.00116s] [('Alice', 23), ('Bob', 24)]\n", + "2026-06-17 12:03:17,586 INFO sqlalchemy.engine.Engine COMMIT\n", + "2026-06-17 12:03:17,591 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,593 INFO sqlalchemy.engine.Engine \n", + " SELECT * FROM users\n", + " \n", + "2026-06-17 12:03:17,593 INFO sqlalchemy.engine.Engine [generated in 0.00211s] ()\n", + "1 Alice 23\n", + "2 Bob 24\n", + "3 Alice 23\n", + "4 Bob 24\n", + "5 Alice 23\n", + "6 Bob 24\n", + "2026-06-17 12:03:17,595 INFO sqlalchemy.engine.Engine ROLLBACK\n" + ] + } + ], + "execution_count": 39 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.624396207Z", + "start_time": "2026-06-17T03:03:17.600052176Z" + } + }, + "cell_type": "code", + "source": [ + "# 2.x 버전 코드\n", + "from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column\n", + "from sqlalchemy import DateTime, func\n", + "from datetime import datetime\n", + "\n", + "\n", + "engine = create_engine('sqlite:///db/users.db', echo=True)\n", + "# engine = create_engine('sqlite:///users.db')\n", + "\n", + "# 모든 모델 크래스의 부모 클래스가 될 Base 객체 생성\n", + "class Base(DeclarativeBase):\n", + " pass\n", + "\n", + "# 데이터베이스랑 관련있는 클래스 : 모델 클래스\n", + "class User(Base):\n", + " # 테이블 이름 지정\n", + " __tablename__ = 'users'\n", + "\n", + " # 컬럽 타입 지정 : 티입힌트(파이썬 데이터 타입) 사용\n", + " id:Mapped[int] = mapped_column(primary_key=True, autoincrement=True)\n", + " name:Mapped[str]\n", + " email:Mapped[str] = mapped_column(unique=True)\n", + " age:Mapped[int]\n", + " created_at:Mapped[datetime] = mapped_column(DateTime, server_default=func.now())\n", + "\n", + " def __str__(self):\n", + " return f\"\"\n", + "\n", + "# 테이블 생성 ( 없는 경우만 새로 생성됨 )\n", + "# create_all() : IF NOT EXISTS\n", + "Base.metadata.create_all(engine)" + ], + "id": "d7eb9178f27499a7", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-17 12:03:17,606 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,607 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"users\")\n", + "2026-06-17 12:03:17,609 INFO sqlalchemy.engine.Engine [raw sql] ()\n", + "2026-06-17 12:03:17,610 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"users\")\n", + "2026-06-17 12:03:17,610 INFO sqlalchemy.engine.Engine [raw sql] ()\n", + "2026-06-17 12:03:17,612 INFO sqlalchemy.engine.Engine \n", + "CREATE TABLE users (\n", + "\tid INTEGER NOT NULL, \n", + "\tname VARCHAR NOT NULL, \n", + "\temail VARCHAR NOT NULL, \n", + "\tage INTEGER NOT NULL, \n", + "\tcreated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, \n", + "\tPRIMARY KEY (id), \n", + "\tUNIQUE (email)\n", + ")\n", + "\n", + "\n", + "2026-06-17 12:03:17,613 INFO sqlalchemy.engine.Engine [no key 0.00171s] ()\n", + "2026-06-17 12:03:17,618 INFO sqlalchemy.engine.Engine COMMIT\n" + ] + } + ], + "execution_count": 40 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "#### 세션\n", + "- 데이터베이스 연동\n", + "- 변경사항 추적하고 트랜잭션 관리\n", + "- sessionmaker 팩토리 사용" + ], + "id": "de5e7ee1adb4cee6" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.633680827Z", + "start_time": "2026-06-17T03:03:17.625430327Z" + } + }, + "cell_type": "code", + "source": [ + "# 세션 팩토리 생성\n", + "Session = sessionmaker(bind=engine, autoflush=False, autocommit=False)\n", + "# 세션 객체 생성\n", + "session = Session()" + ], + "id": "140093069c78dd21", "outputs": [], - "execution_count": null, + "execution_count": 41 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.647449353Z", + "start_time": "2026-06-17T03:03:17.637405010Z" + } + }, + "cell_type": "code", + "source": "session.close()", + "id": "c9f8fdb361307ea", + "outputs": [], + "execution_count": 42 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.667693242Z", + "start_time": "2026-06-17T03:03:17.648384205Z" + } + }, + "cell_type": "code", + "source": [ + "# insert\n", + "# session.add() / session.add_all() / session.commit()\n", + "\n", + "session = Session()\n", + "# 사용자 생성\n", + "new_User = User(name=\"Alice\", email=\"alice@example.com\", age=23)\n", + "session.add(new_User)\n", + "session.commit()\n", + "session.close()" + ], + "id": "8c0f8f2c60597d73", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-17 12:03:17,651 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,654 INFO sqlalchemy.engine.Engine INSERT INTO users (name, email, age) VALUES (?, ?, ?) RETURNING id, created_at\n", + "2026-06-17 12:03:17,655 INFO sqlalchemy.engine.Engine [generated in 0.00099s] ('Alice', 'alice@example.com', 23)\n", + "2026-06-17 12:03:17,658 INFO sqlalchemy.engine.Engine COMMIT\n" + ] + } + ], + "execution_count": 43 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.691852173Z", + "start_time": "2026-06-17T03:03:17.669654096Z" + } + }, + "cell_type": "code", + "source": [ + "with Session() as session:\n", + " session.add_all(\n", + " [\n", + " User(name=\"Bob\", email=\"bob@example.com\", age=25),\n", + " User(name=\"Charlie\", email=\"charlie@example.com\", age=30),\n", + " ]\n", + " )\n", + " session.commit()" + ], + "id": "2c91d42f265ded17", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-17 12:03:17,672 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,674 INFO sqlalchemy.engine.Engine INSERT INTO users (name, email, age) VALUES (?, ?, ?) RETURNING id, created_at\n", + "2026-06-17 12:03:17,675 INFO sqlalchemy.engine.Engine [generated in 0.00009s (insertmanyvalues) 1/2 (ordered; batch not supported)] ('Bob', 'bob@example.com', 25)\n", + "2026-06-17 12:03:17,676 INFO sqlalchemy.engine.Engine INSERT INTO users (name, email, age) VALUES (?, ?, ?) RETURNING id, created_at\n", + "2026-06-17 12:03:17,676 INFO sqlalchemy.engine.Engine [insertmanyvalues 2/2 (ordered; batch not supported)] ('Charlie', 'charlie@example.com', 30)\n", + "2026-06-17 12:03:17,678 INFO sqlalchemy.engine.Engine COMMIT\n" + ] + } + ], + "execution_count": 44 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.733033419Z", + "start_time": "2026-06-17T03:03:17.693025923Z" + } + }, + "cell_type": "code", + "source": [ + "from sqlalchemy import select\n", + "\n", + "with Session() as session:\n", + " # id 를 사용해 조회\n", + " user = session.get(User, 1)\n", + " print(\"--- 단일 사용자 ---\")\n", + " print(user)\n", + "\n", + " print(\"--- 모든 사용자 ---\")\n", + " all_users = session.query(User).all()\n", + " for user in all_users:\n", + " print(user)\n", + "\n", + " stmt = select(User)\n", + " all_users = session.scalars(stmt)\n", + " for user in all_users:\n", + " print(user)\n", + "\n", + " print(\"--- where ---\")\n", + " alice = session.query(User).filter_by(name = \"Alice\").first()\n", + " print(f\"찾은 사용자 {alice}\")\n", + "\n", + " stmt = select(User).where(User.age >= 30)\n", + " find_users = session.scalars(stmt).all()\n", + " for user in find_users:\n", + " print(user)\n", + "\n", + " stmt = select(User).where(User.email.like('%ali%'))\n", + " find_users = session.scalars(stmt).all()\n", + " for user in find_users:\n", + " print(user)\n", + "\n", + " stmt = select(User).order_by(User.id.desc()).limit(2)\n", + " find_users = session.scalars(stmt).all()\n", + " for user in find_users:\n", + " print(user)\n", + "\n", + " # 집계 함수\n", + " count = session.scalar(select(func.count()).select_from(User))\n", + " print(count)" + ], + "id": "b0ce2cc0ee9d68ed", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-17 12:03:17,698 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,704 INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.age AS users_age, users.created_at AS users_created_at \n", + "FROM users \n", + "WHERE users.id = ?\n", + "2026-06-17 12:03:17,704 INFO sqlalchemy.engine.Engine [generated in 0.00082s] (1,)\n", + "--- 단일 사용자 ---\n", + "\n", + "--- 모든 사용자 ---\n", + "2026-06-17 12:03:17,706 INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.age AS users_age, users.created_at AS users_created_at \n", + "FROM users\n", + "2026-06-17 12:03:17,707 INFO sqlalchemy.engine.Engine [generated in 0.00096s] ()\n", + "\n", + "\n", + "\n", + "2026-06-17 12:03:17,710 INFO sqlalchemy.engine.Engine SELECT users.id, users.name, users.email, users.age, users.created_at \n", + "FROM users\n", + "2026-06-17 12:03:17,711 INFO sqlalchemy.engine.Engine [generated in 0.00100s] ()\n", + "\n", + "\n", + "\n", + "--- where ---\n", + "2026-06-17 12:03:17,713 INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.age AS users_age, users.created_at AS users_created_at \n", + "FROM users \n", + "WHERE users.name = ?\n", + " LIMIT ? OFFSET ?\n", + "2026-06-17 12:03:17,714 INFO sqlalchemy.engine.Engine [generated in 0.00058s] ('Alice', 1, 0)\n", + "찾은 사용자 \n", + "2026-06-17 12:03:17,718 INFO sqlalchemy.engine.Engine SELECT users.id, users.name, users.email, users.age, users.created_at \n", + "FROM users \n", + "WHERE users.age >= ?\n", + "2026-06-17 12:03:17,719 INFO sqlalchemy.engine.Engine [generated in 0.00090s] (30,)\n", + "\n", + "2026-06-17 12:03:17,720 INFO sqlalchemy.engine.Engine SELECT users.id, users.name, users.email, users.age, users.created_at \n", + "FROM users \n", + "WHERE users.email LIKE ?\n", + "2026-06-17 12:03:17,721 INFO sqlalchemy.engine.Engine [generated in 0.00080s] ('%ali%',)\n", + "\n", + "2026-06-17 12:03:17,723 INFO sqlalchemy.engine.Engine SELECT users.id, users.name, users.email, users.age, users.created_at \n", + "FROM users ORDER BY users.id DESC\n", + " LIMIT ? OFFSET ?\n", + "2026-06-17 12:03:17,724 INFO sqlalchemy.engine.Engine [generated in 0.00092s] (2, 0)\n", + "\n", + "\n", + "2026-06-17 12:03:17,727 INFO sqlalchemy.engine.Engine SELECT count(*) AS count_1 \n", + "FROM users\n", + "2026-06-17 12:03:17,728 INFO sqlalchemy.engine.Engine [generated in 0.00053s] ()\n", + "3\n", + "2026-06-17 12:03:17,728 INFO sqlalchemy.engine.Engine ROLLBACK\n" + ] + } + ], + "execution_count": 45 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.756463366Z", + "start_time": "2026-06-17T03:03:17.734005295Z" + } + }, + "cell_type": "code", + "source": [ + "with Session() as session:\n", + " alice = session.query(User).filter_by(name = \"Alice\").first()\n", + " alice.email = \"alice.new@example.com\"\n", + " session.commit()\n", + "\n", + " print(f\"수정된 사용자 {session.query(User).filter_by(name='Alice').first()}\")" + ], + "id": "93f58b5307abd93f", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-17 12:03:17,737 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,739 INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.age AS users_age, users.created_at AS users_created_at \n", + "FROM users \n", + "WHERE users.name = ?\n", + " LIMIT ? OFFSET ?\n", + "2026-06-17 12:03:17,740 INFO sqlalchemy.engine.Engine [cached since 0.02646s ago] ('Alice', 1, 0)\n", + "2026-06-17 12:03:17,741 INFO sqlalchemy.engine.Engine UPDATE users SET email=? WHERE users.id = ?\n", + "2026-06-17 12:03:17,742 INFO sqlalchemy.engine.Engine [generated in 0.00075s] ('alice.new@example.com', 1)\n", + "2026-06-17 12:03:17,743 INFO sqlalchemy.engine.Engine COMMIT\n", + "2026-06-17 12:03:17,747 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,749 INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.age AS users_age, users.created_at AS users_created_at \n", + "FROM users \n", + "WHERE users.name = ?\n", + " LIMIT ? OFFSET ?\n", + "2026-06-17 12:03:17,749 INFO sqlalchemy.engine.Engine [cached since 0.03613s ago] ('Alice', 1, 0)\n", + "수정된 사용자 \n", + "2026-06-17 12:03:17,751 INFO sqlalchemy.engine.Engine ROLLBACK\n" + ] + } + ], + "execution_count": 46 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.785689642Z", + "start_time": "2026-06-17T03:03:17.757935816Z" + } + }, + "cell_type": "code", + "source": [ + "from sqlalchemy import update\n", + "\n", + "with Session() as session:\n", + " user = session.get(User, 1)\n", + " if user:\n", + " user.age = 40\n", + " session.commit()\n", + "\n", + " # 일괄 업데이트\n", + " stmt = update(User).where(User.age <= 30).values(age=20)\n", + " session.execute(stmt)\n", + " session.commit()" + ], + "id": "1f333d8d8bcfbf54", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-17 12:03:17,761 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,762 INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.age AS users_age, users.created_at AS users_created_at \n", + "FROM users \n", + "WHERE users.id = ?\n", + "2026-06-17 12:03:17,763 INFO sqlalchemy.engine.Engine [cached since 0.0592s ago] (1,)\n", + "2026-06-17 12:03:17,764 INFO sqlalchemy.engine.Engine UPDATE users SET age=? WHERE users.id = ?\n", + "2026-06-17 12:03:17,766 INFO sqlalchemy.engine.Engine [generated in 0.00217s] (40, 1)\n", + "2026-06-17 12:03:17,769 INFO sqlalchemy.engine.Engine COMMIT\n", + "2026-06-17 12:03:17,773 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,775 INFO sqlalchemy.engine.Engine UPDATE users SET age=? WHERE users.age <= ?\n", + "2026-06-17 12:03:17,776 INFO sqlalchemy.engine.Engine [generated in 0.00108s] (20, 30)\n", + "2026-06-17 12:03:17,777 INFO sqlalchemy.engine.Engine COMMIT\n" + ] + } + ], + "execution_count": 47 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.805168757Z", + "start_time": "2026-06-17T03:03:17.787206837Z" + } + }, + "cell_type": "code", + "source": [ + "with Session() as session:\n", + " bob = session.query(User).filter_by(name=\"Bob\").first()\n", + " session.delete(bob)\n", + " session.commit()\n" + ], + "id": "f6c6f87020eb7927", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-17 12:03:17,790 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,791 INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.age AS users_age, users.created_at AS users_created_at \n", + "FROM users \n", + "WHERE users.name = ?\n", + " LIMIT ? OFFSET ?\n", + "2026-06-17 12:03:17,793 INFO sqlalchemy.engine.Engine [cached since 0.07924s ago] ('Bob', 1, 0)\n", + "2026-06-17 12:03:17,794 INFO sqlalchemy.engine.Engine DELETE FROM users WHERE users.id = ?\n", + "2026-06-17 12:03:17,795 INFO sqlalchemy.engine.Engine [generated in 0.00093s] (2,)\n", + "2026-06-17 12:03:17,797 INFO sqlalchemy.engine.Engine COMMIT\n" + ] + } + ], + "execution_count": 48 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.824210114Z", + "start_time": "2026-06-17T03:03:17.807206458Z" + } + }, + "cell_type": "code", + "source": [ + "from sqlalchemy import delete\n", + "\n", + "# delete from 테이블명 where id = 1\n", + "with Session() as session:\n", + " stmt = delete(User).where(User.id == 1)\n", + " session.execute(stmt)\n", + " session.commit()" + ], + "id": "b35c028d86f48e31", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-17 12:03:17,810 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,811 INFO sqlalchemy.engine.Engine DELETE FROM users WHERE users.id = ?\n", + "2026-06-17 12:03:17,812 INFO sqlalchemy.engine.Engine [generated in 0.00074s] (1,)\n", + "2026-06-17 12:03:17,813 INFO sqlalchemy.engine.Engine COMMIT\n" + ] + } + ], + "execution_count": 49 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.834127797Z", + "start_time": "2026-06-17T03:03:17.824909859Z" + } + }, + "cell_type": "code", + "source": "Base.metadata.clear()", + "id": "9da5d9421e56853f", + "outputs": [], + "execution_count": 50 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.863676311Z", + "start_time": "2026-06-17T03:03:17.834932678Z" + } + }, + "cell_type": "code", + "source": [ + "from sqlalchemy.orm import relationship\n", + "from sqlalchemy import ForeignKey\n", + "\n", + "# 관계(외래키)\n", + "# 1:N, N:1, M:N\n", + "# 게시글 하나 : 댓글 여러개\n", + "\n", + "class Post(Base):\n", + " __tablename__ = 'posts'\n", + "\n", + " id : Mapped[int] = mapped_column(primary_key=True, autoincrement=True)\n", + " title:Mapped[str] = mapped_column(String(200))\n", + " content:Mapped[str]\n", + " # 역참조\n", + " comments: Mapped[list['Comment']] = relationship('Comment', back_populates='post', cascade='all, delete-orphan')\n", + "\n", + "# FOREIGN KEY (post_id) REFERENCES posts(id);\n", + "class Comment(Base):\n", + " __tablename__ = 'comments'\n", + "\n", + " id : Mapped[int] = mapped_column(primary_key=True, autoincrement=True)\n", + " content:Mapped[str]\n", + " # 어느 게시글의 댓글인가?\n", + " post_id:Mapped[int] = mapped_column(ForeignKey(\"posts.id\"))\n", + " # 역참조\n", + " post:Mapped['Post'] = relationship('Post', back_populates='comments')\n" + ], + "id": "cc7b55c5bf90b92b", + "outputs": [], + "execution_count": 51 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.884050919Z", + "start_time": "2026-06-17T03:03:17.864744917Z" + } + }, + "cell_type": "code", + "source": "Base.metadata.create_all(engine)", + "id": "50e66cc696cfdd21", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-17 12:03:17,868 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,872 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"posts\")\n", + "2026-06-17 12:03:17,873 INFO sqlalchemy.engine.Engine [raw sql] ()\n", + "2026-06-17 12:03:17,873 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"comments\")\n", + "2026-06-17 12:03:17,874 INFO sqlalchemy.engine.Engine [raw sql] ()\n", + "2026-06-17 12:03:17,876 INFO sqlalchemy.engine.Engine COMMIT\n" + ] + } + ], + "execution_count": 52 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.932695111Z", + "start_time": "2026-06-17T03:03:17.885317745Z" + } + }, + "cell_type": "code", + "source": [ + "with Session() as session:\n", + " post = Post(title=\"LLM 입문\", content=\"LLM이란 무엇인가?\")\n", + " post.comments = [\n", + " Comment(content=\"LLM은 언어 모델의 일종입니다.\"),\n", + " Comment(content=\"좋아요\"),\n", + "\n", + " ]\n", + "\n", + " session.add(post)\n", + " session.commit()\n", + "\n", + " # 역참조 이용\n", + " p = session.get(Post, 1)\n", + " for c in p.comments:\n", + " print(c.content)" + ], + "id": "7400ebdad0575a44", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2026-06-17 12:03:17,897 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,902 INFO sqlalchemy.engine.Engine INSERT INTO posts (title, content) VALUES (?, ?)\n", + "2026-06-17 12:03:17,903 INFO sqlalchemy.engine.Engine [generated in 0.00105s] ('LLM 입문', 'LLM이란 무엇인가?')\n", + "2026-06-17 12:03:17,904 INFO sqlalchemy.engine.Engine INSERT INTO comments (content, post_id) VALUES (?, ?) RETURNING id\n", + "2026-06-17 12:03:17,906 INFO sqlalchemy.engine.Engine [generated in 0.00008s (insertmanyvalues) 1/2 (ordered; batch not supported)] ('LLM은 언어 모델의 일종입니다.', 2)\n", + "2026-06-17 12:03:17,906 INFO sqlalchemy.engine.Engine INSERT INTO comments (content, post_id) VALUES (?, ?) RETURNING id\n", + "2026-06-17 12:03:17,907 INFO sqlalchemy.engine.Engine [insertmanyvalues 2/2 (ordered; batch not supported)] ('좋아요', 2)\n", + "2026-06-17 12:03:17,908 INFO sqlalchemy.engine.Engine COMMIT\n", + "2026-06-17 12:03:17,918 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", + "2026-06-17 12:03:17,920 INFO sqlalchemy.engine.Engine SELECT posts.id AS posts_id, posts.title AS posts_title, posts.content AS posts_content \n", + "FROM posts \n", + "WHERE posts.id = ?\n", + "2026-06-17 12:03:17,920 INFO sqlalchemy.engine.Engine [generated in 0.00071s] (1,)\n", + "2026-06-17 12:03:17,923 INFO sqlalchemy.engine.Engine SELECT comments.id AS comments_id, comments.content AS comments_content, comments.post_id AS comments_post_id \n", + "FROM comments \n", + "WHERE ? = comments.post_id\n", + "2026-06-17 12:03:17,924 INFO sqlalchemy.engine.Engine [generated in 0.00067s] (1,)\n", + "LLM은 언어 모델의 일종입니다.\n", + "좋아요\n", + "2026-06-17 12:03:17,925 INFO sqlalchemy.engine.Engine ROLLBACK\n" + ] + } + ], + "execution_count": 53 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.950832461Z", + "start_time": "2026-06-17T03:03:17.933917669Z" + } + }, + "cell_type": "code", + "source": [ + "# 💡 이 셀을 실행해야 'Session'이라는 이름이 메모리에 등록됩니다!\n", + "from sqlalchemy import create_engine\n", + "from sqlalchemy.orm import sessionmaker\n", + "\n", + "engine = create_engine(\"sqlite:///users.db\") # 본인의 engine 설정 코드\n", + "Session = sessionmaker(bind=engine)" + ], + "id": "7d17932707f7f879", + "outputs": [], + "execution_count": 54 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-06-17T03:03:17.968159853Z", + "start_time": "2026-06-17T03:03:17.952470603Z" + } + }, + "cell_type": "code", "source": "", - "id": "de1ebcd8f162fd42" + "id": "48795cdb32be042b", + "outputs": [], + "execution_count": 54 } ], "metadata": {