I'm trying to do this:
class Foo(Base):
id = Column(Integer, primary_key=True)
class Bar(Foo):
id = Column(Integer, primary_key=True)
class FooBarAssociation(Base):
foo_id = Column(Integer, ForeignKey('foo_table.id'))
bar_id = Column(Integer, ForeignKey('bar_table.id'))
foo = relationship(Foo, backref=...)
bar = relationship(Bar, backref=...)
...but I get errors like this:
Could not determine join condition between parent/child tables on relationship FooBarAssociation.foo. Specify a 'primaryjoin' expression. If this is a many-to-many relationship, 'secondaryjoin' is needed as well.
I've tried specifying foreign_keys and primary_join-s in the relationship declarations, but all for naught. Help? Is the inheritance of Bar from Foo messing with me?
thanks!
Following should work (exactly what the error tells: missing primaryjoin):
class FooBarAssociation(Base):
foo_id = Column(Integer, ForeignKey('foo_table.id'), primary_key = True, )
bar_id = Column(Integer, ForeignKey('bar_table.id'), ForeignKey('foo_table.id'), primary_key = True, )
foo = relationship(Foo, backref="bars", primaryjoin=(foo_id==Foo.id))
bar = relationship(Bar, backref="foos", primaryjoin=(bar_id==Bar.id))
As you can see, there are two foreign keys on the bar_id column. This could be required due to inheritance, or you might remove one. But if you do not store any other information apart of the many-to-many relationship, then you might consider Association Proxy instead.
Related
I try to add some contribution on 3c7's cool project, and I want to apply a filter on a join query(sqlalchemy).
Simple statement: multiple rules(table) can have multiple tags(table) - I want to filter out some rules based on some tags.
[Rules][2] table (rule_id, etc)
[Tags][2] Table (tag_id, etc)
tags_rules(Junction table) (rule_id,tag_id) -- no declaration
Issue: Applying a filter after join of course that will remove only the rules that have only one tag(the one that I specify). If a rule has multiple tags, one record from the join result will be removed, but the rule will still appear in there are any other tags associated with that rule
Sql alchemy declaration:
class Rule(Base):
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
name = Column(String(255), index=True)
meta = relationship("Meta", back_populates="rule", cascade="all, delete, delete-orphan")
strings = relationship("String", back_populates="rule", cascade="all, delete, delete-orphan")
condition = Column(Text)
imports = Column(Integer)
tags = relationship("Tag", back_populates="rules", secondary=tags_rules)
ruleset_id = Column(Integer, ForeignKey("ruleset.id"))
ruleset = relationship("Ruleset", back_populates="rules")
class Tag(Base):
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
name = Column(String(255), index=True)
rules = relationship("Rule", back_populates="tags", secondary=tags_rules)
I tried with sub queries but the most feasible seem to apply a filter on the tags table before the join.
Current implementation:
rules = rules.select_from(Tag).join(Rule.tags).filter(~Tag.name.in_(tags))
Any idea is greatly appreciated.
I have a piece of working code but it is very inefficient, instead of a single query with a join. I get one initial query, followed by one query per row in the response.
I have to following scenario:
class Job(Base, SerializeMixin, JobInterface):
__tablename__ = 'job_subjobs'
id = Column(Integer, primary_key=True, autoincrement=True)
group_id = Column(Integer, ForeignKey("job_groups.id"), nullable=False)
class Crash(Base, SerializeMixin):
__tablename__ = 'crashes'
id = Column(Integer, primary_key=True, autoincrement=True)
job_id = Column(Integer, ForeignKey("job_subjobs.id", ondelete='CASCADE'), nullable=False)
job = relationship('Job', backref='Crash')
#hybrid_property
def job_identifier(self):
return "{}:{}".format(self.job.group_id, self.job.id)
So given the above and I perform a query for all Crashes, It will perform one SELECT for all crashes. When I iterate and ask for job_identifier it will then do one separate SELECT for each crash.
self.session.query(Crash).all()
Is there someway i can create a #hybrid_property referencing a different table and have it JOIN from the beginning and preload the expression?
I've experimented with #xxx.expression without success. If all else fails I can add another foreign key in Crash table, but I would like to avoid changing current data structure if possible.
ended up using:
jobs = relationship('Job', backref='Crash', lazy='joined')
According to SQLAlchemy's documentation for many-to-many relationships, the join table is declared using Traditional mappings. The other tables are declared using Declarative mappings.
Why not just one type of mapping, like Declarative? Is that possible in this case?
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)
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
It's absolutely possible, but the reason people don't typically do it is simply because they usually don't want to use the association table like an object.
It'd look something like:
class Left(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True
class Right(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
class M2MRightLeft(Base):
__tablename__ = 'm2m_right_left'
left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
That being said, I'd generally stick with the traditional mappings for M2M relationships. Generally, I only use the declarative style if there are additional columns I want to add to the M2M table.
Suppose I am modelling postal address changes. I'd like each AddressChange to have a before relationship to an Address, as well as an after relationship to another Address. And I'd like a reference back from the Address to the AddressChange it is associated with.
class AddressChange(Base):
__tablename__ = 'AddressChanges'
id = Column(Integer, primary_key=True)
before_id = Column(Integer, ForeignKey('Addresses.id'))
before = relationship('Address', foreign_keys=before_id, uselist=False,
back_populates='change')
after_id = Column(Integer, ForeignKey('Addresses.id'))
after = relationship('Address', foreign_keys=after_id, uselist=False,
back_populates='change')
class Address(Base):
__tablename__ = 'Addresses'
id = Column(Integer, primary_key=True)
street, city, etc = Column(String), Column(String), Column(String)
change = relationship('AddressChange')
However SQLAlchemy complains:
Could not determine join condition between parent/child tables on relationship Address.change - 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.
My Address does not have a foreign key reference to the parent table, and it's not clear to me why it should need one. If I add one, I get
Address.change and back-reference AddressChange.before are both of the same direction symbol('MANYTOONE'). Did you mean to set remote_side on the many-to-one side ?
Which is starting to get confusing, because the documentation for remote_side is for "self-referential relationships."
Thanks to #alex-grönholm for helping on #sqlalchemy.
This can be solved by adding a primaryjoin parameter to Address's side of the relationship to teach it how to map back to the parent AddressChange:
change = relationship('AddressChange', uselist=False,
viewonly=True,
primaryjoin=or_(
AddressChange.before_id == id,
AddressChange.after_id == id
))
I've a case where I'm using one table to store user and group related datas. This column is called profile. So, basically this table is many-to-many table for the cases where one user is belonging in to many groups or there are many users in one group.
I'm a bit confused how it should be described...
Here's a simplified presentation of the class.
Entity relationship model
user_group_table = Table('user_group', metadata,
Column('user_id', Integer,ForeignKey('profiles.id',
onupdate="CASCADE", ondelete="CASCADE")),
Column('group_id', Integer, ForeignKey('profiles.id',
onupdate="CASCADE", ondelete="CASCADE"))
)
class Profile(Base)
__tablename__ = 'profiles'
id = Column(Integer, autoincrement=True, primary_key=True)
name = Column(Unicode(16), unique=True) # This can be either user- / groupname
groups = relationship('Profile', secondary=user_group_table, backref = 'users')
users = relationship('Profile', secondary=user_group_table, backref = 'groups')
#Example of the usage:
user = Profile()
user.name = 'Peter'
salesGroup = Profile()
salesGroup.name = 'Sales'
user.groups.append(salesGroup)
salesGroup.users
>[peter]
First of all, I agree with Raven's comment that you should use separate tables for Users and Groups. The reason being that you might get some inconsistent data where a User might have other Users as its users relations, as well as you might have cycles in the relationship tree.
Having said that, to make the relationship work declare it as following:
...
class Profile(Base):
__tablename__ = 'profiles'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(Unicode(16), unique=True) # This can be either user- / groupname
groups = relationship('Profile',
secondary=user_group_table,
primaryjoin=user_group_table.c.user_id==id,
secondaryjoin=user_group_table.c.group_id==id,
backref='users')
...
Also see Specifying Alternate Join Conditions to relationship() documentation section.