Is there any way to create a relationship like this (example data) between Parent and Child based on parent_id and id respectively:
Parent
parent_id: "A1234"
name: "Parent Name"
Child
id: 1234
how can I add the foreign key to the Child? The parent_id is a String. Is there a way to slice it and then cast to Integer?
Edit:
Also what if the situation happens other way round:
Child:
child_id: "A1234"
Parent:
parent_letter: "A"
parent_id: 1234
would that be something like:
primaryjoin=(child_id == (Parent.parent_letter + str(Parent.parent_id)))
what would the remote_side look like? or the entire relationship?
See Creating Custom Foreign Conditions section of documentation. Using the cast, the relationship can be setup for the model below:
class Parent(Base):
__tablename__ = 'parent'
parent_id = Column(String, primary_key=True)
name = Column(String, nullable=False)
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
parent = relationship(
Parent,
primaryjoin=("A" + cast(id, String) == Parent.parent_id),
foreign_keys=id,
remote_side=Parent.parent_id,
backref="children",
# uselist=False, # use in case of one-to-one relationship
)
In this case you can query for Parent.children or Child.parent:
p1 = session.query(Parent).get('A1234')
print(p1)
print(p1.children)
c1 = session.query(Child).get(1234)
print(c1)
print(c1.parent)
However you would still not be able to create relationship items like below:
p = Parent(
parent_id='A3333', name='with a child',
children=[Child(name='will not work')]
)
session.add(p)
session.commit() # this will fail
Edit-1: For the alternative case you mention in your comments and edit, following relationship definition should work (obviously, the model is defined differently as well):
parent = relationship(
Parent,
primaryjoin=(
foreign(child_id) ==
remote(Parent.parent_letter + cast(Parent.parent_id, String))
),
backref="children",
uselist=False,
)
Related
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 the multiple-level one-to-many tables, linked by the foreign key.
I frequently query the specific child according to family_id, grandparents, and parent's name.
The query result should be only one.
If a child does not exist, I'll create a new child record and link it to the given parent.
The number of the child table is much larger than the parent table.
Childs >>>> Parents >> Grandparents > families
People from different families can have the same name.
(The name in the child table can be the same because they might come from different families and different parents)
Here are the model definitions
class Families:
__tablename__ = 'families'
id = Column(Integer, primary_key=True)
family_name = Column(String, nullable=False)
grand_parents = relationship(GrandParents, backref="family")
class GrandParents:
__tablename__ = 'grand_parents'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
family_id = Column(Integer, ForeignKey("families.id"))
parents = relationship(Parents, backref="grand_parent")
class Parents:
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
grand_parent_id = Column(Integer, ForeignKey("grand_parents.id"))
childs = relationship(Childs, backref="parent")
class Childs:
__tablename__ = 'childs'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
parent_id = Column(Integer, ForeignKey("parents.id"))
For now, I use join to query the targeted child row by given family_id, grandparent's name, and parent's name
def get_child(family_id, grand_praent_name, parent_name, child_name):
child = session.query(Childs)\
.join(Parents)\
.join(GrandParents)\
.filter(GrandParents.family_id == family_id,
GrandParents.name == grand_praent_name,
Parents.name == parent_name,
Childs.name == child_name).one_or_none()
return child
But each time, I have to do this kind of query (go through all children for a specific family and update their value depending on business logic).
Is there a better approach/design/idiomatic to do this kind of query?
Your query looks good, if you only update child. You may consider using with_entities to avoid fetching to many unnecessary data:
def get_child(family_id, grand_praent_name, parent_name, child_name):
child = session.query(Childs)\
.join(Parents)\
.join(GrandParents)\
.filter(GrandParents.family_id == family_id,
GrandParents.name == grand_praent_name,
Parents.name == parent_name,
Childs.name == child_name)\
.with_entities(Childs).one_or_none()
return child
If you would like to update also parent or grandparent you should use joinedload to avoid additional implicit queries.
If you encountered performance issues for your query you should add indexes to foreign keys to improve joining, e.g.:
parent_id = Column(Integer, ForeignKey("parents.id"), index=True)
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.
I have a polymorphic association set up like below:
class Parent(Base)
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
class Child(Parent)
parent_id = Column(ForeignKey(Parent.id), primary_key=True)
description = Column(String)
__mapper_args__ = {
'polymorphic_identity': 'child'
}
I would like to be able to first create the parent table, and then link it to the child table like so:
parent = Parent(name='foobar')
session.add(parent)
session.flush()
child = Child(description='baz', parent_id=parent.id)
However I receive an integrity error on the name field null constraint on the child creation - meaning that last line attempts to insert into the parent table as well.
Is there a way to achieve this split inserts?
How can I make a relationship attribute be sorted by the child tables primary key? I have a schema where many of the tables have a common parent and the parent has a list of each of its children using the declaritive system like this
class Scenario(Base):
__tablename__ = 'scenarios'
id = Column(Integer, primary_key=True, autoincrement=True)
class ScenarioMixin(object):
#declared_attr
def scenario_id(cls):
return Column(None, ForeignKey(Scenario.id),
primary_key=True,
index=True)
#declared_attr
def scenario(cls):
return relationship('Scenario',
backref=backref(cls.__tablename__))
# example scenario-specific table
class Child(Base, ScenarioMixin):
__tablename__ = 'children'
id1 = Column(String, primary_key=True)
id2 = Column(String, primary_key=True)
So the scenario object has an attribute children which is a list of children. This works fine, except that the order of the Child objects in the list is arbitrary. This is fine for the semantics of the application, it leads to nondeterministic behavior. What's the best way to configure the mixin so that every child list will be sorted by the primary key of its table, even if it's a compound primary key?
Specify order_by argument in your relationship definition as in the code below.
#declared_attr
def scenario(cls):
return relationship(
'Scenario',
backref=backref(
cls.__tablename__,
order_by=lambda: cls.__table__.primary_key.columns
),
)
In you case you cannot return cls.__table__.primary_key.columns straight away because at the definition time the __table__ does not exist yet. But returning a callable works.
See Relationships API for more information.