1. sqlalchemy CRUD 예제 실습
2. callcenter 프로젝트 제작중
This commit is contained in:
Generated
+10
@@ -0,0 +1,10 @@
|
||||
# 디폴트 무시된 파일
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 에디터 기반 HTTP 클라이언트 요청
|
||||
/httpRequests/
|
||||
# 쿼리 파일을 포함한 무시된 디폴트 폴더
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="~/Source/project/CALLCENTER_APP/.venv" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PackageRequirementsSettings" />
|
||||
<component name="PyDocumentationSettings" />
|
||||
<component name="ReSTService" />
|
||||
<component name="TestRunnerService" />
|
||||
</module>
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="callcenter" uuid="91805924-e27b-4e0d-8c54-ca806a60bde7">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/db/callcenter.db</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/CALLCENTER_APP.iml" filepath="$PROJECT_DIR$/.idea/CALLCENTER_APP.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PyToolsState">
|
||||
<option name="tools">
|
||||
<map>
|
||||
<entry key="black">
|
||||
<value>
|
||||
<ToolEntry />
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="pyrefly">
|
||||
<value>
|
||||
<ToolEntry />
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="pyright">
|
||||
<value>
|
||||
<ToolEntry />
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="ruff">
|
||||
<value>
|
||||
<ToolEntry />
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="ty">
|
||||
<value>
|
||||
<ToolEntry />
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -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}",
|
||||
)
|
||||
@@ -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",
|
||||
)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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
|
||||
- 상담사가 제공한 해결 방법
|
||||
"""
|
||||
@@ -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()
|
||||
@@ -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"<Customer id={self.customer_id} name={self.name} phone = {self.phone}]"
|
||||
|
||||
|
||||
# 상담기록 저장
|
||||
class CallHistory(Base):
|
||||
__tablename__ = 'call_history'
|
||||
|
||||
call_id:Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
customer_id:Mapped[int] = mapped_column(ForeignKey('customers.customer_id'), nullable=False)
|
||||
transcript:Mapped[str]
|
||||
summary:Mapped[str]
|
||||
category:Mapped[str] = mapped_column(String(50))
|
||||
sentiment:Mapped[str] = mapped_column(String(20))
|
||||
customer_issue:Mapped[str]
|
||||
resolution:Mapped[str]
|
||||
# created_at:Mapped[datetime] = mapped_column(server_default=func.now(), nullable=False)
|
||||
created_at:Mapped[datetime] = mapped_column(default=datetime.now, nullable=False)
|
||||
@@ -0,0 +1,28 @@
|
||||
# 강제로 회원가입
|
||||
from sqlalchemy.orm import Session
|
||||
from backend.repository.db_init import SessionLocal
|
||||
from backend.repository.models import Customer
|
||||
|
||||
db = SessionLocal()
|
||||
|
||||
DEFAULT_CUSTOMERS = [
|
||||
Customer(name="홍길동", phone="010-1234-5678"),
|
||||
Customer(name="최철수", phone="010-4321-8765"),
|
||||
Customer(name="박영희", phone="010-5678-1234"),
|
||||
]
|
||||
|
||||
def seed_customers(db: Session):
|
||||
"""
|
||||
customer 테이블에 기본(연습용) 회원 데이터 삽입
|
||||
(중복 실행 방지 : 이미 데이터가 있으면 건너뜀)
|
||||
"""
|
||||
|
||||
existing = db.query(Customer).first()
|
||||
if existing:
|
||||
print("[Seed] customer 테이블에 이미 데이터가 있습니다.")
|
||||
return
|
||||
|
||||
db.add_all(DEFAULT_CUSTOMERS)
|
||||
db.commit()
|
||||
db.close()
|
||||
print(f"[Seed] 기본 회원 {len(DEFAULT_CUSTOMERS)}명 생성 완료")
|
||||
@@ -0,0 +1,18 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from backend.schemas.summary_schema import SummaryRequest, CallSummary, CallRequest
|
||||
from backend.services.call_service import summary_call, create_call_history
|
||||
from sqlalchemy.orm import Session
|
||||
from backend.repository.db_init import get_db
|
||||
|
||||
|
||||
router = APIRouter(prefix="/api/calls", tags=["Summary"])
|
||||
|
||||
# 요약 라우터
|
||||
@router.post("", response_model=CallSummary)
|
||||
def generate_summary(req:SummaryRequest):
|
||||
return summary_call(req.transcript)
|
||||
|
||||
@router.post("/save", response_model=CallSummary)
|
||||
# @router.post("/save", response_model=CallRequest)
|
||||
def create_call(req: CallRequest, db:Session = Depends(get_db)):
|
||||
return create_call_history(req, db)
|
||||
@@ -0,0 +1,28 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
class SummaryRequest(BaseModel):
|
||||
transcript: str
|
||||
|
||||
class CallSummary(BaseModel):
|
||||
summary: str
|
||||
keywords: list[str]
|
||||
category: str
|
||||
sentiment: str
|
||||
action_items: list[str]
|
||||
customer_issue: str
|
||||
resolution: str
|
||||
|
||||
# 상담 요청 시 사용할 타입
|
||||
class CallRequest(BaseModel):
|
||||
customer_id: int
|
||||
transcript: str
|
||||
|
||||
# 상담 요약 저장 시 사용할 타입
|
||||
class CallCreate(BaseModel):
|
||||
customer_id: int
|
||||
transcript: str
|
||||
summary: str
|
||||
category: str
|
||||
sentiment: str
|
||||
customer_issue: str
|
||||
resolution: str
|
||||
@@ -0,0 +1,26 @@
|
||||
from langchain_community.vectorstores import Chroma
|
||||
from backend.ai.embedding import watson_embedding
|
||||
from langchain_core.documents import Document
|
||||
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
||||
from pathlib import Path
|
||||
|
||||
def main():
|
||||
# data 폴더 안의 파일을 읽은 후
|
||||
data_dir = Path("data")
|
||||
|
||||
documents = []
|
||||
|
||||
# Document 객체 생성
|
||||
for file_path in data_dir.glob("*.txt"):
|
||||
content = file_path.read_text(encoding="utf-8")
|
||||
documents.append(Document(page_content=content, metadata={"source": str(file_path.name)}))
|
||||
|
||||
# 분할
|
||||
splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=30)
|
||||
splite_docs = splitter.split_documents(documents)
|
||||
|
||||
# 인덱스 설정(벡터 db) ./vectordb
|
||||
Chroma.from_documents(documents = splite_docs, embedding=watson_embedding, persist_directory="./vectordb")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,72 @@
|
||||
from backend.ai.llm import hugging_llm
|
||||
from langchain_core.prompts import ChatPromptTemplate
|
||||
from backend.prompts.all_prompt import SUMMARY_SYSTEM_PROMPT
|
||||
from backend.repository.models import CallHistory
|
||||
from backend.schemas.summary_schema import CallSummary, CallCreate
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from backend.schemas.summary_schema import CallRequest
|
||||
|
||||
# LLM transcript 요약 시키기
|
||||
def summary_call(transcript: str):
|
||||
|
||||
summary_prompt = ChatPromptTemplate.from_messages(
|
||||
[
|
||||
("system", SUMMARY_SYSTEM_PROMPT),
|
||||
("human", "상담내용\n{transcript}"),
|
||||
]
|
||||
)
|
||||
|
||||
structured_llm = hugging_llm.with_structured_output(CallSummary)
|
||||
summary_chain = summary_prompt | structured_llm
|
||||
|
||||
result = summary_chain.invoke({"transcript" : transcript})
|
||||
return result
|
||||
|
||||
def save_call_history(db, data):
|
||||
"""상담 저장"""
|
||||
history = CallHistory(
|
||||
customer_id = data.customer_id,
|
||||
transcript = data.transcript,
|
||||
summary = data.summary,
|
||||
category = data.category,
|
||||
sentiment = data.sentiment,
|
||||
customer_issue = data.customer_issue,
|
||||
resolution = data.resolution
|
||||
)
|
||||
|
||||
db.add(history)
|
||||
db.commit()
|
||||
db.refresh(history)
|
||||
|
||||
return history
|
||||
|
||||
def evaluate_call():
|
||||
"""상담 평가"""
|
||||
|
||||
pass
|
||||
|
||||
def save_call_evaluation():
|
||||
"""상담 평가 내용 저장"""
|
||||
|
||||
pass
|
||||
|
||||
def create_call_history(req: CallRequest, db:Session):
|
||||
# 요약
|
||||
# CallSummary
|
||||
summary = summary_call(req.transcript)
|
||||
print("summary",summary)
|
||||
# 데이터베이스 저장용 객체
|
||||
call_data = CallCreate(
|
||||
customer_id = req.customer_id,
|
||||
transcript = req.transcript,
|
||||
summary = summary.summary,
|
||||
category = summary.category,
|
||||
sentiment = summary.sentiment,
|
||||
customer_issue = summary.customer_issue,
|
||||
resolution = summary.resolution
|
||||
)
|
||||
|
||||
return save_call_history(db = db, data = call_data)
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
Q. 비밀번호를 재설정하려면?
|
||||
|
||||
A. 고객 포털의 비밀번호 찾기 메뉴를 이용하세요.
|
||||
|
||||
Q. 서비스 장애가 발생하면?
|
||||
|
||||
A. 홈페이지 또는 앱에서 장애 공지를 확인하세요.
|
||||
|
||||
Q. 요금제를 변경하려면?
|
||||
|
||||
A. 고객 포털 > 내 요금제 메뉴에서 변경 가능합니다.
|
||||
|
||||
Q. 데이터 사용량은 어디서 확인하나요?
|
||||
|
||||
A. 고객 포털 > 데이터 사용량 메뉴에서 확인 가능합니다.
|
||||
@@ -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상담사: 최대한 신속히 처리하겠습니다. 신고해 주셔서 감사합니다.
|
||||
@@ -0,0 +1,27 @@
|
||||
상담사: 안녕하세요. 고객센터입니다. 무엇을 도와드릴까요?
|
||||
|
||||
고객: 몇 시간 전부터 인터넷 속도가 너무 느려서 답답합니다.
|
||||
|
||||
상담사: 고객 확인을 위해 성함을 알려주시겠습니까?
|
||||
|
||||
고객: 네, 홍길동입니다.
|
||||
|
||||
상담사: 감사합니다. 본인 확인을 위해 PIN 번호도 알려주시겠습니까?
|
||||
|
||||
고객: 지금은 PIN 번호를 가지고 있지 않습니다.
|
||||
|
||||
상담사: 괜찮습니다. 우선 제가 회선 상태를 확인하고 조정해보겠습니다. 잠시만 기다려 주세요.
|
||||
|
||||
(잠시 후)
|
||||
|
||||
상담사: 회선 설정을 일부 조정했습니다. 현재 인터넷 속도를 다시 확인해보시겠습니까?
|
||||
|
||||
고객: 잠시만요... 네, 훨씬 빨라졌네요.
|
||||
|
||||
상담사: 다행입니다. 상담 종료 후 설문조사가 발송될 예정입니다. 설문은 인터넷 서비스 품질이 아니라 상담 서비스에 대한 평가입니다.
|
||||
|
||||
고객: 네, 알겠습니다.
|
||||
|
||||
상담사: 이용해 주셔서 감사합니다. 좋은 하루 보내세요.
|
||||
|
||||
고객: 감사합니다.
|
||||
@@ -0,0 +1,17 @@
|
||||
상담사: 안녕하세요. 인터넷 서비스 고객센터입니다. 성함과 PIN 번호를 알려주시겠습니까?
|
||||
|
||||
고객: 홍길동이고 PIN은 1234입니다.
|
||||
|
||||
상담사: 감사합니다. 어떤 문제로 연락주셨나요?
|
||||
|
||||
고객: 어제부터 인터넷 속도가 너무 느립니다.
|
||||
|
||||
상담사: 불편을 드려 죄송합니다. 연결 상태를 확인해보겠습니다.
|
||||
|
||||
(확인 중)
|
||||
|
||||
상담사: 현재 고객님 지역의 회선 장애가 의심됩니다. 관련 부서에 장애 내용을 접수하겠습니다.
|
||||
|
||||
고객: 네, 빨리 해결되면 좋겠네요.
|
||||
|
||||
상담사: 최대한 신속히 처리하겠습니다. 신고해 주셔서 감사합니다.
|
||||
@@ -0,0 +1,29 @@
|
||||
상담사: 안녕하세요. 무엇을 도와드릴까요?
|
||||
|
||||
고객: 어제부터 인터넷이 계속 끊어집니다.
|
||||
|
||||
상담사: 공유기를 재부팅해 보셨나요?
|
||||
|
||||
고객: 네. 이미 해봤는데 똑같습니다.
|
||||
|
||||
상담사: 현재 공유기 상태 표시등은 어떻게 되어 있나요?
|
||||
|
||||
고객: 전원등은 켜져 있고 인터넷 표시등은 깜빡입니다.
|
||||
|
||||
상담사: 공유기 케이블 연결 상태를 확인해 주시겠습니까?
|
||||
|
||||
고객: 네. 확인했는데 이상 없습니다.
|
||||
|
||||
상담사: 그렇다면 공유기 초기화를 진행해보겠습니다. 초기화 방법을 알고 계신가요?
|
||||
|
||||
고객: 아니요.
|
||||
|
||||
상담사: 공유기 뒷면의 리셋 버튼을 10~15초 정도 눌러주세요.
|
||||
|
||||
고객: 네. 지금 재시작 중입니다.
|
||||
|
||||
(몇 분 후)
|
||||
|
||||
고객: 이제 정상적으로 작동하는 것 같습니다.
|
||||
|
||||
상담사: 다행입니다. 다른 문제가 있으시면 언제든 연락 주세요.
|
||||
@@ -0,0 +1,21 @@
|
||||
상담사: 안녕하세요. 고객센터입니다. 성함과 PIN 번호를 확인하겠습니다.
|
||||
|
||||
고객: 홍길동이며 PIN은 1234입니다.
|
||||
|
||||
상담사: 감사합니다. 어떤 문제가 있으신가요?
|
||||
|
||||
고객: 인터넷이 느리고 가끔 끊깁니다.
|
||||
|
||||
상담사: 불편을 드려 죄송합니다. 연결 상태를 확인하겠습니다.
|
||||
|
||||
(잠시 후)
|
||||
|
||||
상담사: 회선에 일시적인 문제가 있었으며 현재 초기화 처리를 완료했습니다. 다시 확인해보시겠습니까?
|
||||
|
||||
고객: 네. 정상적으로 작동합니다.
|
||||
|
||||
상담사: 다행입니다. 상담 후 설문조사가 발송될 예정이며 상담 서비스에 대한 평가입니다.
|
||||
|
||||
고객: 알겠습니다.
|
||||
|
||||
상담사: 감사합니다. 좋은 하루 보내세요.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Generated
+7
@@ -15,5 +15,12 @@
|
||||
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/db/users.db</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="demo" uuid="fab7f51e-43ea-4d26-b7f0-6be132dd06fb">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/db/demo.db</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
+1
@@ -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
|
||||
.</data>
|
||||
</component>
|
||||
</project>
|
||||
Binary file not shown.
Binary file not shown.
+748
-23
@@ -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\"<User(name='{self.name}', email='{self.email}')>\"\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\"<User(name='{self.name}', email='{self.email}')>\"\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",
|
||||
"<User(name='Alice', email='alice@example.com')>\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",
|
||||
"<User(name='Alice', email='alice@example.com')>\n",
|
||||
"<User(name='Bob', email='bob@example.com')>\n",
|
||||
"<User(name='Charlie', email='charlie@example.com')>\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",
|
||||
"<User(name='Alice', email='alice@example.com')>\n",
|
||||
"<User(name='Bob', email='bob@example.com')>\n",
|
||||
"<User(name='Charlie', email='charlie@example.com')>\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",
|
||||
"찾은 사용자 <User(name='Alice', email='alice@example.com')>\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",
|
||||
"<User(name='Charlie', email='charlie@example.com')>\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",
|
||||
"<User(name='Alice', email='alice@example.com')>\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",
|
||||
"<User(name='Charlie', email='charlie@example.com')>\n",
|
||||
"<User(name='Bob', email='bob@example.com')>\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",
|
||||
"수정된 사용자 <User(name='Alice', email='alice.new@example.com')>\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": {
|
||||
|
||||
Reference in New Issue
Block a user