7
1

SQLAlchemyを使って1つのテーブル内で同じ外部キーを複数紐づける

Last updated at Posted at 2023-12-09

概要

FastAPI で SQLAlchemy を使用してる際に「1つのテーブル内で同じ外部キーを複数持つ場合の models.py の記述方法」に手こずったので備忘録として残しておきます。

やりたいこと

以下のような 2 つのテーブルが存在していると仮定します。
このとき、「user テーブルの image_id1 と image_id2 の 2 つの id を image テーブルの id と紐づけた外部キー制約を設定したい」状況を考えます。

【image】

id type
1 3
2   5
3  7 
4  8 

【user】

id image_id1 image_id2
1 2 1
2   3 4
3  1  3
4  4  2

テーブルを作成するクエリは以下です。

create_table.sql
CREATE TABLE images (
    id INT NOT NULL,
    type INT NOT NULL,
    PRIMARY KEY (id),
    UNIQUE (id)
);

CREATE TABLE users (
    id INT NOT NULL,
    image_id1 INT NOT NULL,
    image_id2 INT NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (image_id1) REFERENCES images (id),
    FOREIGN KEY (image_id2) REFERENCES images (id)
);

成功パターン

models.py
from sqlalchemy import Column, Integer, ForeignKeyConstraint
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Images(Base):
    __tablename__ = "images"
    id = Column(Integer, nullable=False, unique=True)
    type = Column(Integer, nullable=False)
    users = relationship("Users", back_populates="images")

class Users(Base):
    __tablename__ = "users"
    __table_args__ = (
        ForeignKeyConstraint(
            ["image_id1", "image_id2"],
            ["images.id", "images.id"],
        ),
    )
    id = Column(Integer, nullable=False, unique=True)
    image_id1 = Column(Integer, nullable=False)
    image_id2 = Column(Integer, nullable=False)
    images = relationship("Images", back_populates="users")

ポイントは __table_args__ForeignKeyConstraint を指定する際に、image_id1 と image_id2 それぞれに images.id を紐づけてあげることです。

NGパターン

以下のパターンはどれも NG でした。

NGパターン1
...
image_id1 = Column(Integer, ForeignKey("images.id"),nullable=False)
image_id2 = Column(Integer, ForeignKey("images.id"),nullable=False)
...
images = relationship("Images", back_populates="users")
...
NGパターン2
...
image1 = relationship("Images", foreign_keys=[image_id1], back_populates="users")
image2 = relationship("Images", foreign_keys=[image_id2], back_populates="users")
...
NGパターン3
...
image = relationship("Images", foreign_keys=[image_id1, image_id2], back_populates="users")
...
NGパターン4
...
class Users(Base):
    __tablename__ = "users"
    __table_args__ = (
        ForeignKeyConstraint(
            ["image_id1"],
            ["images.id"],
        ),
        ForeignKeyConstraint(
            ["image_id2"],
            ["images.id"],
        ),
    )
...

教訓

複数のカラムにまたがる制約は __table_args__ に追加しましょう。

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1