목차
반응형
SQLAlchemy 에서 두 테이블 간의 관계를 표현할 때에는
ForeignKey(), relationship() 두가지를 이용한다.
관계를 설정하는 상세 옵션에 대해 알아보자
1. ForeignKey
1.1. 기본 설명
ForeignKey 설명
- 부모테이블의 기본키와 자식테이블의 컬럼을 연결한다.
- 두 테이블간의 관계에서, 자식 테이블에서 설정해 준다.
- 해당 옵션은 데이터베이스에서 테이블을 생성할 때 실행해야 의미가 있다. ( Base.metadata.create_all(engine) )
- 즉, ForeignKey에서 정의한 내용은 데이터베이스 스키마에 반영된다.
- 여기서 설정한 옵션은 데이터베이스 레벨의 설정이다.
예시 테이블 코드
- ForeignKey('parent.id') 를 이용해 외래키 정의
Base = declarative_base()
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
name = Column(String)
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
name = Column(String)
1.2. ForeignKey 상세 옵션
1.2.1. ondelete
- 설명
- 참조된 행이 삭제될 때의 동작을 지정
- 가능한 값
- CASCADE: 참조된 행이 삭제되거나 업데이트될 때, 해당 외래 키를 참조하는 행도 함께 삭제되거나 업데이트
- SET NULL: 참조된 행이 삭제되거나 업데이트될 때, 해당 외래 키를 참조하는 컬럼의 값이 NULL로 설정
- SET DEFAULT: 참조된 행이 삭제되거나 업데이트될 때, 해당 외래 키를 참조하는 컬럼의 값이 미리 정의된 기본값으로 설정
- RESTRICT: 참조된 행이 삭제되거나 업데이트될 때, 해당 외래 키를 참조하는 행이 존재하면 삭제나 업데이트가 제한
- NO ACTION: 참조된 행이 삭제되거나 업데이트될 때, 아무런 동작도 수행되지 않는다. 데이터베이스에서 기본적으로 제공하는 동작을 따름
parent_id = Column(Integer, ForeignKey('parent.id', ondelete='CASCADE'))
1.2.2. onupdate
- 참조된 행이 업데이트될 때의 동작을 지정
- 가능한 값 : CASCADE, SET NULL, SET DEFAULT, RESTRICT, NO ACTION
parent_id = Column(Integer, ForeignKey('parent.id', onupdate='CASCADE'))
1.2.3. deferrable과 initially
deferrable과 initially 옵션은 함께 사용
- deferrable
- 외래 키 제약 조건을 지연할 수 있는지 여부
- 트랜잭션의 끝까지 외래 키 제약 조건의 검사를 지연
- 가능한 값
- True: 외래 키 제약 조건을 지연 가능
- False: 지연하지 않음 (기본값)
- initially
- 외래 키 제약 조건의 초기 상태를 지정
- 가능한 값
- DEFERRED: 외래 키 제약 조건의 검사를 트랜잭션이 끝날 때까지 지연합니다.
- IMMEDIATE: 외래 키 제약 조건을 즉시 검사합니다. (기본값)
- 주의사항
- deferrable가 False인 경우 initially를 사용하지 않음
- deferrable=True, initially=' IMMEDIATE ' 인 경우 트랜젝션에서 외래키 제약조건 검사를 유연하게 가능
- deferrable=False 인 경우는 즉시 검사
parent_id = Column(Integer, ForeignKey('parent.id', deferrable=True, initially='DEFERRED'))
2. relationship
relationship은 SQLAlchemy에서 테이블 간의 관계를 정의
객체 간의 연관성을 설정하는 데 사용
SQLAlchemy ORM 레벨에서 동작 한다.
2.1. 기본 사용법
- 설명
- 각 테이블을 정의할 때, 부모 테이블과 자식 테이블에 각각 relationship()을 넣어준다. (양쪽에 넣어줘야 명확함)
- relationship()로 정의한 객체는 변수처럼 사용할 수 있다.
- 부모 클래스에 정의
- 정의 방법
- relationship(<클래스이름>, back_populates=<속성이름>)
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
profile = relationship("Profile", uselist=False, back_populates="user")
class Profile(Base):
__tablename__ = 'profile'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('user.id'))
bio = Column(String)
user = relationship("User", back_populates="profile")
2.2. 1:1, 1:N, N:M 관계
- 1:1 관계 : uselist=False 옵션을 사용함
- 1:N 관계 : uselist=True 옵션을 사용함
- M:N 관계 : secondary옵션 사용, 매핑 테이블을 만듦
M:N 관계 예시코드
# 매핑 테이블
class StudentCourse(Base):
__tablename__ = 'student_course'
student_id = Column(Integer, ForeignKey('student.id'), primary_key=True)
course_id = Column(Integer, ForeignKey('course.id'), primary_key=True)
extra_info = Column(String) # 추가적인 속성 예시
class Student(Base):
__tablename__ = 'student'
id = Column(Integer, primary_key=True)
name = Column(String)
courses = relationship("Course", secondary='student_course', back_populates="students")
class Course(Base):
__tablename__ = 'course'
id = Column(Integer, primary_key=True)
name = Column(String)
students = relationship("Student", secondary='student_course', back_populates="courses")
2.3. cascade
- 설명
- SQLAlchemy ORM 레벨에서 동작하며, 부모 객체가 삭제될 때 자식 객체에 대한 동작을 정의
- 부모 클래스에서 정의
- 주의
- 일반적으로 cascade 옵션과 ForeignKey의 ondelete 옵션을 동일하게 설정하여 데이터베이스 레벨과 ORM 레벨에서 일관된 동작을 보장하는 것이 좋으나, 상황에 따라서는 다를 수도 있음
- 올 수 있는 값
- save-update: 부모 객체가 저장되거나 업데이트될 때, 자식 객체도 함께 저장되거나 업데이트됩니다.
- delete: 부모 객체가 삭제될 때, 자식 객체도 함께 삭제됩니다.
- delete-orphan: 부모 객체와의 관계가 끊어진 자식 객체를 자동으로 삭제합니다.
- merge: 부모 객체가 병합될 때, 자식 객체도 함께 병합됩니다.
- refresh-expire: 부모 객체가 새로 고침되거나 만료될 때, 자식 객체도 함께 새로 고침되거나 만료됩니다.
- expunge: 부모 객체가 세션에서 제거될 때, 자식 객체도 함께 세션에서 제거됩니다.
- all: delete-orphan을 제외한 모두를 포함
예시 코드
parent = relationship("Parent", back_populates="children", cascade="all, delete-orphan")
2.4. lazy
- 설명
- SQLAlchemy ORM 레벨에서 동작 하며, 부모 객체를 로드 할 때 자식 객체에 대한 동작을 정의
- 부모 클래스에서 정의
- 올 수 있는 값
- select : 지연 로딩(lazy loading) 방식, 관계된 데이터를 필요할 때마다 개별적인 SELECT 쿼리
- joined : 관계된 데이터를 즉시 로드, join으로 한번에 불러옴
- subquery : 관계된 데이터를 즉시 로드, subquery로 불러옴
- selectin : 관계된 데이터를 즉시 로드, select in 쿼리를 사용하여 로드
- raise : 관계된 데이터에 접근할 때 예외를 발생
- raise_on_sql : 관계된 데이터에 접근할 때 SQL 쿼리가 실행되면 예외를 발생
- noload : 관계된 데이터를 자동으로 로드하지 않는다.
- dynamic : 관계된 데이터를 동적으로 로드, 쿼리 객체를 반환하여 추가적인 필터링이나 조작이 가능
Base = declarative_base()
class Parent(Base):
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
name = Column(String)
children = relationship('Child', back_populates='parent', lazy='select')
class Child(Base):
__tablename__ = 'children'
id = Column(Integer, primary_key=True)
name = Column(String)
parent_id = Column(Integer, ForeignKey('parents.id'))
parent = relationship('Parent', back_populates='children')
2.5. order_by
- 설명
- 자식 객체를 특정 순서로 정렬
- 부모 클래스에서 정의
- 설정 방법 : <클래스이름>.<속성이름>
# models.py
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Parent(Base):
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
name = Column(String)
# 자식 객체들을 'name' 컬럼을 기준으로 정렬
children = relationship("Child", back_populates="parent", order_by="Child.name")
class Child(Base):
__tablename__ = 'children'
id = Column(Integer, primary_key=True)
name = Column(String)
parent_id = Column(Integer, ForeignKey('parents.id'))
parent = relationship("Parent", back_populates="children")
여러 컬럼을 기준으로 order_by를 하려면 [] 에 넣으면 된다.
children = relationship("Child", back_populates="parent", order_by=["Child.name", "Child.id"])
2.6. primaryjoin
- 설명
- 기본 조인 조건을 명시적으로 지정
- 문자열을 사용 할 수 있으며, 클래스를 그대로 사용할 수도 있음
- 외래 키 관계를 기반으로 조인 조건을 자동으로 추론하지만, 복잡한 관계나 사용자 정의 조인 조건이 필요한 경우 사용
- 부모와 자식 모두에 똑같이 넣어주어, 명확하게 해준다.
문자열 사용
class Parent(Base):
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
name = Column(String)
# 자식 객체들과의 관계를 정의
children = relationship("Child", primaryjoin="Parent.id == Child.parent_id")
class Child(Base):
__tablename__ = 'children'
id = Column(Integer, primary_key=True)
name = Column(String)
parent_id = Column(Integer, ForeignKey('parents.id'))
parent = relationship("Parent", primaryjoin="Child.parent_id == Parent.id")
클래스 사용
class Parent(Base):
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
name = Column(String)
# 자식 객체들과의 관계를 정의
children = relationship("Child", primaryjoin=and_(Parent.id == Child.parent_id, Child.name != ''))
class Child(Base):
__tablename__ = 'children'
id = Column(Integer, primary_key=True)
name = Column(String)
parent_id = Column(Integer, ForeignKey('parents.id'))
parent = relationship("Parent", primaryjoin=and_(Child.parent_id == Parent.id, Parent.name != ''))
반응형
'Fastapi' 카테고리의 다른 글
[FastAPI] JWT 기반 인증 (7-1) (1) | 2025.01.19 |
---|---|
[FastAPI] SQLAlchemy 상세 - Join (6-6) (0) | 2025.01.19 |
[FastAPI] SQLAlchemy 상세 - Delete (6-4) (0) | 2025.01.01 |
[FastAPI] SQLAlchemy 상세 - Update(6-3) (0) | 2025.01.01 |
[FastAPI] SQLAlchemy 상세 - Read/Select (6-2) (1) | 2025.01.01 |