SQLAlchemy Handling Multiple Paths In One Relationship - python

Please note: this question is related but separate from my other currently open question SQLAlchemy secondary join relationship on multiple foreign keys.
The SQLAlchemy documentation describes handling multiple join paths in a single class for multiple relationships:
from sqlalchemy import Integer, ForeignKey, String, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Customer(Base):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
name = Column(String)
billing_address_id = Column(Integer, ForeignKey("address.id"))
shipping_address_id = Column(Integer, ForeignKey("address.id"))
billing_address = relationship("Address")
shipping_address = relationship("Address")
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
street = Column(String)
city = Column(String)
state = Column(String)
zip = Column(String)
Within the same section the documentation shows three separate ways to define the relationship:
billing_address = relationship("Address", foreign_keys=[billing_address_id])
billing_address = relationship("Address", foreign_keys="[Customer.billing_address_id]")
billing_address = relationship("Address", foreign_keys="Customer.billing_address_id")
As you can see in (1) and (2) SQLAlchemy allows you to define a list of foreign_keys. In fact, the documentation explicitly states:
In this specific example, the list is not necessary in any case as there’s only one Column we need: billing_address = relationship("Address", foreign_keys="Customer.billing_address_id")
But I cannot determine how to use the list notation to specify multiple foreign keys in a single relationship.
For the classes
class PostVersion(db.Model):
id = db.Column(db.Integer, primary_key=True)
...
tag_1_id = db.Column(db.Integer, db.ForeignKey("tag.id"))
tag_2_id = db.Column(db.Integer, db.ForeignKey("tag.id"))
tag_3_id = db.Column(db.Integer, db.ForeignKey("tag.id"))
tag_4_id = db.Column(db.Integer, db.ForeignKey("tag.id"))
tag_5_id = db.Column(db.Integer, db.ForeignKey("tag.id"))
class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)
tag = db.Column(db.String(127))
I have tried all of the following:
tags = db.relationship("Tag", foreign_keys=[tag_1_id, tag_2_id, tag_3_id, tag_4_id, tag_5_id]) resulting in
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship AnnotationVersion.tags - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
tags = db.relationship("Tag", foreign_keys="[tag_1_id, tag_2_id, tag_3_id, tag_4_id, tag_5_id]") resulting in
sqlalchemy.exc.InvalidRequestError: When initializing mapper Mapper|AnnotationVersion|annotation_version, expression '[tag_1_id, tag_2_id, tag_3_id, tag_4_id, tag_5_id]' failed to locate a name ("name 'tag_1_id' is not defined"). If this is a class name, consider adding this relationship() to the class after both dependent classes have been defined.
And many others variations on the list style, using quotes inside and outside, using Table names and Class names.
I've actually solved the problem in the course of this question. Since there seems to be no direct documentation, I'll answer it myself instead of deleting this question.

The key is to define the relationship on a primary join and specify the uselist parameter.
tags = db.relationship("Tag", primaryjoin="or_(PostVersion.tag_1_id==Tag.id,"
"PostVersion.tag_2_id==Tag.id, PostVersion.tag_3_id==Tag.id,"
"PostVersion.tag_4_id==Tag.id, PostVersion.tag_5_id==Tag.id)",
uselist=True)

Related

Many to many relationship in SQLAlchemy with same foreign key used twice in association object

I have three tables - Competition, Competitor, and Duel. Table Duel is actually an association object between tables Competition and Competitor. The code is following:
class Competition(db.Model):
__tablename__ = 'competitions'
id_competition = db.Column(db.Integer, primary_key=True, autoincrement=True)
# some additional attributes
competitors_duel = relationship('Duel', back_populates='rel_competition')
class Competitor(db.Model):
__tablename__ = 'competitors'
id_competitor = db.Column(db.Integer, primary_key=True, autoincrement=True)
# some additional attributes
competitions_duel = relationship('Duel', back_populates='rel_competitor')
class Duel(db.Model):
__tablename__ = 'duels'
id_competitor1 = db.Column(db.ForeignKey('competitors.id_competitor'), primary_key=True)
id_competitor2 = db.Column(db.ForeignKey('competitors.id_competitor'), primary_key=True)
id_competition = db.Column(db.ForeignKey('competitions.id_competition'), primary_key=True)
phase = db.Column(db.Integer, primary_key=True)
# some additional attributes
rel_competitor = relationship('Competitor', back_populates='competitions_duel')
rel_competition = relationship('Competition', back_populates='competitors_duel')
This is the same association object as example of bidirectional association object in documentation.
Problem that I have is this:
Could not determine join condition between parent/child tables on
relationship Competitor.competitions_duel - there are multiple foreign
key paths linking the tables. Specify the 'foreign_keys' argument,
providing a list of those columns which should be counted as
containing a foreign key reference to the parent table.
The error is gone when I delete id_competitor2 (or 1). But I need both ids because it is a duel, I need to know both competitors. Also, each contest has its phase so those four keys (id_competitor1, id_competitor2, id_competition, and phase) should actually create one composite key which differentiates between duels.
How can I solve this?

SqlAlchemy AmbiguousForeign Keys Error - foreign key attribute is specified [duplicate]

Am trying to setup a postgresql table that has two foreign keys that point to the same primary key in another table.
When I run the script I get the error
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Company.stakeholder - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
That is the exact error in the SQLAlchemy Documentation yet when I replicate what they have offered as a solution the error doesn't go away. What could I be doing wrong?
#The business case here is that a company can be a stakeholder in another company.
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
class Stakeholder(Base):
__tablename__ = 'stakeholder'
id = Column(Integer, primary_key=True)
company_id = Column(Integer, ForeignKey('company.id'), nullable=False)
stakeholder_id = Column(Integer, ForeignKey('company.id'), nullable=False)
company = relationship("Company", foreign_keys='company_id')
stakeholder = relationship("Company", foreign_keys='stakeholder_id')
I have seen similar questions here but some of the answers recommend one uses a primaryjoin yet in the documentation it states that you don't need the primaryjoin in this situation.
Tried removing quotes from the foreign_keys and making them a list. From official documentation on Relationship Configuration: Handling Multiple Join Paths
Changed in version 0.8: relationship() can resolve ambiguity between
foreign key targets on the basis of the foreign_keys argument alone;
the primaryjoin argument is no longer needed in this situation.
Self-contained code below works with sqlalchemy>=0.9:
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine(u'sqlite:///:memory:', echo=True)
session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
#The business case here is that a company can be a stakeholder in another company.
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
class Stakeholder(Base):
__tablename__ = 'stakeholder'
id = Column(Integer, primary_key=True)
company_id = Column(Integer, ForeignKey('company.id'), nullable=False)
stakeholder_id = Column(Integer, ForeignKey('company.id'), nullable=False)
company = relationship("Company", foreign_keys=[company_id])
stakeholder = relationship("Company", foreign_keys=[stakeholder_id])
Base.metadata.create_all(engine)
# simple query test
q1 = session.query(Company).all()
q2 = session.query(Stakeholder).all()
The latest documentation:
http://docs.sqlalchemy.org/en/latest/orm/join_conditions.html#handling-multiple-join-paths
The form of foreign_keys= in the documentation produces a NameError, not sure how it is expected to work when the class hasn't been created yet. With some hacking I was able to succeed with this:
company_id = Column(Integer, ForeignKey('company.id'), nullable=False)
company = relationship("Company", foreign_keys='Stakeholder.company_id')
stakeholder_id = Column(Integer, ForeignKey('company.id'), nullable=False)
stakeholder = relationship("Company",
foreign_keys='Stakeholder.stakeholder_id')
In other words:
… foreign_keys='CurrentClass.thing_id')

SQLAlchemy one-to-one, store foreign key in each table?

I have a relationship that is one to one between cage codes and duns numbers.
I have set up my relationship that looks like, where I store a ForeignKey on each of the respective tables.
class Cage(Base):
__tablename__ = 'DimCage'
id = Column(Integer, primary_key=True)
cage = Column(String(8), unique=True, nullable=False)
duns_id = Column(Integer, ForeignKey('DimDuns.id'))
duns = relationship('Duns', uselist=False, back_populates='cage')
class Duns(Base):
__tablename__ = 'DimDuns'
id = Column(Integer, primary_key=True)
duns = Column(String(10), unique=True, nullable=False)
dunsInt = Column(Integer, unique=True, nullable=False)
cage_id = Column(Integer, ForeignKey('DimCage.id'))
cage = relationship('Cage', uselist=False, back_populates='duns')
When I create the tables I get the below error, how to do I set up my foreign keys so I can keep a reference on both tables?
sqlalchemy.exc.AmbiguousForeignKeysError: Can't determine join between 'DimCage' and 'DimDuns'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.
And During handling of the above exception, another exception occurred:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Cage.duns - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
I believe you only need to store one foreign key for a one-to-one relationship.
See https://docs.sqlalchemy.org/en/13/orm/basic_relationships.html#one-to-one
You shouldn't lose any data access this way. If you remove the duns_id column from Cage, you now access the id by cage.duns.id instead of cage.duns_id.
You could set the child primary key as a foreign key to the parent.
For a design perspective, it is not a bad choice to use foreign keys as primary keys in One-to-One relationships.
class Cage(Base):
__tablename__ = 'DimCage'
id = Column(Integer, primary_key=True)
cage = Column(String(8), unique=True, nullable=False)
class Duns(Base):
__tablename__ = 'DimDuns'
id = Column(Integer, ForeignKey(Cage.id) primary_key=True)
duns = Column(String(10), unique=True, nullable=False)
dunsInt = Column(Integer, unique=True, nullable=False)
cage = relationship('Cage', uselist=False, backref='duns')
Now both cage and duns have the same id. So:
session.add(Cage(id=1, duns=Duns(duns='Duns', dunsInt=10)))
sesion.commit()
id = 1
cage = select(Cage).where(Cage.id == id)
duns = select(Duns).where(Duns.id == cage.id)
assert cage.id == duns.id
Please note that the child cannot exist without the parent.
If the parent is going to be deleted then the child must be deleted first, unless you configure some cascade option.

