SQLAlchemyでリレーション
以下のサンプルでは、
各クラスについてidやテーブル名を定義するのが面倒なのでベースクラスを用意する。
from sqlalchemy.ext.declarative import declarative_base, declared_attr Base = declarative_base() class BaseModel(Base): # 継承してね __abstract__ = True # idはすべてのクラスで使うので id = Column(Integer, primary_key=True) # 例えば、__tablename__ = "user" と同じ @declared_attr def __tablename__(cls): return cls.__name__.lower()
多対一
ユーザーが複数の日記をもっているリレーションの例。
class User(BaseModel): name = Column(String) class Dialy(BaseModel): # ForeignKeyはデータベースレベルでのみリレーションを定義する。 user_id = Column(Integer, ForeignKey("user.id"), nullable=False) title = Column(String) # オブジェクトレベルでのリレーションを張る # order_by はクラス名から書かないと組み込み関数idとかいわれる。 user = relationship(User, backref=backref("dialies"), order_by='User.id') def test_many2one(): engine = create_engine("sqlite:///:memory:", echo=True) BaseModel.metadata.create_all(engine) Session = sessionmaker(bind=engine) sess = Session() user = User(name="hoge") user2 = User(name="foo") sess.add(user) sess.add(user2) sess.commit() d = Dialy(user_id=user.id, title="first") sess.add(d) user.name = "fuga" sess.commit() for instance in sess.query(Dialy).all(): print(instance.title, instance.user.name)
一対一
ユーザーが専用のプロフィールをもっている例。
class Profile(BaseModel): user_id = Column(Integer, ForeignKey("user.id"), nullable=False) age = Column(Integer) # ProfileからもUserからもuselist=Falseを使うことがポイント user = relationship(User, uselist=False, backref=backref("profile", uselist=False)) def test_one2one(): engine = create_engine("sqlite:///:memory:", echo=True) BaseModel.metadata.create_all(engine) Session = sessionmaker(bind=engine) sess = Session() user = User(name="hoge") sess.add(user) sess.commit() ## コミットするとidプロパティが暗黙的に定義される p = Profile(age=50, user_id=user.id) sess.add(p) sess.commit() for u, p in sess.query(User, Profile).join(Profile, User.id == Profile.user_id).all(): print(p.user.name, p.user.profile.age)
多対多
ユーザーと行ったことがある国の関係。
user_country_table = Table("user_country", Base.metadata, Column("user_id", Integer, ForeignKey("user.id")), Column("country_id", Integer, ForeignKey("country.id")), ) class Country(BaseModel): name = Column(String) users = relationship("User", secondary=user_country_table, backref="countries") def test_many2many(): engine = create_engine("sqlite:///:memory:", echo=True) BaseModel.metadata.create_all(engine) Session = sessionmaker(bind=engine) sess = Session() user = User(name="hoge") user2 = User(name="foo") c1 = Country(name="Japan") c2 = Country(name="Russia") user.countries.extend([c1, c2]) user2.countries.extend([c1, c2]) sess.add(user) sess.add(user2) sess.commit() for u, c in sess.query(User, Country).join("countries").all(): print(u.name, c.name)
その他Tips
relation
は内部でrelationship
を呼んでいるので、
relationship
を直接呼ぶべし。
orm/init.py:112
def relation(*arg, **kw): """A synonym for :func:`relationship`.""" return relationship(*arg, **kw)