인공지능 개발자 수다(유튜브 바로가기) 자세히보기

Fastapi

[FastAPI] SQLAlchemy 상세 - ForeignKey, relationship (6-5)

Suda_777 2025. 1. 9. 14:23

목차

    반응형

     

    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 != ''))

     

    반응형