sqlalchemy multiple foreignkey - python

In SQLAlchemy, I would like to write a set of records in table A. Then in table B I want to write a record that references an (a priori) unknown number of the records in A.
In python terms, I would create classes A and B and then have a list in B that contains objects of type A.
Can that be done?

This is not really a SQLAlchemy question, but a basic relational tables question.
You are either talking about a one-to-many relationship, or a many-to-many relationship, both of which SQLAlchemy supports out-of-the-box.
One-to-many
A one-to-many relationship is one of basic containment; a department has many employees, but each employee has only one department. The class with only one outgoing relationship gets the foreign key in this case, so employees refer to departments, not the other way around.
The example from the SQLAlchemy documentation is given as:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
So if your A classes can only be part of one B set, use this pattern.
Many-to-many
A many-to-many relationship is one where both sides can refer to more than one instance of the other side. Think of employees and projects; an employee can be part of multiple projects, and a project can involve multiple employees.
In SQL, this kind of relationship requires an association table, an extra database table that holds the mapping between the two linked types. The SQLAlchemy documentation on many-to-many relationships documents clearly how to do this; here is the example:
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)
Use this pattern if each A object can be part of more than one B set.

Related

Correct way to implement many-to-many with additional columns in Flask-SQLAlchemy?

I need to implement many-to-many relationship with additional columns in Flask-SQLAlchemy. I am currently using association table to link two models (following this guide https://flask-sqlalchemy.palletsprojects.com/en/master/models/#many-to-many-relationships). My problem is that this relationship need to have additional attached data. My two models and table are:
log = db.Table('log',
db.Column('workout_id', db.Integer, db.ForeignKey('workout.id')),
db.Column('exercise_variant_id', db.Integer, db.ForeignKey('exercise_variant.id')),
db.Column('quantity', db.Integer, nullable=False),
db.Column('series', db.Integer, nullable=False)
)
class ExerciseVariant(db.Model):
__tablename__ = 'exercise_variant'
id = db.Column(db.Integer, primary_key=True)
class Workout(db.Model):
__tablename__ = 'workout'
id = db.Column(db.Integer, primary_key=True)
exercises = db.relationship('ExerciseVariant', secondary=log, lazy='subquery',
backref=db.backref('workouts', lazy=True))
This approach is working ok, but the current method for adding records to log table seems a bit hacky to me, since I have to first query both objects to get id I am looking for and then create custom statement:
statement = log.insert().values(
workout_id=workout.id,
exercise_variant_id=exercise_variant.id,
quantity=exercise_dict['quantity'],
series=exercise_dict['series']
)
db.session.execute(statement)
My questions are:
1. Should this kind of relationship be implemented using Table or Model?
2. If an answer to 1. is Table, can I somehow use backrefs to pass object instances instead of querying and passing their id?

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.

SQLAlchemy - Relationship with filtering based on subsquery

Given two tables:
class A(Base):
__tablename__ = 'A'
a_id = Column(Integer, primary_key=True)
class AInfo(Base)
a_id = Column(Integer, ForeignKey(A.a_id), primary_key=True)
version = Column(Integer, primary_key=True)
info = Column(String)
I'd like to create a relationship from A to the latest version of AInfo (highest version number). It can be view only.
Is there any way to do this? Is there a way to put a subquery in a relationship?
Note: I can't simply put version on A because in practice I have multiple tables with different version numbers, and I just want the latest version of each.
Currently I'm using a property with a subquery instead of a relationship.
Thanks!

SQLAlchemy - Multiple Classes, Identical Tables

Been searching for this answer for a bit now, but I can't seem to find it, as everything refers back to joined table inheritance, which I understand, but do not want to use. I am looking to create multiple classes in SQLAlchemy that are identical in table construction, only differing in the class name and database table name. I am intentionally separating the tables and not using a discriminator because I expect these tables to grow to very large sizes. There is also a potential that the table schemas may diverge slowly over time, with some fields added to one but not another.
I know the following code doesn't work, as SQLAlchemy tries to find a foreign key for joined table inheritance rather than making them independent tables, but it's basically what I'm going for. I've been over the docs, but can't figure out the way to properly implement this. Is there a way (or multiple ways) to do this?
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class HDD(Base):
"""Class representing a hard drive."""
__tablename__ = 'HDDs'
_id = Column(
Integer,
doc="Auto-incrementing primary key",
name="id",
primary_key=True)
manufacturer = Column(
String(40),
doc="Hard drive manufacturer.")
...
class SDD(HDD):
__tablename__ = 'SSDs'
...
Use __abstract__ = True with no __tablename__ on the base class, and inherit from that.
class HDD(Base):
__abstract__ = True
_id = ...
class SDD(HDD):
__tablename__ = 'SSDs'
class SSD2(HDD):
__tablename = 'SSDs2'
This looks like Concrete Table Inheritance, as discussed in the Mapping Class Inheritance Hierarchies chapter of SQLAlchemy docs.
This is the way I ended up getting this working, with help from inklesspen from #sqlalchemy on freenode. (Thank you!)
class ComponentMixin(object):
"""Pythonic Mixin that defines columns common to most component types."""
_id = Column(
Integer,
doc="Auto-incrementing primary key",
name="id",
primary_key=True)
....
#declared_attr
def _server_id(self): # pylint: disable=R0201
"""
Foreign key to owning server.
MUST BE A DECLARED_ATTR since it's a foreign key!
"""
Subclass below inherits:
class HDD(ComponentMixin, ConcreteBase, Base):
"""Class representing a hard drive."""
__tablename__ = 'HDDs'
__mapper_args__ = {
'polymorphic_identity': 'hdd',
'concrete': True}

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

Categories

Resources