Modeling a two-to-many relationship in an existing database #12623
-
|
Hey folks, I am new to SQLAlchemy, but already loving it, and I am glad I chose to learn it for this project I am working on. I have to deal with an existing database, so I am replicating the schemata as models, and I can focus on reading only. The downside, obviously, is that I am stuck with what the database has to offer, and it's pretty badly designed, unfortunately. One of the challenges I am facing is how to model an existing "two-to-many" relationship. There is no abstraction table, but instead one table has two columns that both point to another table. It's pretty similar to a child having a mother and a father ("two", I am purposely keeping it simple…), mother and father are both parents, and a parent has a number of children > 1 ("many"). Here is an example I cooked up to illustrate the point: from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import declarative_base, relationship
Base = declarative_base()
class Parent(Base):
__tablename__ = "parent_table"
id = Column(Integer, primary_key=True)
name = Column(String)
children = relationship(
"Child",
)
class Child(Base):
__tablename__ = "child_table"
id = Column(Integer, primary_key=True)
name = Column(String)
mother_id = Column(Integer, ForeignKey("parent_table.id"))
mother = relationship("Parent", foreign_keys=mother_id)
father_id = Column(Integer, ForeignKey("parent_table.id"))
father = relationship("Parent", foreign_keys=father_id)
if __name__ == "__main__":
mama = Parent(id=1, name="Mama")
papa = Parent(id=2, name="Papa")
daughter = Child(id=1, name="Daughter", mother=mama, father=papa)
son = Child(id=2, name="Son", mother=mama, father=papa)
print(daughter.mother.children)This yields an error:
… and while I understand the problem, I think, I am totally blanking at how to approach this. I did search the Web, but I could not find anything that worked. I would like to have relationships on both Parent (to their children), and Child to mother and father, separately, but I understand that Have you got an idea on how to go about this? Thank you, PS: Related issues I found, but they didn't help me get further: |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
|
I got something working, using class Parent(Base):
# …
children = relationship(
"Child",
primaryjoin="or_(Parent.id==Child.mother_id, Parent.id==Child.father_id)",
viewonly=True,
)but this just returns an empty table. There is no Is it even possible? |
Beta Was this translation helpful? Give feedback.
-
|
Here is the complete code that works for me, for future reference: from sqlalchemy import Column, ForeignKey, Integer, String, create_engine, select
from sqlalchemy.orm import Session, aliased, declarative_base, relationship
Base = declarative_base()
class Parent(Base):
__tablename__ = "parent_table"
id = Column(Integer, primary_key=True)
name = Column(String)
children = relationship(
"Child",
primaryjoin="or_(Parent.id==Child.mother_id, Parent.id==Child.father_id)",
viewonly=True,
)
class Child(Base):
__tablename__ = "child_table"
id = Column(Integer, primary_key=True)
name = Column(String)
mother_id = Column(Integer, ForeignKey("parent_table.id"))
mother = relationship(Parent, foreign_keys=mother_id, back_populates="children")
father_id = Column(Integer, ForeignKey("parent_table.id"))
father = relationship(Parent, foreign_keys=father_id, back_populates="children")
if __name__ == "__main__":
mama = Parent(id=1, name="Mama")
papa = Parent(id=2, name="Papa")
daughter = Child(id=1, name="Daughter", mother=mama, father=papa)
son = Child(id=2, name="Son", mother=mama, father=papa)
engine = create_engine("sqlite://")
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add_all((mama, papa, daughter, son))
session.commit()
assert daughter.mother == mama
assert daughter.father == papa
assert son.mother == mama
assert son.father == papa
assert son in mama.children
assert son in papa.children
st = (
select(Child, mum := aliased(Parent), dad := aliased(Parent))
.join(mum, Child.mother_id == mum.id)
.join(dad, Child.father_id == dad.id)
)
for child in session.execute(st):
print(child) |
Beta Was this translation helpful? Give feedback.
If we use a Session and flush it after adding ORM objects, it seems to work as far as I can tell: