What SQLAlchemy Query should I run to traverse this backreference? - python

I have the following 3 SQLAlchemy Models:
class MyClassA(db.Model):
myclassa_id = db.Column(db.Integer, nullable=False, primary_key=True)
myfield_1 = db.Column(db.String(1024), nullable=True)
myclassbs = db.relationship("MyClassB", backref="myclassa")
class MyClassC(db.Model):
myclassc_id = db.Column(db.Integer, nullable=False, primary_key=True)
myfield_2 = db.Column(db.String(1024), nullable=False, unique=True)
class MyClassB(db.Model):
legal_document_content_format_id = db.Column(db.Integer, nullable=False, primary_key=True)
myclassa_id = db.Column(db.Integer, db.ForeignKey(MyClassA.myclassa_id), nullable=False)
myclassc_id = db.Column(db.Integer, db.ForeignKey(MyClassC.myclassc_id))
myfield_3 = db.Column(db.String(1024))
__table_args__ = (db.UniqueConstraint('myclassa_id', 'myclassc_id', name='unique_constraint_mcaid_mccid'),)
Here is what I want to do:
Suppose I have a -- an instance of MyClassA.
I want to return b -- the instance of MyClassB -- that meets the following 2 criteria:
b is a child record of a
b is a child record of the instance of MyClassC which has my_field2 == "Hello"
If no record meets those criteria, throw an exception.
How can I do this in a few compact lines of code??

q = (
db.session.query(MyClassB)
.join(MyClassA) # this will satisfy first criteria
.join(MyClassC) # criteria 2 part 1
.filter(MyClassC.my_field2 == "Hello") # criteria 2 part2
)
b_list = q.all()
assert b_list
But for this to work you also need to add relationship between B and C:
class MyClassC(db.Model):
# ...
myclassbs = db.relationship("MyClassB", backref="myclassc")
If you do not want to do that, you can modify the query not to rely on the relationship definition.

Related

sqlalchemy column_property in self-referential

I can't describe the column "invited_name" (column_property). I don't know how to do this correctly.
class Worker(declarative_base()):
__tablename__ = "staff_worker_info"
id = Column(Integer, primary_key=True)
first_name = Column(String(40), nullable=False)
last_name = Column(String(40), nullable=False)
invited_id = Column(Integer, ForeignKey('staff_worker_info.id'))
invited = relationship("Worker", uselist=False, remote_side=[id], join_depth=1)
# I don't know how to describe this column
invited_name = column_property(
select([Worker.first_name]). \
where(Worker.id == invited_id).\
label('invited_n'))
I understand why this doesn't work, but I don't know how to write it differently.
I should get such a SQL query.
SELECT staff_worker_info.id, staff_worker_info.first_name staff_worker_info.last_name, staff_worker_info.invited_id,
(SELECT worker_invited.first_name
FROM staff_worker_info AS worker_invited
WHERE staff_worker_info.invited_id = worker_invited.id) AS invited_n,
FROM staff_worker_info
Might be a bit late, but I recently faced a similar question. I think your problem is quite easy to solve with only the relationship. If you want you can also solve it by using a column_property.
First, using the relationship. If you make the invited relationship joined, then the actual query that is send to the database is a self-join. You can access the first name via that relationship (reference https://docs.sqlalchemy.org/en/14/orm/self_referential.html).
class Worker(declarative_base()):
__tablename__ = "staff_worker_info"
id = Column(Integer, primary_key=True)
first_name = Column(String(40), nullable=False)
last_name = Column(String(40), nullable=False)
invited_id = Column(Integer, ForeignKey('staff_worker_info.id'))
invited = relationship("Worker", uselist=False, remote_side=[id], join_depth=1, lazy='joined')
#property
def invited_name(self):
return self.invited.first_name
Then, if the query you want to do is more complex, and it requires you to create a column_property, you can also do it as follows (reference https://docs.sqlalchemy.org/en/14/orm/mapped_sql_expr.html):
from sqlalchemy import inspect
from sqlalchemy.orm import aliased
class Worker(declarative_base()):
__tablename__ = "staff_worker_info"
id = Column(Integer, primary_key=True)
first_name = Column(String(40), nullable=False)
last_name = Column(String(40), nullable=False)
invited_id = Column(Integer, ForeignKey('staff_worker_info.id'))
invited = relationship("Worker", uselist=False, remote_side=[id], join_depth=1)
# Requires alias
worker = aliased(Worker)
inspect(Worker).add_property(
"invited_name",
column_property(
select([worker.first_name]). \
where(worker.id == Worker.invited_id)
)
)
I found a method. But he did not like it.
invited_name = column_property(
select([text("invited_table.first_name")]).
where(text("invited_table.id = staff_worker_info.invited_id")).
select_from(text("staff_worker_info AS invited_table")).
label('invited_n'))

Filtering across related models with SQLAlchemy core API (using AIOPG)

I'm trying to do something I'm fairly sure is simple using AIOPG, which can use sqlalchemy's core API. My SQL is not great, so that's where I'm falling down here.
models
class School(Base):
__tablename__ = 'schools'
id = Column(Integer, primary_key=True, nullable=False)
sa_school = School.__table__
class SubjectCategory(Base):
__tablename__ = 'subject_categories'
id = Column(Integer, primary_key=True, nullable=False)
name = Column(String(63))
sa_subject_category = SubjectCategory.__table__
class Subject(Base):
__tablename__ == 'subjects'
id = Column(Integer, primary_key=True, nullable=False)
name = Column(String(63))
category = Column(Integer, ForeignKey('subject_categories.id'), nullable=False)
sa_subject = Subject.__table__
class SchoolSubject(Base):
__tablename__ = 'school_subjects'
id = Column(Integer, primary_key=True, nullable=False)
school = Column(Integer, ForeignKey('schools.id'), nullable=False)
subject = Column(Integer, ForeignKey('subjects.id'), nullable=False)
sa_school_subject = SchoolSubject.__table__
So I'm just trying to get all schools which teach subjects that have a certain subject_category ID.
Currently I have:
from sqlalchemy import select, join
school_c = sa_school.c
school_subj_c = sa_school_subject.c
async def get_schools(subject=None, subj_cat=None)
query = select(
[school_c.id, school_c.name]
).select_from(sa_school.join(sa_school_subject)
if subj_cat:
# Then I need to filter where a school_subj.subject.category == subj_cat
pass
elif subject:
query = query.where(sa_school_subject.c.subject == subj)
cur = await conn.execute(query)
return [dict(b) async for b in cur]
After a bit of faffing, I can simply do:
query = select(
[school_c.id, school_c.name]
).select_from(sa_school.join(sa_school_subject.join(sa_subject)))

How to make 'tags' affect a relation(column_property)

I'm working on a project with Flask-SQLAlchemy.
The model looks like this:
cars have components,
components can have issues
car has a column_property 'needs_repair' which is true when a car's component has issues
needs_repair = column_property(exists().where(and_(
carcomponent.columns['car_id'] == id,
carcomponent.columns['component_id'] == componentissue.columns['component_id']
)))
I added a table for tags with a 'skip'-column, tags are assigned via a table issue_car_tag(ignoring components, only referencing specific car-issue-relations).
Now, i want needs_repair to be true if all assigned tags have skip = False or no tags are assigned
How do I extend the column_property to achieve this?
edit:
Model/table definitions:
class Component(Base):
id = db.Column(db.Integer, primary_key=True)
[...]
issues = db.relationship('ISsue', secondary=componentissue, lazy='dynamic',
back_populates='components')
cars = db.relationship('Car', lazy = 'dynamic', secondary=carcomponent,
back_populates="component"
broken = column_property(exists().where(componentissue.columns['component_id'] == id))
class Car(Base):
id = db.Column(db.Integer, primary_key=True)
[...]
components = db.relationship('Component', secondary=carcomponent,
back_populates="cars", lazy='dynamic')
needs_repair = column_property(exists().where(and_(
carcomponent.columns['car_id'] == id,
carcomponent.columns['component_id'] == componentissue.columns['component_id']
)))
class Issue(Base):
__tablename__ = "issues"
[...]
components = db.relationship('Component' lazy = 'dynamic', secondary=componentissue,
back_populates='issues')
class Tag(Base):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, unique=True)
description = db.Column(db.Text, nullable=False, default="")
skip = db.Column(db.Boolean, default = False)
class Issue_Car_Tag(Base):
id = db.Column(db.Integer, primary_key=True)
tag_id = db.Column(db.Integer, db.ForeignKey('tag.id'))
car_id = db.Column(db.Integer, db.ForeignKey('car.id'))
issue_id = db.Column(db.Integer, db.ForeignKey('issue.id'))
tag = db.relationship('Tag', backref=db.backref('issue_car_tags'))
car = db.relationship('Car', backref=db.backref('issue_car_tags'))
issue = db.relationship('Issue', backref=db.backref('issue_car_tags'))
If you'd move the definition of Car after the definitions of Tag and Issue_Car_Tag or reference those tables in some other manner, you could produce the following query construction
func.coalesce(func.bool_and(not_(Tag.skip)), False).\
select().\
where(Tag.id == Issue_Car_Tag.tag_id).\
where(Issue_Car_Tag.car_id == id).\
as_scalar()
and use that in an OR with your existing check:
needs_repair = column_property(
or_(func.coalesce(func.bool_and(not_(Tag.skip)), False).
select().
where(Tag.id == Issue_Car_Tag.tag_id).
where(Issue_Car_Tag.car_id == id).
as_scalar(),
exists().where(and_(
carcomponent.c.car_id == id,
carcomponent.c.component_id == componentissue.c.component_id))))
The query selects tags related to a car using the association table issue_car_tag and aggregates the skip values, coalescing an empty result or all null values.
Note that this results in false if no tags are assigned, so you have to handle that separately. If I've understood your existing query correctly, this is handled by your EXISTS expression already. Put another way, the new query results in true if tags exist and all have skip set to false.

nested relationship in hybrid_property in sqlalchemy

I have a #hybrid_property which references a nested relationship self.establishment_type.establishment_base_type.name == 'QSR'. It works on a Location object as in assert location.is_qsr == True, but not in a filter. I have tried adding a #is_qsr.expression function, but can't get any of them working. How can I enable a filter such as query(Location).filter(Location.is_qsr == True)?
class Location(Base):
__tablename__ = 'houses'
id = Column(Integer, primary_key=True)
establishment_type_id = Column(
Integer, ForeignKey('establishment_types.id')
)
establishment_type = relationship('EstablishmentType')
#hybrid_property
def is_qsr(self):
if self.establishment_type:
if self.establishment_type.establishment_base_type:
return self.establishment_type.establishment_base_type.name == 'QSR'
return False
class EstablishmentType(Base):
__tablename__ = 'establishment_types'
id = Column(Integer, primary_key=True)
establishment_base_type_id = Column(
Integer, ForeignKey('establishment_base_types.id')
)
establishment_base_type = relationship('EstablishmentBaseType')
class EstablishmentBaseType(Base):
__tablename__ = 'establishment_base_types'
id = Column(Integer, primary_key=True)
You can use .has on relationships:
#is_qsr.expression
def is_qsr(cls):
return cls.establishment_type.has(
EstablishmentType.establishment_base_type.has(
EstablishmentBaseType.name == "QSR"))
This doesn't produce the most efficient query in the world (it does a EXISTS (SELECT 1 FROM ...)) but a decent optimizer should be able to figure it out.

Hybrid expression that counts number rows with condition

I have 4 tables:
class Cebola(Base):
__tablename__ = 'cebolas'
id = Column(Integer, primary_key=True, autoincrement=True)
class Checklist(Base):
__tablename__ = 'checklists'
id = Column(Integer, primary_key=True, autoincrement=True)
checklist_type = Column(String)
cebola_id = Column(Integer, ForeignKey('cebolas.id'))
cebola = relationship('Cebola', backref=backref('checklists'))
__mapper_args__ = {'polymorphic_on': checklist_type,
'polymorphic_identity': 'generic'}
class ChecklistA(Checklist):
__tablename__ = 'checklist_a'
id = Column(Integer, ForeignKey('checklists.id', ondelete='CASCADE'), primary_key=True)
notes = Column(Unicode)
__mapper_args__ = {'polymorphic_identity': 'a'}
class ChecklistB(Checklist):
__tablename__ = 'checklist_b'
id = Column(Integer, ForeignKey('checklists.id', ondelete='CASCADE'), primary_key=True)
notes = Column(Unicode)
__mapper_args__ = {'polymorphic_identity': 'b'}
Now I need a way (probably a hybrid property) that will tell me how many checklists I have in a Cebola with notes <> ''.
I have added:
class Cebola(Base):
#hybrid_property
def number_of_comments(self):
return len([c for c in self.checklists if c.notes])
#number_of_comments(cls)
def number_of_comments(cls):
???
I have found a similar problem in SQLAlchemy - Writing a hybrid method for child count, but my example is a bit more complex.
Below should get you started:
class Cebola(Base):
__tablename__ = 'cebolas'
id = Column(Integer, primary_key=True, autoincrement=True)
#hybrid_property
def number_of_comments(self):
# return len([c for c in self.checklists if c.notes])
# #NOTE:1: below will prevent error for those that do not have `notes` column
# #NOTE:2: in any case this may issue a lot of SQL statements to load all subclasses of checklists relationship, so is not really efficient
return len([c for c in self.checklists
if hasattr(c, 'notes') and c.notes])
#number_of_comments.expression
def number_of_comments(cls):
from sqlalchemy import select
ce, cl, cla, clb = [
_.__table__ for _ in (Cebola, Checklist, ChecklistA, ChecklistB)]
s = (
select([func.count(cl.c.id).label("number_of_comments")])
.select_from(cl.outerjoin(cla).outerjoin(clb))
.where(or_(cla.c.notes != None, clb.c.notes != None))
.where(cl.c.cebola_id == cls.id)
.correlate(cls)
.as_scalar()
)
return s
Usage in a query:
q = session.query(Cebola, Cebola.number_of_comments)
for cebola, number_of_comments in q.all():
print(cebola, number_of_comments)

Categories