Skip to content

SQLAlchemy의 relations

SQLAlchemy로 테이블의 관계를 사용하는 방법


1:N 관계

기본적인 1:N 관계는 아래와 같이 설정한다.

from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy.schema import ForeignKey
from sqlalchemy.types import BigInteger, String


class BaseEntity(DeclarativeBase): ...


class RoleEntity(BaseEntity):
    __tablename__ = "role"

    id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
    name: Mapped[str] = mapped_column(String(length=30), unique=True)

    user = relationship(argument="UserEntity", back_populates="role")


class UserEntity(BaseEntity):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
    name: Mapped[str] = mapped_column(String(length=30), unique=True)
    role_id: Mapped[int] = mapped_column(BigInteger, ForeignKey(column=RoleEntity.id))

    role = relationship(argument="RoleEntity", back_populates="user")
type hint를 사용해서 더 간결하고 직관적으로 작성하는 방법

아래와 같이 코드를 작성하면 더 간결하고 직관적으로 작성할 수 있으며, IDE와 사용자에게 연관관계에 대한 정보를 더 쉽게 줄 수 있는 장점이 있지만,
엔티티 관리 모듈이 길어져 모듈을 분리할 때 순환 참조 오류가 발생한다는 문제점이 있다.

from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy.schema import ForeignKey
from sqlalchemy.types import BigInteger, String


class BaseEntity(DeclarativeBase): ...


class RoleEntity(BaseEntity):
    __tablename__ = "role"

    id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
    name: Mapped[str] = mapped_column(String(length=30), unique=True)

    user: Mapped[list["UserEntity"]] = relationship(back_populates="role")


class UserEntity(BaseEntity):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
    name: Mapped[str] = mapped_column(String(length=30), unique=True)
    role_id: Mapped[int] = mapped_column(BigInteger, ForeignKey(column=RoleEntity.id))

    role: Mapped["RoleEntity"] = relationship(back_populates="user")

N:M 관계

N:M 관계는 아래와 같이 중계(junction) 테이블을 이용해서 설정할 수 있다.

from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy.schema import ForeignKey
from sqlalchemy.types import BigInteger, String


class BaseEntity(DeclarativeBase): ...


class UserEntity(BaseEntity):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
    name: Mapped[str] = mapped_column(String(length=30), unique=True)

    post = relationship(
        argument="PostEntity",
        secondary="PostVoteEntity",
        back_populates="user",
    )


class PostEntity(BaseEntity):
    __tablename__ = "post"

    id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
    name: Mapped[str] = mapped_column(String(length=30), unique=True)
    user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey(column=UserEntity.id))

    user = relationship(
        argument="UserEntity",
        secondary="PostVoteEntity",
        back_populates="post",
    )


class PostVoteEntity(BaseEntity):
    __tablename__ = "post_vote"

    user_id: Mapped[int] = mapped_column(
        BigInteger,
        ForeignKey(UserEntity.id),
        primary_key=True,
    )
    post_id: Mapped[int] = mapped_column(
        BigInteger,
        ForeignKey(PostEntity.id),
        primary_key=True,
    )

자기참조 관계

계층형 테이블의 자기참조 관계는 아래와 같이 설정할 수 있다.

from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy.schema import ForeignKey
from sqlalchemy.types import BigInteger, Integer, String


class BaseEntity(DeclarativeBase): ...


class PostCategoryEntity(BaseEntity):
    __tablename__ = "category"

    id: Mapped[int] = mapped_column(
        type_=BigInteger,
        primary_key=True,
        autoincrement=True,
        sort_order=-1,
    )  # need to override for self relations
    tier: Mapped[int] = mapped_column(Integer)
    name: Mapped[str] = mapped_column(String(length=30))
    parent_id: Mapped[int | None] = mapped_column(BigInteger, ForeignKey("category.id"))

    parent = relationship(argument="PostCategoryEntity", remote_side=[id])