Sqlalchemy One - Many and One-One in one table - python

I've got two models: User and Group.
User can be in one group so:
class User(db.Model):
# other fields
group_id = db.Column(db.Integer(), db.ForeignKey('group.id'))
but on the other hand I would also have some info about user who create that specific group:
class Group(db.Model):
# other fields
users = db.relationship("User", backref='group')
created_by = db.Column(db.Integer(), db.ForeignKey('user.id'))
Result is:
sqlalchemy.exc.CircularDependencyError: Can't sort tables for DROP; an unresolvable foreign key dependency exists between tables: group, user. Please ensure that the ForeignKey and ForeignKeyConstraint objects involved in the cycle have names so that they can be dropped using DROP CONSTRAINT.
I tried use_alter=True, but it gives me:
sqlalchemy.exc.CompileError: Can't emit DROP CONSTRAINT for constraint ForeignKeyConstraint(

Interestingly I'd expect you to get an AmbiguousForeignKeyError but instead you seem to get a CircularDependencyError? According to the docs this is caused by two scenarios:
In a Session flush operation, if two objects are mutually dependent on each other, they can not be inserted or deleted via INSERT or
DELETE statements alone; an UPDATE will be needed to post-associate or
pre-deassociate one of the foreign key constrained values. The
post_update flag described at Rows that point to themselves / Mutually
Dependent Rows can resolve this cycle.
In a MetaData.sorted_tables
operation, two ForeignKey or ForeignKeyConstraint objects mutually
refer to each other. Apply the use_alter=True flag to one or both, see
Creating/Dropping Foreign Key Constraints via ALTER.
I'm not sure what you're executing that's causing this particular error, but most likely you'll be able to solve it by solving the ambiguous reference.
The ambigious reference is due to SQLAlchemy not being able to figure out how to perform the join when there are multiple references (users and created_by in this case). This can be resolved by specifying how the relationship should join which can be done by either giving the specific foreign key it should use or by explicitly determining the join condition.
You can see these being applied to your example here:
class User(Base):
# Other setup / fields
group_id = Column(Integer, ForeignKey('group.id'))
class Group(Base):
# Other setup / fields
created_by_id = Column(Integer, ForeignKey('user.id'), nullable=False)
created_by = relationship("User", foreign_keys=[created_by_id])
users = relationship("User", backref="group", primaryjoin=id==User.group_id)
Documentation regarding relationship joins: http://docs.sqlalchemy.org/en/latest/orm/join_conditions.html#configuring-how-relationship-joins

Related

SqlAlchemy doubly linked tables [duplicate]

I'm trying to model the following situation: A program has many versions, and one of the versions is the current one (not necessarily the latest).
This is how I'm doing it now:
class Program(Base):
__tablename__ = 'programs'
id = Column(Integer, primary_key=True)
name = Column(String)
current_version_id = Column(Integer, ForeignKey('program_versions.id'))
current_version = relationship('ProgramVersion', foreign_keys=[current_version_id])
versions = relationship('ProgramVersion', order_by='ProgramVersion.id', back_populates='program')
class ProgramVersion(Base):
__tablename__ = 'program_versions'
id = Column(Integer, primary_key=True)
program_id = Column(Integer, ForeignKey('programs.id'))
timestamp = Column(DateTime, default=datetime.datetime.utcnow)
program = relationship('Filter', foreign_keys=[program_id], back_populates='versions')
But then I get the error: Could not determine join condition between parent/child tables on relationship Program.versions - 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.
But what foreign key should I provide for the 'Program.versions' relationship? Is there a better way to model this situation?
Circular dependency like that is a perfectly valid solution to this problem.
To fix your foreign keys problem, you need to explicitly provide the foreign_keys argument.
class Program(Base):
...
current_version = relationship('ProgramVersion', foreign_keys=current_version_id, ...)
versions = relationship('ProgramVersion', foreign_keys="ProgramVersion.program_id", ...)
class ProgramVersion(Base):
...
program = relationship('Filter', foreign_keys=program_id, ...)
You'll find that when you do a create_all(), SQLAlchemy has trouble creating the tables because each table has a foreign key that depends on a column in the other. SQLAlchemy provides a way to break this circular dependency by using an ALTER statement for one of the tables:
class Program(Base):
...
current_version_id = Column(Integer, ForeignKey('program_versions.id', use_alter=True, name="fk_program_current_version_id"))
...
Finally, you'll find that when you add a complete object graph to the session, SQLAlchemy has trouble issuing INSERT statements because each row has a value that depends on the yet-unknown primary key of the other. SQLAlchemy provides a way to break this circular dependency by issuing an UPDATE for one of the columns:
class Program(Base):
...
current_version = relationship('ProgramVersion', foreign_keys=current_version_id, post_update=True, ...)
...
This design is not ideal; by having two tables refer to one another, you cannot effectively insert into either table, because the foreign key required in the other will not exist. One possible solution in outlined in the selected answer of
this question related to microsoft sqlserver, but I will summarize/elaborate on it here.
A better way to model this might be to introduce a third table, VersionHistory, and eliminate your foreign key constraints on the other two tables.
class VersionHistory(Base):
__tablename__ = 'version_history'
program_id = Column(Integer, ForeignKey('programs.id'), primary_key=True)
version_id = Column(Integer, ForeignKey('program_version.id'), primary_key=True)
current = Column(Boolean, default=False)
# I'm not too familiar with SQLAlchemy, but I suspect that relationship
# information goes here somewhere
This eliminates the circular relationship you have created in your current implementation. You could then query this table by program, and receive all existing versions for the program, etc. Because of the composite primary key in this table, you could access any specific program/version combination. The addition of the current field to this table takes the burden of tracking currency off of the other two tables, although maintaining a single current version per program could require some trigger gymnastics.
HTH!

Flask SQLAlchemy query join

I have 2 table like this:
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
index = db.Column(db.String(64))
users = db.relationship('User',
backref='role', lazy='dynamic')
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, index=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
Then I try to making 2 kinds of query to get data for the relationship models.
first, I make it like this:
user = db.session.query(User, Role.index).filter_by(email=form.email.data).first()
and the second one I use join statement on that:
user = db.session.query(User, Role.index).join(Role).filter(User.email==form.email.data).first()
My questions are, what's the difference in that query while in the second one I use the join statement but the result still same.
For the fast query or performance, should I use the first or the second one..?
The difference is that the first query will add both users and roles to FROM list, which results in a CROSS JOIN. In other words every row from users is joined with every row from roles. The second query performs an INNER JOIN and SQLAlchemy deduces the ON clause based on the foreign key relationship between the tables.
You should use the first one when you want a cartesian product, and the second one when you want the role related to the user by the foreign key relationship. That the result happens to be the same for you is just a coincidence.
For future reference, try enabling echo so that you can check from your logs what queries are actually emitted. Also have a look at defining ORM relationships, which would allow you to have a role attribute on User for accessing its related Role.
If your entities are from different classes/tables then joining is implied and SQL Alchemy will add it to actual SQL. You may add custom join if that connection isn't the one that SQL Alchemy uses (retrieved from foreign key or such).

SQLAlchemy one-to-many relationship (Single table with join table)

I have db that I cannot modify, it has two tables 'people' and 'relation'. The table 'people' has names, ids and the column parent (yes/no). The table 'relation' contains a foreign key 'people.id' for parent and a 'people.id' for its child. I want to join columns in the people table so I can
People.query.filter_by(id='id of the parent')
to get the name of the parent and it's childs. This is my code:
class People(db.model):
__tablename__ = 'people'
id = db.Column(db.integer(), primary_key=True
name = db.Column(db.String())
parent = db.Column(db.Integer()) ##0 for no 1 for yes
parent_id=db.relationship('Link',backref=db.backref('Link.parent_id')
class Link(db.Model):
_tablename__ = 'link'
parent_id=db.Column(db.Integer(),db.ForeignKey('people.id'),primary_key=True)
id = db.Column(db.Integer(), db.ForeignKey('people.id'), primary_key=True)
dateofbirth = db.Column(db.Integer())
SQLAlchemy tells me:
ArgumentError: relationship 'parent_id' expects a class or a mapper argument (received: <class 'sqlalchemy.sql.schema.Table'>)
Excuse me if I messed up, but it's my first question here (and also the first steps with SQLAlchemy)
Typically you would want to set up the foreign key and backref in the same table, like this:
class Link(db.Model):
_tablename__ = 'link'
parent_id = db.Column(db.Integer(),db.ForeignKey('people.id'),primary_key=True)
parent = db.relationship('People', backref='links')
Now you can access each Link entries parent via Link.parent, and you can get a list of each People entries links via People.links (assuming this is a one-to-many relationship).
Also, if People.parent is supposed to represent a boolean value then:
1.) you should follow the standard naming convention and call it something like is_parent
2.) you should declare People.parent as a db.Boolean type, not a db.Integer. In most (probably all) database implementations, using booleans instead of integers (when appropriate) is more memory efficient.
I hope this helped.

