SQLAlchemy conditional relationship - sqlalchemy

I have 2 db models like this:
class Movie(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=False)
show_start = db.Column(db.Integer)
show_times = db.relationship('ShowTime')
class ShowTime(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=False)
theatre_id = db.Column(db.String(255))
date_time = db.Column(db.Integer)
movie_id = db.Column(db.Integer, db.ForeignKey('movie.id'))
Is it possible to make the show_times relationship conditional, i.e. it would react to the theatre_id I pass to it and select only show_times with that id?
I need that to pass query params to my REST API built on top of these models, something like this:
http://my-api.com/movies?theatre_id=3

It's done with SQLAlchemy's bindparam() function. A relationship is created for Movie model:
show_times = db.relationship(lambda: ShowTime,
primaryjoin=lambda: db.and_(
Movie.id == ShowTime.movie_id,
ShowTime.theatre_id == db.bindparam('theatre_id')))
Then a query would be something like this:
Movie.query.params(theatre_id=3).options(db.joinedload(Movie.show_times)).all()

Related

SqlAlchemy - make a query where Relationship Attribute is a list

I have two models:
class Profile(Base):
__tablename__ = 'profiles'
id = Column(Integer, primary_key=True)
...
stagesP_list = relationship(
'StageP',
back_populates='profiles_list',
secondary=stageP_profile
)
class Project(Base):
__tablename__ = 'projects'
id = Column(Integer, primary_key=True)
...
stagesP_list = relationship(
'StageP',
back_populates='projects_list',
secondary=stageP_project
)
I need to select Profiles for which at least one value of the Profile.stagesP_list is contained in the project.stagesP_list.
Please help to compose the query or indicate the direction in which to search.
If you have project instance loaded, you can compose the following query:
project = ...
stageP_ids = [obj.id for obj in project.stagesP_list]
query = session.query(Profile).filter(
Profile.stagesP_list.any(StageP.id.in_(stageP_ids))
)
You can also perform joins on the database directly from having only project_id:
query = (
session.query(Profile)
.join(StageP, Profile.stagesP_list)
.join(Project, StageP.projects_list)
.where(Project.id == project_id)
.distinct()
)

SQLAlchemy relationship selection criteria

I have two model classes:
class Programs(db.Model):
__tablename__ = "programs"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(100), nullable=False)
duration = db.Column(db.Integer, nullable=False)
date_created = db.Column(db.DATE, default=datetime.now())
created_by = db.Column(db.String(100))
program_sessions = db.relationship('Program_Session',backref='programs')
class Program_Session(db.Model):
__tablename__ = "program_session"
id = db.Column(db.Integer, primary_key=True)
session_title = db.Column(db.String(100), nullable=False)
session_description = db.Column(db.String(100))
session_year = db.Column(db.Integer)
program_id = db.Column(db.Integer, db.ForeignKey("programs.id"), nullable=False)
students = db.relationship('Student_Registration', backref='program_session')
date_created = db.Column(db.DATE, default=datetime.now())
created_by = db.Column(db.String(100))
I create an object of Programs with:
program = Programs.query.first()
Now I can access all the Program_Sessions from the selected Program:
print(pro.program_sessions)
Is it possible to subquery/query to retrieve only those Program_session in Program whose year is 2021?
Option-1: filter on 'python' (in memory)
Once you get all Program_Sessions (all_sessions = pro.program_sessions), you filter them by sessions_2021 = [item for item in all_sessions if item.session_year == 2021].
Needless to say, this is not efficient at all as lots of data will be loaded from the database to be immediately discarded.
Option2: use Dynamic Relationship Loaders
Define the relationship with lazy="dynamic", which will return a Query and hence you will be able to apply additional criteria to the query:
class Programs(db.Model):
# ...
program_sessions = db.relationship('Program_Session', backref='programs', lazy="dynamic")
program = Programs.query.first()
sessions_2021 = program.program_sessions.filter(Program_Session.year == 2021).all()
Option3: use orm.with_parent [BEST]
sessions_2021 = select(Program_Session).where(with_parent(program, Program_Session.programs)).where(Program_Session.year == 2021)
The answer is yes...
van's answer shows you options for playing with sqlalchemy's query mechanism. But what if you want to write this logic on the Programs class itself? That way anywhere you have a Programs object, you can access the filter.
You can do it in pretty plain python by altering the Programs class like so:
class Programs(db.Model):
__tablename__ = "programs"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(100), nullable=False)
duration = db.Column(db.Integer, nullable=False)
date_created = db.Column(db.DATE, default=datetime.now())
created_by = db.Column(db.String(100))
program_sessions = db.relationship('Program_Session',backref='programs')
'''Here I add a filter that returns only the sessions for a particular year
'''
def program_sessions_by_year(self, year):
return filter(lambda ps: ps.session_year == year, self.program_sessions)
If you care about efficiency, you can get the database to do the filtering for you using a bit more sqlalchemy magic:
from sqlalchemy.orm import object_session
class Programs(db.Model):
__tablename__ = "programs"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(100), nullable=False)
duration = db.Column(db.Integer, nullable=False)
date_created = db.Column(db.DATE, default=datetime.now())
created_by = db.Column(db.String(100))
program_sessions = db.relationship('Program_Session',backref='programs')
'''Improve efficiency by using DB's SQL engine to filter the object.
'''
def program_sessions_by_year(self, year):
return object_session(self)\
.query(Program_Session)\
.filter_by(session_year=year, program_id=self.id)\
.all()
Either way you can then write (where-ever you have a Program object):
# lets say you just want the first program
first_program = Programs.query.first()
# to get the program sessions by year 2021
first_program.program_sessions_by_year(2021)
There's probably a bunch of other ways you could do something like this. SqlAlchemy is a big library. For more background on my answer, have a look at the SQL expressions as Mapped Attributes docs.

Confused with multiple Join in single query, Sqlalchemy

I am trying to join three tables in a single query in SQLAlchemy specifically but unable to do so.
I have done this so far :-
Test.query.join(Test.sections).join(QuestionSection.section).all()
I know it is wrong but I am trying to do it in a single query.
Here are my tables :-
Question section Model :-
class QuestionSection(Dictifiable, db.Model):
__tablename__ = 'question_section'
section_id = Column(Integer, ForeignKey('section.id'), primary_key=True)
question_id = Column(Integer, ForeignKey('question.id'), primary_key=True)
question = db.relationship('Question', backref='question_section')
section = db.relationship('Section', backref='question_section')
Section Table :-
class Section(Dictifiable, db.Model):
__tablename__ = 'section'
id = Column(Integer, Sequence('section_id_seq'), primary_key=True)
test_id = Column(Integer, ForeignKey('test.id'))
name = Column(String(50))
test = relationship("Test", back_populates="sections")
questions = db.relationship('Question',
secondary='question_section')
And Test table :-
id = Column(Integer, Sequence('test_id_seq'), primary_key=True)
name = Column(String(50))
sections = relationship("Section", back_populates="test")
tests = relationship("Corporate", secondary='corporate_test',
back_populates='tests')
I understood what I was doing wrong, I should join like this :-
Test.query.join(Test.sections).join(QuestionSection, QuestionSection.section_id == Section.id).all()

How to query a relationship on multiple polymorphic-inheritance tables?

