I have many to many relationships between the same table and want to define them in Flask-AppBuilder. For example, a parent-child relationship between humans, where both instances belong to the same table "Human". However when I do this, I get the error:
Error
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition
between parent/child tables on relationship Human.child - there are no
foreign keys linking these tables. Ensure that referencing columns
are associated with a ForeignKey or ForeignKeyConstraint, or specify a
'primaryjoin' expression.
Example models.py
from flask_appbuilder import Model
from sqlalchemy import Column, String, ForeignKey
from sqlalchemy.orm import relationship
class HumanHasChild(Model):
parent_id = Column(String(200), ForeignKey("Human.id"), primary_key=True)
child_id = Column(String(200), ForeignKey("Human.id"), primary_key=True)
class Human(Model):
id = Column(String(200), primary_key=True)
child = relationship("HumanHasChild", foreign_keys="HumanHasChild.child_id")
Contrary to the error message, I do specify the foreign key three times. However I tried adding an explicit join condition as well:
class Human(Model):
id = Column(String(200), primary_key=True)
child = relationship("HumanHasChild", foreign_keys="HumanHasChild.child_id",primaryjoin="Human.id==HumanHasChild.parent_id")
However even then I get an error:
sqlalchemy.exc.ArgumentError: Could not locate any relevant foreign
key columns for primary join condition 'human.id =
human_has_child.parent_id' on relationship Human.child. Ensure that
referencing columns are associated with a ForeignKey or
ForeignKeyConstraint, or are annotated in the join condition with the
foreign() annotation.
How can I get many-to-many relationships within the same table to work?
Following SQLAlchemy Many-to-Many Relationship on a Single Table, the answer is:
from flask_appbuilder import Model
from sqlalchemy import Column, String, ForeignKey, Table
from sqlalchemy.orm import relationship
HumanHasChild = Table("HumanHashChild",Model.metadata,
Column("parent_id",String(200), ForeignKey("Human.id"), primary_key=True),
Column("child_id",String(200), ForeignKey("Human.id"), primary_key=True)
)
class Human(Model):
id = Column(String(200), primary_key=True)
child = relationship("Human",
secondary=HumanHasChild,
foreign_keys = [HumanHasChild.c.parent_id,HumanHasChild.c.child_id],
primaryjoin=id==HumanHasChild.c.parent_id,
secondaryjoin=id==HumanHasChild.c.child_id,
backref="children")
Related
I am wondering if I could use sqlalchemy relationship() with Array of ForeignKey,
like the below examples :
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.types import ARRAY
from sqlalchemy.orm import relationship
from database import Base
class Topic(Base):
__tablename__ = 'topics'
id = Column(Integer, primary_key=True, index=True)
topic_name = Column(String)
class Room(Base):
__tablename__ = 'rooms'
id = Column(Integer, primary_key=True, index=True)
room_name = Column(String)
body = Column(String)
topics_id = Column(ARRAY(Integer, ForeignKey("topics.id")))
topics = relationship("Topic", foreign_keys='topics_id', uselist=True) #Single-direction
The relationship is that a Room has many of topics in it, but a Topic does not need to belong to any room.
Since the Room model already has a topics_id field that store array of foreign_keys of Topics model,
so I tried use this topics_id, passing into relationship()
Plus I do not want the Topic to has a link to Room model
but it give me this error
sqlalchemy.exc.InvalidRequestError: When initializing mapper mapped class Room->rooms, expression 'topics_id' failed to locate a name ("name 'topics_id' is not defined"). If this is a class name, consider adding this relationship() to the <class 'models.Room'> class after both dependent classes have been defined.
You can create a many-to-many relation for a schema like this. In the many-to-many relation, your topics can be a part of many rooms and every room can have multiple topics.
You can read about it here on the sqlalchemy official documentation
https://docs.sqlalchemy.org/en/14/orm/basic_relationships.html#many-to-many
I have a self-referential relationship in sqlalchemy that is based heavily on the example found in this answer.
I have a table of users, and an association table that links a primary user to a secondary user. User A can be primary for user B, and B may or may not also be a primary for user A. It works exactly like the twitter analogy in the answer I linked above.
This works fine, except that I don't know how to establish cascade rules for an association proxy. Currently, if I delete a user, the association record remains, but it nulls out any FKs to the deleted user. I would like the delete to cascade to the association table and remove the record.
I also need to be able to disassociate users, which would only remove the association record, but would propagate to the "is_primary_of" and "is_secondary_of" association proxies of the users.
Can anyone help me figure out how to integrate these behaviors into the models that I have? Code is below. Thanks!
import sqlalchemy
import sqlalchemy.orm
import sqlalchemy.ext.declarative
import sqlalchemy.ext.associationproxy
# This is the base class from which all sqlalchemy table objects must inherit
SAModelBase = sqlalchemy.ext.declarative.declarative_base()
class UserAssociation(SAModelBase):
__tablename__ = 'user_associations'
# Columns
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
# Foreign key columns
primary_user_id = sqlalchemy.Column(sqlalchemy.Integer,
sqlalchemy.ForeignKey('users.id', name='user_association_primary_user_fk'))
secondary_user_id = sqlalchemy.Column(sqlalchemy.Integer,
sqlalchemy.ForeignKey('users.id', name='user_association_secondary_user_fk'))
# Foreign key relationships
primary_user = sqlalchemy.orm.relationship('User',
foreign_keys=primary_user_id,
backref='secondary_users')
secondary_user = sqlalchemy.orm.relationship('User',
foreign_keys=secondary_user_id,
backref='primary_users')
def __init__(self, primary, secondary, **kwargs):
self.primary_user = primary
self.secondary_user = secondary
for kw,arg in kwargs.items():
setattr(self, kw, arg)
class User(SAModelBase):
__tablename__ = 'users'
# Columns
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
first_name = sqlalchemy.Column(sqlalchemy.String)
last_name = sqlalchemy.Column(sqlalchemy.String)
is_primary_of = sqlalchemy.ext.associationproxy.association_proxy('secondary_users', 'secondary_user')
is_secondary_of = sqlalchemy.ext.associationproxy.association_proxy('primary_users', 'primary_user')
def associate(self, user, **kwargs):
UserAssociation(primary=self, secondary=user, **kwargs)
Turns out to be pretty straightforward. The backrefs in the original code were just strings, but they can instead be backref objects. This allows you to set cascade behavior. See the sqlalchemy documentation on backref arguments.
The only changes required here are in the UserAssociation object. It now reads:
# Foreign key columns
primary_user_id = sqlalchemy.Column(sqlalchemy.Integer,
sqlalchemy.ForeignKey('users.id',
name='user_association_primary_user_fk'),
nullable=False)
secondary_user_id = sqlalchemy.Column(sqlalchemy.Integer,
sqlalchemy.ForeignKey('users.id',
name='user_association_associated_user_fk'),
nullable=False)
# Foreign key relationships
primary_user = sqlalchemy.orm.relationship('User',
foreign_keys=primary_user_id,
backref=sqlalchemy.orm.backref('secondary_users',
cascade='all, delete-orphan'))
secondary_user = sqlalchemy.orm.relationship('User',
foreign_keys=secondary_user_id,
backref=sqlalchemy.orm.backref('primary_users',
cascade='all, delete-orphan'))
The backref keyword argument is now a backref object instead of a string. I was also able to make the foreign key columns non-nullable, since it now cascades deleted users such that the associations are deleted as well.
I'm using sqlacodegen for reflecting a bunch of tables from my database.
And i'm getting the following error:
sqlalchemy.exc.AmbiguousForeignKeysError: Can't determine join between 'Employee' and 'Sales'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.
Here's a simplified version of my tables.
I read in the documentation that I should use the foreign_keys parameter to resolve ambiguity between foreign key targets. Although, I think this problem is because of the inheritance. Could someone help me understand what is going on.
# coding: utf-8
from sqlalchemy import Column, ForeignKey, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Employee(Base):
__tablename__ = 'Employee'
EmployeeId = Column(Integer, primary_key=True)
class Sales(Employee):
__tablename__ = 'Sales'
EmployeeID = Column(ForeignKey('Employee.EmployeeId'), primary_key=True)
OldemployeeID = Column(ForeignKey('Employee.EmployeeId'))
employee = relationship('Employee', foreign_keys=[EmployeeID])
old_employee = relationship("Employee", foreign_keys=[OldemployeeID])
When your tables have multiple possible paths to inherit between them (Sales.EmployeeID or Sales.OldEmployeeID), SqlAlchemy doesn't know which one to use and you'll need to tell it the path explicitly, by using inherit_condition. For instance to inherit by EmployeeID:
class Sales(Employee):
...
__mapper_args__ = { "inherit_condition": EmployeeID == Employee.EmployeeId }
For the sake of example, you could also inherit by OldEmployeeID, by entering OldEmployeeID == Employee.EmployeeId - this would mean that both your Sales primary key and the Employee primary key are allowed to be different.
Just use backref and use Integer on both EmployeeID and OldemployeeID. Otherwise you will get an another error.
class Sales(Employee):
__tablename__ = 'Sales'
EmployeeID = Column(Integer, ForeignKey('Employee.EmployeeId'), primary_key=True)
OldemployeeID = Column(Integer, ForeignKey('Employee.EmployeeId'))
employee = relationship('Employee', foreign_keys=[EmployeeID], backref='Employee')
old_employee = relationship("Employee", foreign_keys=[OldemployeeID], backref='Employee')
Why do I get the TraceBack
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition
between parent/child tables on relationship County.Legislators -
there are no foreign keys linking these tables.
Ensure that referencing columns are associated with a
ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
with the following models:
class County(Base):
__tablename__ = 'tblCounty'
CountyCode = Column('CountyCode', String, primary_key=True)
Legislators = relationship('Legislators', backref='County', lazy='dynamic')
class Legislators(Base):
__tablename__ = 'VLegislators'
EmployeeNo = Column('EmployeeNo', String, primary_key=True)
CountyCode = Column('CountyCode', String, ForeignKey('County.CountyCode'))
I'm trying to map a public facing MS SQL database provided by the State of New Hampshire. So no schema changes allowed.
Why does it complain about the lack of a ForeignKey relation when one is clearly defined in class Legislators?
AFAIK you should use tablename in ForeignKey:
CountyCode = Column('CountyCode', String, ForeignKey('tblCounty.CountyCode'))
I am trying to dynamically populate a field using SqlAlchemy 0.8.4 and GeoAlchemy2 0.2.2. The goal is to assign a District to the Facility based on the facility's position when it is read from the database. The code looks like this:
class District(Base):
__tablename__ = 'districts'
id = Column(Integer, primary_key=True)
geom = Column('geog', Geometry(geometry_type='POLYGON'), nullable=False)
class Facility(Base):
__tablename__ = 'facilities'
id = Column(Integer, primary_key=True)
pos = Column('geog', Geometry(geometry_type='POINT'), nullable=True)
district = relationship(District,
viewonly=True,
primaryjoin="District.geom.ST_Contains(Facility.pos)",
foreign_keys=[District.id])
But this gives me the following error:
ArgumentError: Could not locate any relevant foreign key columns for
primary join condition 'ST_Contains(districts.geog, facilities.geog)'
on relationship Facility.district. Ensure that referencing columns
are associated with a ForeignKey or ForeignKeyConstraint, or are
annotated in the join condition with the foreign() annotation
I would really like to avoid having foreignkey relation between these classes since the districts are constantly changing, but would like to get a facility with a district set without querying the database again.
How should I solve this?