같은 테이블 내에서 다른 레코드를 참조해야 하는 경우가 종종 있습니다.
예를 들어, 사용자(user)와 관리자가 모두 동일한 member 테이블에 저장되는 경우, 각 사용자는 관리자 ID를 외래키로 참조하고 있을 수 있습니다.
이러한 구조에서 SQLAlchemy를 통해 자기 참조(joining the same table) 를 수행할 때는 몇 가지 주의할 점이 있습니다.
기본 테이블 구조
예제에서는 관리자가 있는 member 테이블을 사용합니다. 각 member는 선택적으로 admin_id를 가지고 있으며, 이는 같은 테이블 내 다른 member를 참조합니다.
from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship, declarative_base
Base = declarative_base()
class Member(Base):
__tablename__ = 'member'
id = Column(Integer, primary_key=True)
admin_id = Column(Integer, ForeignKey('member.id'))
# 자기 참조 관계
admin = relationship('Member', remote_side=[id])
여기서 remote_side=[id]를 설정함으로써 SQLAlchemy는 어떤 방향으로 관계를 바라볼지 결정할 수 있습니다.
1. aliased를 사용하는 방법 (relationship 없이도 가능)
SQLAlchemy의 aliased를 사용하면 같은 테이블을 서로 다른 별칭으로 조회할 수 있습니다. 예를 들어, 관리자를 a2, 일반 사용자를 a1이라고 한다면 다음과 같이 사용할 수 있습니다.
from sqlalchemy.orm import aliased
a1 = aliased(Member)
a2 = aliased(Member)
q = (
session.query(a1, a2)
.select_from(a1)
.outerjoin(a2, a2.id == a1.admin_id)
.filter(a1.admin_id.is_(None))
)
이 방식은 relationship 없이도 가능하며, 복잡한 자기 참조 구조를 단순하게 처리할 수 있다는 장점이 있습니다.
2. relationship을 활용한 ORM 방식 (of_type 사용)
relationship이 선언되어 있는 경우에는 더 간결하게 ORM 스타일로 쿼리를 작성할 수 있습니다. 이때 of_type() 메서드를 사용하여 aliased 객체와의 관계를 명확하게 지정할 수 있습니다.
q = (
session.query(a1, a2)
.select_from(a1)
.outerjoin(a1.admin.of_type(a2)) # a1 → admin (즉 a1.admin_id = a2.id)
.filter(a1.admin_id.is_(None))
)
이 방법은 SQLAlchemy가 relationship을 이용하여 자동으로 join 조건을 유추하기 때문에 보다 직관적인 코드 작성이 가능합니다.
정리
방식 필요 요소 장점
aliased + 명시적 조건 | relationship 없어도 됨 | join 조건을 명확히 설정 가능 |
relationship + of_type() | relationship 필요 | ORM 스타일로 간결하게 작성 가능 |
자기 참조 테이블을 다루는 것은 복잡해 보일 수 있지만, 위와 같이 aliased와 relationship을 적절히 활용하면 깔끔하고 유지보수가 쉬운 코드를 작성할 수 있습니다. SQLAlchemy의 유연함을 활용해 여러분의 모델링을 한층 더 발전시켜 보세요.
'Python' 카테고리의 다른 글
[Python] 예외 체이닝: 더 나은 디버깅을 위한 예외 처리 전략 (0) | 2025.05.09 |
---|---|
[SQLAlchemy] ORM 성능 최적화: Lazy Loading vs Eager Loading (feat. 로딩 전략) (0) | 2025.05.09 |
[SQLAlchemy] 윈도우 함수 orm으로 사용 (0) | 2025.05.09 |
[Python] Python으로 Slack 메시지 보내기 (채널 & DM) (0) | 2025.04.22 |
[Python] .style.yapf YAPF (feat. Python formatter) (0) | 2025.04.16 |