Let's say you have the following simplified example schema, which uses SQLAlchemy joined table polymorphic inheritance. Engineer and Analyst models have a Role relationship. The Intern model does not.
class Role(db.Model):
__tablename__ = 'role'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(16), index=True)
class EmployeeBase(db.Model):
__tablename__ = 'employee_base'
id = db.Column(db.Integer, primary_key=True)
some_attr = db.Column(db.String(16))
another_attr = db.Column(db.String(16))
type = db.Column(db.String(50), index=True)
__mapper_args__ = {
'polymorphic_identity': 'employee',
'polymorphic_on': type
}
class Engineer(EmployeeBase):
__tablename__ = 'engineer'
id = db.Column(db.Integer, db.ForeignKey('employee_base.id'), primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id'), index=True)
role = db.relationship('Role', backref='engineers')
__mapper_args__ = {
'polymorphic_identity': 'engineer',
}
class Analyst(EmployeeBase):
__tablename__ = 'analyst'
id = db.Column(db.Integer, db.ForeignKey('employee_base.id'), primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id'), index=True)
role = db.relationship('Role', backref='analysts')
__mapper_args__ = {
'polymorphic_identity': 'analyst',
}
class Intern(EmployeeBase):
__tablename__ = 'intern'
id = db.Column(db.Integer, db.ForeignKey('employee_base.id'), primary_key=True)
term_ends = db.Column(db.DateTime, index=True, nullable=False)
__mapper_args__ = {
'polymorphic_identity': 'intern',
}
If I want to find Employees with a Role name having "petroleum" somewhere in the name, how would I do that?
I've tried many, many approaches. The closest I've come is this, which only returns Analyst matches:
employee_role_join = with_polymorphic(EmployeeBase,
[Engineer, Analyst])
results = db.session.query(employee_role_join).join(Role).filter(Role.name.ilike('%petroleum%'))
If I try to do something like this, I get an AttributeError, because I'm searching on an attribute of the joined Role table:
employee_role_join = with_polymorphic(EmployeeBase,
[Engineer, Analyst])
results = db.session.query(employee_role_join).filter(or_(
Engineer.role.name.ilike('%petroleum%'),
Analyst.role.name.ilike('%petroleum%')))
You can try specifying the join ON clause explicitly since the issue with your first query seems to be that Role is joining only on the analyst.role_id column:
employee_role_join = with_polymorphic(EmployeeBase, [Engineer, Analyst])
results = session.query(employee_role_join).join(Role).filter(Role.name.ilike('%petroleum%'))
print(str(results))
SELECT employee_base.id AS employee_base_id,
employee_base.some_attr AS employee_base_some_attr,
employee_base.another_attr AS employee_base_another_attr,
employee_base.type AS employee_base_type,
engineer.id AS engineer_id,
engineer.role_id AS engineer_role_id,
analyst.id AS analyst_id,
analyst.role_id AS analyst_role_id
FROM employee_base
LEFT OUTER JOIN engineer ON employee_base.id = engineer.id
LEFT OUTER JOIN analyst ON employee_base.id = analyst.id
JOIN role ON role.id = analyst.role_id
WHERE lower(role.name) LIKE lower(?)
employee_role_join is an AliasedClass that exposes both Analyst and Engineer, which we can then use to create a join-ON clause like so:
results = session.query(employee_role_join)\
.join(Role, or_( \
employee_role_join.Engineer.role_id==Role.id, \
employee_role_join.Analyst.role_id==Role.id \
))\
.filter(Role.name.ilike('%petroleum%'))
which changes the resulting SQL to JOIN role ON engineer.role_id = role.id OR analyst.role_id = role.id
Define the role_id on EmployeeBase. Even though Intern doesn't have the relationship back to the role table, the field can be null for that case.
I changed EmployeeBase to this:
class EmployeeBase(db.Model):
__tablename__ = 'employee_base'
id = db.Column(db.Integer, primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id'), index=True)
given_name = db.Column(db.String(16))
surname = db.Column(db.String(16))
type = db.Column(db.String(50), index=True)
__mapper_args__ = {
'polymorphic_identity': 'employee',
'polymorphic_on': type
}
And removed the role_id column definition from all other employee models.
db.create_all()
petrolium_engineer = Role(name='Petrolium Engineer')
geotech_engineer = Role(name='Geotech Engineer')
analyst_petrolium = Role(name='Analyst of Petrolium')
db.session.add(petrolium_engineer)
db.session.add(geotech_engineer)
db.session.add(analyst_petrolium)
db.session.add(
Intern(given_name='Joe', surname='Blogs', term_ends=datetime.now())
)
db.session.add(
Engineer(given_name='Mark', surname='Fume', role=petrolium_engineer)
)
db.session.add(
Engineer(given_name='Steve', surname='Rocks', role=geotech_engineer)
)
db.session.add(
Analyst(given_name='Cindy', surname='Booker', role=analyst_petrolium)
)
db.session.commit()
petrolium_roles = db.session.query(EmployeeBase).join(Role).\
filter(Role.name.contains('Petrolium')).all()
for emp in petrolium_roles:
print(f'{emp.given_name} {emp.surname} is {emp.role.name}')
# Mark Fume is Petrolium Engineer
# Cindy Booker is Analyst of Petrolium

SQLAlchemy: Trouble querying in manyto many relationship

New to SQLalchemy, an sql queries in general but hopefully this will be clear to someone :) In a Flask application, I have two models, User and classes, in a many to many relationship
Here is the models.py
user_to_classes = db.Table('user_to_classes', Base.metadata,
db.Column('class_id', db.Integer, db.ForeignKey('classes.id')),
db.Column('user_id', db.Integer, db.ForeignKey('users.id'))
)
class Classes(db.Model):
__tablename__= 'classes'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
date = db.Column(db.DateTime)
participants = db.relationship('User', secondary=user_to_classes, backref = db.backref('classes',lazy='dynamic'))
classtype_id = db.Column(db.Integer, db.ForeignKey('classtype.id'))
status = db.Column(db.Integer) #1 = open, 0 = closed
class User(UserMixin,db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
email = db.Column(db.String(64),unique=True,index=True)
firstname = db.Column(db.String(64))
lastname = db.Column(db.String(64))
fullname = db.Column(db.String(64), index=True)
telephone = db.Column(db.String(64))
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
password_hash = db.Column(db.String(128))
member_since = db.Column(db.DateTime(), default=datetime.utcnow)
last_seen = db.Column(db.DateTime(), default=datetime.utcnow)
notes = db.Column(db.Text())
punchcard_passes = db.Column(db.Integer)
I am trying to know how many classes attended a user. I have no problem querying how many users participated in a class, but not the reverse as there is no value to query in the user model. Is it even possible? Not being fluent in SQL queries, I am not sure what to search for either on google. All the exemples I have seen do a one way query, and never the other way.
thanks!
How about len(user.classes) ? Doesn't it work ?
In addition: don't name a class in plural, since an object of it represents only one class.