I'm quite new to SQLAlchemy (and I do not have much experience with databases in general). I'm trying to traversere two many-to-many relationships. Given a parent, how can I get all unique grandchildren?
parent_child_table = Table('parent_child', Base.metadata,
Column('parent_id', Integer, ForeignKey('parent.id')),
Column('child_id', Integer, ForeignKey('child.id'))
)
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary=parent_child_table,
backref="parents")
child_grandchild_table = Table('child_grandchild', Base.metadata,
Column('child_id', Integer, ForeignKey('child.id')),
Column('grandchild_id', Integer, ForeignKey('grandchild.id'))
)
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
grandchildren = relationship("Grandchild",
secondary=child_grandchild_table,
backref="children")
class Grandchild(Base):
__tablename__ = 'grandchild'
id = Column(Integer, primary_key=True)
Thanks! This problem is giving me a headache...
The most straight-forard way:
# my_parent = ... (instance of Parent)
q = (session.query(Grandchild)
.join(Child, Grandchild.children)
.join(Parent, Child.parents)
.filter(Parent.id == my_parent.id)
)
sqlalchemy will return only unique Grandchild instances (although the SQL query does not filter duplicates out).
Related
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.
Starting from the many-to-many relationship example from the SQLAlchemy documentation, I want to add an attribute first_child that will return the first child of children defined by the relationship. The first_child attribute needs to be useable in an association_proxy attribute definition such as first_child_id below.
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child", secondary=association_table)
first_child = ???
first_child_id = association_proxy('first_child', 'id')
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
I'm thinking I need a to declare first_child as either a hybrid_property or a column_property, but I don't know how to return the first element.
In addition to first_child, I also need last_child and an associated last_child_id attribute.
I'm using SQLAlchemy with a MySQL database.
If what you need is to have minimum start_time and maximum end_time, then I would use column_property just for these columns:
association_table = Table(
'association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
start_time = Column(DateTime)
end_time = Column(DateTime)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship(
"Child",
secondary=association_table,
backref="parents",
)
min_start_time = column_property(
select([Child.start_time.label("min_start_time")])
.where(Child.id == association_table.c.right_id)
.where(id == association_table.c.left_id)
.order_by(Child.start_time.asc())
.limit(1)
)
max_end_time = column_property(
select([Child.end_time.label("max_end_time")])
.where(Child.id == association_table.c.right_id)
.where(id == association_table.c.left_id)
.order_by(Child.end_time.desc())
.limit(1)
.as_scalar()
)
But if you need more than one such special column from the relationship, probably it would be more efficient to use hybrid_property.
The problem with association_proxy is that you cannot use it on hybrid_properties (or at least not in a direct manner).
A simple solution might be to use a property decorator, which would be evaluated on an already-loaded instance:
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child", secondary=association_table)
#property
def first_child(self):
return children[0]
However, you will not be able to use it on queries and it is a "read only" property. More information on SQLAlchemy plain-descriptor.
I have two models with a many to many relationship (SQLAlchemy):
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary="association",
backref="parents")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
Get "all parents of one (second on the list) the child" I can that way:
parents = session.query(Parent).filter(Parent.children.any(id=2))
And how to get "all the children of a parent"?
Any of the below should do:
# 1.
children = session.query(Child).filter(Child.parents.any(Parent.id==??))
# 2.
children = session.query(Child).join(Parent, Child.parents).filter(Parent.id == 99)
# 3.
my_parent = session.query(Parent).get(2)
children = session.query(Child).with_parent(my_parent).all()
This question already has an answer here:
Creating container relationship in declarative SQLAlchemy
(1 answer)
Closed 3 years ago.
I have already finished a good bit of my python/Elixir interface on my existing database. I am now considering to drop Elixir and move everything into pure SQLAlchemy, most likely wanting to use Declarative methods.
I am not sure where to even start with this particular inheritance relationship. I don't think sqlalchemy performs inheritance in this manner (or as "magically"), and I am a bit confused how the same would look in sqlalchemy:
This is a polymorphic multi-table join, with each class mapped to its own database table. When finished, another class (not included here) will have a OneToMany with 'Comp'. The Comp subclasses have a Primary Key that is a Foreign key to Comp.id.
class Comp(Entity):
using_options(inheritance='multi')
parent = ManyToOne('Assembly', onupdate='cascade', ondelete='set null')
quantity = Field(Numeric(4), default=1)
def __repr__(self):
return "<Comp>"
## If not familiar with Elixir, each of the following "refid" point to a different
## table depending on its class. This is the primary need for polymorphism.
class CompAssm(Comp):
using_options(inheritance='multi')
refid = ManyToOne('Assembly', onupdate='cascade', ondelete='set null')
def __repr__(self):
return "<CompAssm>"
class CompItem(Comp):
using_options(inheritance='multi')
refid = ManyToOne('Item', onupdate='cascade')
def __repr__(self):
return "<CompItem>"
class CompLabor(Comp):
using_options(inheritance='multi')
refid = ManyToOne('Labor', onupdate='cascade')
def __repr__(self):
return "<CompLabor>"
I think this is the general direction, but may still need tweaking.
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Assembly(Base):
__tablename__ = 'assembly'
assm_id = Column(Integer, primary_key=True)
children = relationship('Comp')
### other assembly stuff
class Comp(Base):
__tablename__ = 'components'
id = Column(Integer, primary_key=True)
comp_type = Column('type', String(50))
__mapper_args__ = {'polymorphic_on': comp_type}
parent = Column(Integer, ForeignKey('assembly.assm_id'))
quantity = Column(Integer)
class CompAssm(Comp):
__tablename__ = 'compassm'
__mapper_args__ = {'polymorphic_identity': 'compassm'}
id = Column(Integer, ForeignKey('components.id'), primary_key=True)
refid = Column(Integer, ForeignKey('assembly.assm_id'))
class CompItem(Comp):
__tablename__ = 'compitem'
__mapper_args__ = {'polymorphic_identity': 'compitem'}
id = Column(Integer, ForeignKey('components.id'), primary_key=True)
refid = Column(Integer, ForeignKey('items.id'))
class CompLabor(Comp):
__tablename__ = 'complabor'
__mapper_args__ = {'polymorphic_identity': 'complabor'}
id = Column(Integer, ForeignKey('components.id'), primary_key=True)
refid = Column(Integer, ForeignKey('labors.id'))