Count related items in a sqlalchemy model - sqlalchemy

I'm trying to count the number of items in their respective categories and end up with a collection that I can iterate through in a jinja template. My final output is something like:
category1, 5
category2, 10
category3, 0
The zero items case is important.
My model is:
class Category(Base):
__tablename__ = 'category'
id = Column(Integer, primary_key=True)
name = Column(String(80), unique=True)
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship(User)
class Item(Base):
__tablename__ = 'item'
id = Column(Integer, primary_key=True)
name = Column(String(80))
description = Column(String(500))
category_id = Column(Integer, ForeignKey('category.id'))
category = relationship(Category)
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship(User)
date_added = Column(DateTime, default=datetime.datetime.now)
I have been kindly pointed in the direction of Stackoverflow: Counting relationships in SQLAlchemy, which led me to the query
count_categories = db_session.query(Category.name, func.count(Item.id)).join(Item.category).group_by(Category.id).all()
Which is almost correct, but it does not handle the zero case. When a category has zero items, I still need the category returned by the query.
Any help, much appreciated.

Actually, I've figured it out:
count_categories = db_session.query(
Category.name, func.count(Item.id)).outerjoin(
Item).group_by(Category.id).all()
See SQLAlchemy documentation on Joins

Related

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 make this query in sqlalchemy?

SELECT
maintener.*,
(SELECT COUNT(*)
FROM device d
WHERE d.in_stock_maintener_id = maintener.id) AS in_stock_devices
FROM maintener;
I'm creating a report that show all mainteners but i need to show the number of devices that each one of that mainteners has by looking at the devices model reference in_stock_maintener_id;
I have this models in my persist sqlalchemy.
class Maintener(persist.Base):
__tablename__ = 'maintener'
id = Column(Integer, primary_key=True)
name = Column(String(255))
document_number = Column(String(30))
phone_1 = Column(String(12))
phone_2 = Column(String(12))
email = Column(String(255))
class Device(persist.Base):
__tablename__ = 'device'
id = Column(Integer, primary_key=True)
serial = Column(String(45))
in_stock = Column(SmallInteger)
in_stock_maintener_id = Column(ForeignKey(u'maintener.id'), nullable=True, index=True)
in_stock_maintener = relationship(u'Maintener', lazy='noload', \
primaryjoin='Device.in_stock_maintener_id == Maintener.id')
If anyone could help me, i'll be grateful =)
sq = (
session
.query(func.count())
.select_from(Device)
.filter(Device.in_stock_maintener_id == Maintener.id)
).as_scalar()
q = session.query(Maintener, sq.label('in_stock_devices'))
Query above will return an enumerable of tuple(Maintener, Integer).
If you would like to have columns instead (as per your comment), then you can either specify the columns you want in the query implicitly:
q = session.query(Maintener.id, Maintener.name, sq.label('in_stock_devices'))
or if you would like all columns (as in SELECT *), then you could query the Table instead of the mapped entity:
q = session.query(Maintener.__table__, sq.label('in_stock_devices'))
Above I assumed that you use declarative extension.

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.

How to create a parent-child-subchild information in flask-sqlalchemy

I'm using Flask-SQLAlchemy and flask on server side.
I want to create a relationship between User(Parent), Category(Child) and item(Subchild).
I don't know if a parent-child-subchild is right way to approach this but my end goal is to easily fetch all the items rated by user in each category.
Following is one of the cases i tried but it doesn't work, probably because i'm doing something wrong.
I have an item table with item category(such as fiction, non-fiction, crime book). each category have 5 items(books). I want to let user rate five items from each category. So, i have following table.
class User(db.Model, UserMixin):
__tablename__ = "users"
id = db.Column('user_id',db.Integer, primary_key=True)
active = db.Column(db.Boolean(), nullable=False, default=False)
username = db.Column(db.String(50), nullable=False, unique=True)
password = db.Column(db.String(255), nullable=False, default='')
# do we need unique=True in any column
#let's create class for item(child) and category(parent)
class Rated_item(db.Model):
__tablename__ = 'item'
id = db.Column(db.Integer, primary_key=True)
item_id = db.Column(db.Integer)
category_id = db.Column(db.Integer)
quality = db.Column(db.String(50))
taste = db.Column(db.String(50))
user_id = db.Column(db.Integer, db.ForeignKey('users.user_id'))
# evaluation_status is set to True, once a user finishes an item.
evaluation_status = db.Column(db.Boolean, unique=False, default=False)
Since i'm working with flask-jinja based webpage, every-time a user logs in, i need to send status of user i.e how many categories have been rated by user and how many items have been rated in each category.
I tried following but i think i'm doing something conceptually wrong.
status=Rated_item.query.filter_by(user_id = current_user.id).order_by(Rated_item.pub_date.desc()).all()
Any suggestion how should i improve my table or query to get all categories (and all items in each category) rated by user.
''' items rated by user '''
status1 = Rated_item.query.func.count(distinct(item_id)).filter_by(user_id == current_user.id)
'''categories rated by user'''
status2 = Rated_item.query.func.count(distinct(category_id)).filter_by(user_id == current_user.id)
I guess this query would help you.