How to delete a one-to-one relationship with SQLAlchemy

I would like to create a nullable, self-referencing relationship which can be deleted using SQLAlchemy. An example model is as follows (note, using Flask-SQLAlchemy):
class Person(db.Model):
__tablename__ = 'person'
id = db.Column(db.Integer, primary_key=True)
partner_id = db.Column(db.Integer, db.ForeignKey('person.id'), nullable=True)
partner = db.relationship('Person', uselist=False)
So think of this as a table of cops who have only a single partner, but that partner may turn out to have been in the mafia all along, so they lose their partner for a while. A cop without a partner is fine, at least in database terms - but I assume over the course of the show their partnerless status means a lot of property damage.
Needless to say, this question: sqlalchemy: one-to-one relationship with declarative discusses how to set up this relationship. The question is how do you remove the relationship? Normally with a different foreign key you'd do this as follows:
joe.partner.remove(larry)
Where joe and larry are both Person objects. However, via the uselist argument, joe.partner is now actually a Person with no remove method.
How to delete one-to-one relationships is buried away in the SQLAlchemy documentation under the explanation of Cascades: https://docs.sqlalchemy.org/en/14/orm/cascades.html#notes-on-delete-deleting-objects-referenced-from-collections-and-scalar-relationships
The delete-orphan cascade can also be applied to a many-to-one or
one-to-one relationship, so that when an object is de-associated from its parent, it is also automatically marked for deletion. Using
delete-orphan cascade on a many-to-one or one-to-one requires an
additional flag relationship.single_parent which invokes an assertion
that this related object is not to shared with any other parent
simultaneously
So you'll want to set up your one-to-one relationship like so:
partner = db.relationship(
'Person',
cascade='all, delete-orphan',
uselist=False,
single_parent=True,
)
Then, deleting a Person's partner is just a matter of setting it to None:
some_person.partner = None
session.flush() # will delete the partner object

A django like unique together in turbogears/sqlalchemy

This question essentially two parts.
1. I have a situation where I require things to be unique together i.e the elements in db need to be unique together with each other.
Lets say we have a model Things ( Rough PseudoCode)
Class ShoppingList( object ):
thing1_id = Column(Integer)
thing2_id = Column(Integer)
Now I need thing1_id and thing2_id to be a unique together ie the set of thing1_id and thing2_id needs to be unique together. Coming from django world I know that you can do a meta declaration in django models of unique_together. But how can do this in turbogears .
Also how do I actually apply a unique_together on a legacy system.
You simply want to add a UniqueConstraint to your table definition (using a primary key would achive similar effects, but with different semantics nevertheless).
This is as simple as:
Class ShoppingList( object ):
thing1_id = Column(Integer)
thing2_id = Column(Integer)
__table_args__ = (
UniqueConstraint('thing1_id', 'thing2_id'),
)
See also https://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/table_config.html#table-configuration
For the first part of your question, if I understand your question correctly, I believe you are talking about the need for defining composite primary keys. As stated in http://docs.sqlalchemy.org/en/latest/core/schema.html#describing-databases-with-metadata:
Multiple columns may be assigned the primary_key=True flag which denotes a multi-column primary key, known as a composite primary key.
Defining such a relationship on a class using the declarative ORM way in SQLAlchemy, should be as simple as:
class ShoppingList(Base):
thing1_id = Column(Integer, primary_key=True)
thing2_id = Column(Integer, primary_key=True)
As for the second part of your question, I believe you mean how one would define the same SQLAlchemy mapping for an existing, legacy database. If so, you should be able to use the above approach, just don't create the database from the ORM definition. You may also use the classic mapping way, described in: http://docs.sqlalchemy.org/en/rel_0_8/orm/mapper_config.html?highlight=composite%20primary%20key#classical-mappings

Categories

Resources