sqlalchemy mapping multiple entries to the same backref

Perhaps this is a design issue but I have a case where I have a class that defines a default part, and alternate_parts.
class Entity(Base):
__tablename__ = "entity"
id = Column(Integer, primary_key=True)
part_id = Column(Integer, ForeignKey('part.id'))
part = relationship("Part", backref="entities")
alternate_parts = relationship("Part", secondary="associate_entity_to_parts", backref="entities")
...
class Part(Base):
__tablename__ = "part"
id = Column(Integer, primary_key=True)
...
This throws an error:
sqlalchemy.exc.ArgumentError: Error creating backref 'entities' on relationship 'Entity.alternate_parts': property of that name exists on mapper 'Mapper|Parts|part'
The purpose of this is that I want to be able to reverse look up any entities that refer to this part including ones where they are alternates. The reason for storing the alternates in this way is that this way I don't have to to store some kind of a "default" part id somewhere.
Any suggestions?

Do I need multiple association tables for this relationship?

I'm trying to get my head around the best way to construct a relationship that maps many Constants to many Items.
My initial relationship, an Item has a Constant, looked like this.
class Constant(Base):
__tablename__ = "Constant"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(64), nullable=False)
class Item(Base):
__tablename__ = "Item"
id = Column(Integer, primary_key=True, autoincrement=True)
constantId = Column(Integer, ForeignKey("Constant.id"))
constant = relationship("Constant")
However, I really need my item to have more than one constant, something like this...
class Item(Base):
__tablename__ = "Item"
id = Column(Integer, primary_key=True, autoincrement=True)
constant1Id = Column(Integer, ForeignKey("Constant.id"))
constant1 = relationship("Constant")
constant2Id = Column(Integer, ForeignKey("Constant.id"))
constant2 = relationship("Constant")
My first attempt was to use an association table...
item_to_constant_assoc = Table("itemToConstantAssoc", Base.metadata, Column("constantId", Integer, ForeignKey("Constant.id"), Column("itemId", Integer, ForeignKey("Item.id")))
while updating the Item class accordingly:
Class Item(Base):
__tablename__ = "Item"
id = Column(Integer, primary_key=True, autoincrement=True)
constant1 = relationship("Constant", secondary=item_to_constant_assoc, uselist=False)
constant2 = relationship("Constant", secondary=item_to_constant_assoc, uselist=False)
This failed (understandably when looking at the MySQL tables that were created) because Item.constant1 and Item.constant2 referred to the same entry in the association table.
My next step is to add another association table for the second constant but I have to wonder whether I'm barking up the wrong tree as I seem to be creating a large number of tables for a relatively simple mapping. I've read the documentation. It is detailed and substantial (thanks Michael Bayer!) and I may have just overlooked a section. Could anyone provide me with a few pointers either to this problem, or what I should be looking for in the docs?
Thanks!
Phil
Couldn't see the wood for the trees. This is easily accomplished by using the primaryjoin argument on the relationship.
class Item(Base):
__tablename__ = "Item"
id = Column(Integer, primary_key=True, autoincrement=True)
constant1Id = Column(Integer, ForeignKey("Constant.id"))
constant1 = relationship("Constant", primaryjoin="Constant.id==Item.constant1Id")
constant2Id = Column(Integer, ForeignKey("Constant.id"))
constant2 = relationship("Constant", primaryjoin="Constant.id==Item.constant2Id")
A many-to-many association already allows each Item to have an unlimited number of constants. You don't need anything more than this as your two base tables.
class Constant(Base):
__tablename__ = "Constant"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(64), nullable=False)
class Item(Base):
__tablename__ = "Item"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(64), nullable=False)
item_to_constant_assoc = Table("itemToConstantAssoc", Base.metadata, Column("constantId", Integer, ForeignKey("Constant.id"), Column("itemId", Integer, ForeignKey("Item.id")))
At this point, every Item has an unlimited number of Constants. When you want a specific constant, you have to query the constant by the name attribute in the Constant table. Your association table is merely a list of key pairs: (itemID, constantId).
The set of all Constants for an Item is a three-table join for all association rows joined with matching Constant rows for a given Item.
The set of all Items for a Constant is a three-table join for all association rows join with match Item rows for a given Constant.
A specific Constant for an Item needs to be retrieved via a join. You think of it like the the set of all Constants for a given Item where both the Item and the Constant name are given. The SQL involves a join even though only a single row is retrieved.
I think your generic query to associate a constant with all relevant items or an item with all relevant constants will look something like this.
query(Item). join(item_to_constant_assoc.itemId==Item.itemId). join(item_to_constant_assoc.contantId==Constant.constantId

Categories

Resources