I am working with the following database table design. The relationship is one-to-many, parent to child. So one parent (mother or father) has multiple children. I'm having issues mapping these tables because the child's column parent_id is a Foreign Key to both of the parent tables.
class Mother(Base):
id = Column(Integer, primary_key=True)
child_id = Column(Integer, ForeignKey('child.unique_id'))
class Father(Base):
id = Column(Integer, primary_key=True)
child_id = Column(Integer, ForeignKey('child.unique_id'))
Class Children(Base):
id = Column(Integer, primary_key=True)
unique_id = Column(Integer, ForeignKey('mother.child_id')
I have tried defining a relationship with backref in the parent tables to avoid explicitely defining Foreign Keys in the child tables:
class Mother(Base):
id = Column(Integer, primary_key=True)
child_id = Column(Integer, ForeignKey('child.unique_id'))
children = relationship('Child', backref='mother')
class Father(Base):
id = Column(Integer, primary_key=True)
child_id = Column(Integer, ForeignKey('child.unique_id'))
children = relationship('Child', backref='father')
Class Children(Base):
id = Column(Integer, primary_key=True)
unique_id = Column(Integer)
But this yields a one-to-one relationship.
I think that foreign keys should bo on Child table, it would be better and more realistic model:
class Mother(Base):
id = Column(Integer, primary_key=True)
children = relationship('Child', backref='mother')
class Father(Base):
id = Column(Integer, primary_key=True)
children = relationship('Child', backref='father')
class Child(Base):
id = Column(Integer, primary_key=True)
father_id = Column(Integer, ForeignKey('father.id'))
mother_id = Column(Integer, ForeignKey('mother.id'))
In above model, each child has at most one mother and one father, while a mother/father can have many children.
I have a 3-level database, tables such as:
class GrandParent(Base):
__tablename__ = 'grandparent'
id = Column(Integer, primary_key=True)
field0 = Column(Float)
parents = relationship("Parent", back_populates="parent", cascade="all, delete-orphan")
class Parent(Base):
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
field0 = Column(Float)
parent_id = Column("parent", Integer, ForeignKey("grandparent.id"))
parent = relationship("GrandParent", back_populates="parents")
children = relationship("Child", back_populates="parent", cascade="all, delete-orphan")
class Child(Base):
__tablename__ = 'children'
id = Column(Integer, primary_key=True)
field0 = Column(Float)
parent_id = Column("parent", Integer, ForeignKey("parents.id"))
parent = relationship("Parent", back_populates="children")
I stored a record for GrandParent that holds N records of Parent, each handling M records of Child. I need to modify GrandParent instance structure such that it is convenient to erase all its children and grand-children and rebuild the tree. I have to perform these modifications in memory, before letting the user save (persist) changes to the database.
I tried:
for parent in grand_parent.children:
for child in parent.children:
session.delete(child)
session.delete(parent)
# Other code here to add children/ gran-children from scratch to the grand_parent instance
This gives strange behaviour: the new instances are replicated along with the old ones attached to grand_parent.
So I tried force in-class-object deletion:
for parent in grand_parent.children:
parent.children.clear()
grand_parent.children.clear()
Does not work either, with the same result (even if I join the two methods and delete both class-object and from session). How to handle this?
I have a many-to-many relationship similar to the one described here. Notice my Association table includes an extra_data field..
class Association(Base):
__tablename__ = 'association'
left_id = Column(ForeignKey('left.id'), primary_key=True)
right_id = Column(ForeignKey('right.id'), primary_key=True)
extra_data = Column(String(50))
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child", secondary="association", back_populates="parents")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
parents = relationship("Parent", secondary="association", back_populates="children")
If I want to fetch a particular parent object with its children, I can do
db_parent = db.query(Parent).where(Parent.id == 1).first()
print(db_parent.children[0].id) # works fine
BUT, the extra_data field is not included as an attribute of the children.
print(db_parent.children[0].extra_data)
AttributeError: 'Child' object has no attribute 'extra_data'
How can I write fetch the children of a parent such that extra_data is included as an attribute?
Fully Working Example
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, Session
# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=False)
# Make the DeclarativeMeta
Base = declarative_base()
class Association(Base):
__tablename__ = 'association'
left_id = Column(ForeignKey('left.id'), primary_key=True)
right_id = Column(ForeignKey('right.id'), primary_key=True)
extra_data = Column(String(50))
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child", secondary="association", back_populates="parents")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
parents = relationship("Parent", secondary="association", back_populates="children")
# Create the tables in the database
Base.metadata.create_all(engine)
# Test it
with Session(bind=engine) as session:
# add parents
p1 = Parent()
session.add(p1)
p2 = Parent()
session.add(p2)
session.commit()
# add children
c1 = Child()
session.add(c1)
c2 = Child()
session.add(c2)
session.commit()
# map children to parents
a1 = Association(left_id=p1.id, right_id=c1.id, extra_data='foo')
a2 = Association(left_id=p1.id, right_id=c2.id, extra_data='bar')
a3 = Association(left_id=p2.id, right_id=c2.id, extra_data='baz')
session.add(a1)
session.add(a2)
session.add(a3)
session.commit()
with Session(bind=engine) as session:
db_parent = session.query(Parent).where(Parent.id == 1).first()
print(db_parent.children[0].id)
print(db_parent.children[0].extra_data)
What you are asking can't be done exactly how you want using SQLAlchemy. Indeed, items in Parent.children whould be instances of Child class. If your child class has an extra_data property loaded from an association table, to which of its parent would it refer?
What I'm trying to explain is that this implicit reference to "extra_data" that you would like to have in Child, only makes sense if the Child object is referenced from a parent object.
As an example, imagine the following scenario
session.add_all(
Association(left=parent_a.id, right=child.id, extra_data="hello")
Association(left=parent_b.id, right=child.id, extra_data="world")
)
Which parent metadata would you expect in child.extra_data ?
Moreover most of the time, if you need an object as association table, it means that this object makes sense by itself. And so that you should not try to hide it. Have a look at the following concrete example
class Account(Base):
__tablename__ = "accounts"
id = Column(Integer, primary_key=True)
username = Column(String(10), nullable=False)
groups = relationship("Membership", back_populates="account")
class Group(Base):
__tablename__ = "groups"
id = Column(Integer, primary_key=True)
name = Column(String(10), nullable=False)
members = relationship("Membership", back_populates="group")
class Membership(Base):
"""Membership is our association table here"""
__tablename__ = "memberships"
id = Column(Integer, primary_key=True)
account_id = Column(Integer, ForeignKey("accounts.id"))
account = relationship("Account", back_populates="groups")
group_id = Column(Integer, ForeignKey("groups.id"))
group = relationship("Group", back_populates="members")
# extra data embed in association table
role = Column(String(10), nullable=False)
Base.metadata.create_all()
# create user "toto" that belongs to group "Funny people" with role "joker"
toto = Account(username="toto")
funny_people = Group(name="Funny people")
session.add(Membership(account=toto, group=funny_people, role="joker"))
session.commit()
Notice the difference between the two approaches. Here, Account.groups contains memberships and not directly Group objects. Then you can use it this way :
toto = session.query(Account).first()
toto.username
toto.groups[0].group.name
toto.groups[0].role
I know this is not exactly what you asked, but this is probably the closest you can have without introducing weird logic that will interfere with the proper functioning of your application
Thanks to #van for introducing me to SQLAlchemy's AssociationProxy. With AssociationProxy, I can almost get what I want, but it's still not ideal.
The idea here is to create three tables / classes as usual:
left (Parent)
right (Child)
association (Association)
Then I give Parent a children relationship attribute. I also give Association a parent and a child relationship attribute.
Lastly, I set up association proxies inside Association so that it "carries" all the stuff its related child object has that I want. Here's a working example
from sqlalchemy import create_engine, Column, Integer, String, Float, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, Session
from sqlalchemy.ext.associationproxy import association_proxy
# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=True)
# Make the DeclarativeMeta
Base = declarative_base()
class Association(Base):
__tablename__ = 'association'
left_id = Column(ForeignKey('left.id'), primary_key=True)
right_id = Column(ForeignKey('right.id'), primary_key=True)
parent = relationship("Parent", back_populates="children")
child = relationship("Child")
extra_data = Column(String(50))
# Association proxies
child_name = association_proxy("child", "name")
child_weight = association_proxy("child", "weight")
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Association", back_populates="parent")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
weight = Column(Float, nullable=False)
# Create the tables in the database
Base.metadata.create_all(engine)
# Test it
with Session(bind=engine) as session:
# add parents
p1 = Parent()
session.add(p1)
p2 = Parent()
session.add(p2)
session.commit()
# add children
c1 = Child(name = "A", weight = 5)
session.add(c1)
c2 = Child(name = "B", weight = 3)
session.add(c2)
session.commit()
# map children to parents
a1 = Association(left_id=p1.id, right_id=c1.id, extra_data='foo')
a2 = Association(left_id=p1.id, right_id=c2.id, extra_data='bar')
a3 = Association(left_id=p2.id, right_id=c2.id, extra_data='baz')
session.add(a1)
session.add(a2)
session.add(a3)
session.commit()
Now if I fetch a parent instance, I can reference parent.children which returns a list of children with all the attributes I need.
with Session(bind=engine) as session:
db_parent = session.query(Parent).where(Parent.id == 1).first()
print(db_parent.children[0].extra_data)
print(db_parent.children[0].child_name)
print(db_parent.children[0].child_weight)
Technically though, parent.children is returning a list of Associations where each association is acquiring attributes from its related Child instance via my association proxies. A drawback to this is that I have to label these attributes child_name and child_weight as opposed to simply name and weight, otherwise if I decided to set up the reverse relationship, it won't be obvious that name and weight are attributes of the child and not the parent.
Another solution I came up with is to define a read-only property of Parent called children which merely executes the SQL query required to fetch the exact data I need.
from sqlalchemy import create_engine, Column, Integer, String, Float, ForeignKey
from sqlalchemy.orm import declarative_base, Session, object_session
# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=True)
# Make the DeclarativeMeta
Base = declarative_base()
class Association(Base):
__tablename__ = 'association'
left_id = Column(ForeignKey('left.id'), primary_key=True)
right_id = Column(ForeignKey('right.id'), primary_key=True)
extra_data = Column(String(50))
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
#property
def children(self):
s = """
SELECT foo.* FROM (
SELECT
right.*,
association.extra_data,
association.left_id
FROM right INNER JOIN association ON right.id = association.right_id
) AS foo
INNER JOIN left ON foo.left_id = left.id
WHERE left.id = :leftid
"""
result = object_session(self).execute(s, params={'leftid': self.id}).fetchall()
return result
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
weight = Column(Float, nullable=False)
# Create the tables in the database
Base.metadata.create_all(engine)
# Test it
with Session(bind=engine) as session:
# add parents
p1 = Parent()
session.add(p1)
p2 = Parent()
session.add(p2)
session.commit()
# add children
c1 = Child(name = "A", weight = 5)
session.add(c1)
c2 = Child(name = "B", weight = 3)
session.add(c2)
session.commit()
# map children to parents
a1 = Association(left_id=p1.id, right_id=c1.id, extra_data='foo')
a2 = Association(left_id=p1.id, right_id=c2.id, extra_data='bar')
a3 = Association(left_id=p2.id, right_id=c2.id, extra_data='baz')
session.add(a1)
session.add(a2)
session.add(a3)
session.commit()
Usage
with Session(bind=engine) as session:
db_parent = session.query(Parent).where(Parent.id == 1).first()
print(db_parent.children[0].extra_data) # foo
print(db_parent.children[0].name) # A
print(db_parent.children[0].weight) # 5.0
Using SQLAlchemy I have three models: Parent1, Parent2, and Child, where Parent1 has one-to-one relationship with Parent2, and both of them has the same relationship with Child. Here are they:
from extensions import db_session
class Parent1(Base):
__tablename__ = 'parent1'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
parent2 = relationship("Parent2", backref="parent1", uselist=False)
child = relationship("Child", backref="parent1", uselist=False)
class Parent2(Base):
__tablename__ = 'parent2'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
parent1_id = Column(Integer, ForeignKey('parent1.id'))
child = relationship("Child", backref="parent2", uselist=False)
class Child(Base):
id = Column(Integer, primary_key=True)
parent1_id = Column(Integer, ForeignKey('parent1.id'))
parent2_id = Column(Integer, ForeignKey('parent2.id'))
What I am trying to achieve is to fill Child table with its parents' foreign keys.
So, when I execute this:
parent1 = Parent1(name="Adil")
parent1.parent2 = Parent2(name="Aisha")
parent1.child = Child()
db_session.add(parent1)
db_session.commit()
to the parents tables it inserts data as needed, however to the Child table it inserts data like this:
Child
id parent1_id parent2_id
1 1 NULL
How to properly set relationships, so that on any insert to the Parent1->Parent2 tables it also inserts its ids as foreign keys to Child table?
What I want to achieve is:
Child
id parent1_id parent2_id
1 1 1
I came up with this solution. I removed declared model Child and created association table between two models and added secondary parameter filling with assocation table name:
Base = declarative_base()
child = Table('child', Base.metadata,
Column('parent1_id', Integer, ForeignKey('parent1.id')),
Column('parent2_id', Integer, ForeignKey('parent2.id')))
class Parent1(Base):
__tablename__ = 'parent1'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
parent2 = relationship("Parent2", secondary='child', uselist=False)
class Parent2(Base):
__tablename__ = 'parent2'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
parent1_id = Column(Integer, ForeignKey('parent1.id'))
Two declarative classes which have a parent child relationship, the youngest child is the most important child and thus a youngest_child_id column would be useful.
In this there are two relationships - a one to one from parent to child and a one to many from parent to children, but this creates multiple join paths
Something like the below:
class Parent(Base):
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
youngest_child_id = Column(Integer, foreign_key='Child.id')
youngest_child = relationship("Child", uselist=False, foreign_keys=[youngest_child_id])
children = relationship("Child", back_populates='parent')
Class Child(Base):
__tablename__ = 'children'
id = id = Column(Integer, primary_key=True)
parent_id = Column(Integer, foreign_key='Parent.id')
parent = relationship("Parent", back_populates='children')
This and a few other variations that I have created raise AmbiguousForeignKeysError:
Exception has occurred: sqlalchemy.exc.AmbiguousForeignKeysError
Could
not determine join condition between parent/child tables on
relationship Parent.children
Where is this going wrong and can this be achieved via the ORM?
You've defined foreign_keys for the youngest_child relationship, but you also have to define it for the children and parent relationships:
class Parent(Base):
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
youngest_child_id = Column(Integer, ForeignKey('children.id'))
youngest_child = relationship("Child", uselist=False, post_update=True,
foreign_keys=[youngest_child_id])
# Pass foreign_keys= as a Python executable string for lazy evaluation
children = relationship("Child", back_populates='parent',
foreign_keys='[Child.parent_id]')
class Child(Base):
__tablename__ = 'children'
id = id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parents.id'))
parent = relationship("Parent", back_populates='children',
foreign_keys=[parent_id])
In addition you must define post_update=True on for example youngest_child in order to break the circular dependency between the models. Without it SQLAlchemy would have to insert both the parent and the child at the same time, if you do something like this:
p = Parent()
c1, c2 = Child(), Child()
p.children = [c1, c2]
p.youngest_child = c1
session.add(p)
session.commit()
With the post update in place SQLAlchemy first inserts to parents, then to children, and then updates the parent with the youngest child.