Count related and filter in SQLAlchemy - flask

So I have an object Services, object Services has a foreign key to object Waiters. Object waiter has also got a time field "exit" which must be smaller than the current time. The idea is to return all objects Waiter with a count for how many objects service they are related to that have a field exit larger than the current time. Then I have to do the same with Tables.
Finally I need to filter out the Waiters which currently hold four or more current services, and I have to filter out the tables with one or more.
Here are my models:
class Waiter(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))
service = db.relationship('Service', backref='waiter', lazy=True)
class Table(db.Model):
id = db.Column(db.Integer, primary_key=True)
table_no = db.Column(db.String(128))
service = db.relationship('Service', backref='table', lazy=True)
class Service(db.Model):
id = db.Column(db.Integer, primary_key=True)
table = db.Column(db.Integer, db.ForeignKey('table.id'))
waiter = db.Column(db.Integer, db.ForeignKey('waiter.id'))
arrival = db.Column(db.DateTime, default=datetime.datetime.now(mx))
exit = db.Column(db.DateTime)
tip = db.Column(db.Numeric(10,2))
So far I've got something like:
waiters = Waiter.query(Waiter.id, \
func.count(*"I don't know what to put here"*).\
label("service_count")).groupby(Waiter.id).subquery()
EDIT:
After further research I have come to the following query. I still don't know if it's right:
waiters = Waiter.query(Waiter.id, \
func.count("*").filter(Service.exit>=datetime.datetime.now()) .\
label("service_count")).groupby(Waiter.id).subquery()
Any help is much appreciated. Thank you very much.

I got an answer that does what I want but instead of of an annotation I get a tupple with the values. It's this:
waiters = db.session.query(Waiter, func.count(Service.id).\
filter(Service.exit>time)).\
outerjoin(Service).group_by(Waiter).all()
It returns something like this:
[(<Waiter 1>, 0)]

Related

Flask how to query with a filter that checks whether a value is present in a list?

I'm making a bug tracker structured such that each user can have multiple projects, and each project can record multiple bugs. I'd like users to be able to see how many new bugs were reported across all their projects since their last login.
I structured it such that there's a Users model, a Projects model, and Bugs model. I've included the three models with their relevant columns below:
class Users(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key = True, nullable = False)
# ...
last_login = db.Column(db.DateTime, nullable = False)
# AS PARENT
owned_projects = db.relationship('Projects', backref="project_owner")
class Projects(db.Model):
id = db.Column(db.Integer, primary_key = True, nullable = False)
# ...
# AS CHILD
owner = db.Column(db.Integer, db.ForeignKey('users.id'))
# AS PARENT
bugs = db.relationship('Bugs', backref = 'containing_project')
class Bugs(db.Model):
id = db.Column(db.Integer, primary_key = True)
# ...
status = db.Column(db.String(20))
report_date = db.Column(db.DateTime, index = True, default = dt.datetime.now())
# AS CHILD
project = db.Column(db.Integer, db.ForeignKey('projects.id'))
To filter a Bugs query for bugs reported since the user's last login, I can do a filter like Bugs.query.filter(Bugs.report_date > current_user.last_login). This shows ALL bugs from ALL users, but I'm having trouble constructing a query that filters it down to only bugs in projects owned by the user.
.filter(Bugs.containing_project in current_user.owned_projects) returns "<flask_sqlalchemy.BaseQuery object at 0x04E06988>", but I have no idea how to work with that. I read about .contains but that goes in the wrong direction, current_user.owned_projects.contains(Bugs.project) does not work.
I also tried .filter(Bugs.containing_project.owner == current_user.id), but got an error "AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with Bugs.containing_project has an attribute 'owner'".
The only option I can think of now is to iterate through every single bug to find the ones that belong to a project owned by the user, but that would be nightmarish in terms of performance. Surely there's a .in method or something similar?
Please advise on how I can achieve this, thanks in advance!
This code doesn't work?
.filter(Bugs.containing_project.any(Projects.id.in_([ proj['id'] for proj in current_user.owned_projects])
I refered this site.
SQLAlchemy: filter by membership in at least one many-to-many related table
And, if my answer doesn't work well, this site may help you.
SQLAlchemy how to filter by children in many to many

flask sqlalchemy one-to-many filter, count & grouping

I am currently trying to build a query which
give me for a one-to-many sqlalchemy query in flask both my result filters grouped and then says how many individual entries there are for it
Following is my database model to illustrate the question:
class cellphone(db.Model):
__tablename__ = 'cellphone'
id = db.Column(db.Integer, primary_key = True)
number = db.Column(db.String(30), unique=True)
sms = db.relationship('sms_accounting', backref="cellphone", lazy='dynamic')
class sms_accounting(db.Model):
__tablename__ = 'sms_accounting'
id = db.Column(db.Integer, primary_key = True)
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
cellphone_id = db.Column(db.Integer, db.ForeignKey('cellphone.id'))
What I want to do now is find out how many SMS were sent within X days per number.
Filtering and grouping I managed to do, but to calculate the sum per device correctly is not possible.
def sms_count():
search_peroid='90' #time to fetch events in days
period_start = datetime.utcnow() - timedelta(hours=int(search_peroid))
phone_count = sms_accounting.query.filter(sms_accounting.timestamp.between(period_start, \
datetime.utcnow() )).group_by(sms_accounting.cellphone_id).all()
I found some examples for func.count, but unfortunately none of them works. This already starts with the usage,
AttributeError: BaseQuery object has no attribute 'func'
even though it was imported especially.
from sqlalchemy.sql.functions import func
Forgive me if I am wrong.
As an option, you could try executing an SQL Query through Flask.
db.session.execute('select number, count(sms_accounting.id) from cellphone join sms_accounting on sms_accounting.cellphone_id = cellphone.id');
You can easily add the time based filter using where.
Regarding the AttributeError, are you sure you are using the 'func' method properly? The correct usage can be found on this unrelated answer at https://stackoverflow.com/a/4086229/4854064. It might be that you accidentally called func as a method of the query object.

flask - sqlalchemy - self-referential query

I try to do query in sqlalchemy to get with self-referential relationship which is filtered on parent and also child level.
category_country = Table('category_country', Base.metadata,
Column('category_id', Integer, ForeignKey('category.id'), primary_key=True),
Column('country_id', Integer, ForeignKey('country.id'), primary_key=True)
)
class Category(Base):
__tablename__ = "category"
id = Column(Integer, primary_key=True, autoincrement=True)
parent_id = Column(Integer, ForeignKey('category.id'))
subcategories = relationship("Category", backref=backref('parent', remote_side=id))
countries = relationship(Country, secondary = category_country, backref='categories')
class Country(Base):
__tablename__ = "country"
id = Column(Integer, primary_key=True)
query
category = s.query(Category).join(Category.countries).options(contains_eager(Category.countries)).filter(Country.id == 1).filter(Category.id == category_id).join(Category.countries, aliased=True).join(Category.subcategories, aliased=True).options(contains_eager(Category.countries)).filter(Country.id == 1).first()
but it doesn't work. I need to find children which are from country 1 and its parent is category_id and country is also 1
I don't completely get your model/code on my first read, but the way I would tackle this is by splitting the self-referential join into a subquery() statement like this:
filter_by_country = (db.session.query(...)
.filter(...)
.subquery())
final_results = (db.session.query(...)
.join(filter_by_country,
db.and_(Category.id == filter_by_country.c.id, ..., ...))
.options(...)
.filter(...)
.etc(...).first())
I've found this sort of a pattern can help simplify these type of queries. Hope this helps.

Sqlalchemy many to many relationship

I have two tables in many to many relationship:
class Association(db.Model):
__tablename__ = 'association'
club_id = db.Column(db.Integer, db.ForeignKey('clubs.id'), primary_key=True)
student_id = db.Column(db.Integer, db.ForeignKey('students.id'), primary_key=True)
joined_date = db.Column(db.String)
assoc_student = db.relationship("Student")
class Club(db.Model):
__tablename__ = 'clubs'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
location = db.Column(db.String)
club_assoc = db.relationship("Association")
class Student(db.Model):
__tablename__ = 'students'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
age = db.Column(db.String)
gender = db.Column(db.String)
Questions:
1) What is the difference between these two queries?
students = db.session.query(Association).filter_by(club_id='1')
students = Association.query.filter_by(club_id='1')
They seem to give the same result!
2) I'm trying to get a list of students with certain age but this following query doesn't work:
db.session.query(Association).filter_by(Association.club_id=='1', Association.assoc_student.age=='15')
But I get this error:
AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with Association.assoc_student has an attribute 'age'
That's why I'm using this one:
db.session.query(Student).join(Association).filter(Association.club_id=='1', Student.age=='15')
Is there a better way to do this without "join"? Maybe with using "backref"!?
1) What is the difference between these two queries?
They do almost the same thing. Former is the way to query objects provided with SQLAlchemy (library Flask uses to access database).
Latter is the convenient way to query models added by Flask-SQLAlchemy library. It makes your queries more readable + extends query with few useful methods. Take a look at source of the flask_sqlalchemy.BaseQuery class to see them: get_or_404(), first_or_404() and paginate().
Usually you want to use latter method to query objects.
2) I'm trying to get a list of students with certain age but this following query doesn't work.
There are two things here:
Be aware about the difference between filter() and filter_by() methods. In your example you try to use filter_by() with SQL expressions instead of kwargs, which is incorrect.
When you're using filter() you can't specify columns over a relationships (like Association.assoc_student.age). The only allowed format is ModelName.column_name. That's why it fails.
Is there a better way?
Your second approach is absolutely correct and fine to use. I don't think there is a better way to do it. Alternatively you can use code below to avoid importing db (if you define query in another file):
Student.query.join(Association).filter(Association.club_id == '1', Student.age == '15')

set the insert order of a many to many sqlalchemy flask app sqlite db

My Goal
I want to record the order of manys upon insert of data to my table (e.g. Clump-see tables below). The orderinglist module is really great, but how do i apply it to the intermediary table (named clump_syntaxs) between my many-to-many? anyone done this before and have a good example?
problem re-stated
How do i apply ordering upon insert to my many to many. Everything I try using the intermediary table-clump_syntaxs table crashes (sorry for the weird names!).
The following code (reduced for brevity) works! except that it only allows for a syntax to have a unique position (instead of a position for every Clump instance), and I am guessing I need the position variable to be on the clump_syntaxs table.all tables are sqlite
my intermediary table
from sqlalchemy.ext.orderinglist import ordering_list
clump_syntaxs = db.Table('clump_syntaxs',
db.Column('syntax_id', db.Integer, db.ForeignKey('syntax.id')),
db.Column('clump_id', db.Integer, db.ForeignKey('clump.id')),
)
add a clump and order syntax tables
class Clump(db.Model):
id = db.Column(db.Integer, primary_key=True)
syntaxs = db.relationship('Syntax', secondary=clump_syntaxs,
backref=db.backref('clumps', lazy='dynamic'),order_by="Syntax.position",
collection_class=ordering_list('position'))
class Syntax(db.Model):
id = db.Column(db.Integer, primary_key=True)
jobs = db.relationship('Jobs',lazy='dynamic', backref='jobhistory')
position = db.Column(db.Integer)
#Jobs table not included
Yes, you should move position field to the intermediary table ClumpSyntax, and take advantage of association_proxy() in Clump table.
import sqlalchemy.ext.associationproxy import association_proxy
class Syntax(db.Model):
id = db.Column(db.Integer, primary_key=True)
jobs = db.relationship('Jobs',lazy='dynamic', backref='jobhistory')
#position = db.Column(db.Integer) # moved to ClumpSyntax
#Jobs table not included
class ClumpSyntax(db.Model):
syntax_id = db.Column('syntax_id', db.Integer, db.ForeignKey('syntax.id'))
syntax = relationship(Syntax)
clump_id = db.Column('clump_id', db.Integer, db.ForeignKey('clump.id'))
position = db.Column(db.Integer)
# this constructor is very necessary !
def __init__(self, syntax =None):
self.syntax = syntax
class Clump(db.Model):
id = db.Column(db.Integer, primary_key=True)
_syntaxs = db.relationship('ClumpSyntax',
order_by=[ClumpSyntax.position],
collection_class=ordering_list('position'))
syntaxs = association_proxy('_syntaxs','syntax')
My similar request was satisfied quite well by this way, based on the article this and this. You can test it by code like below:
session= some_code_to_get_db_session()
syn1= Syntax()
syn2= Syntax()
syn3= Syntax()
session.add(syn1)
session.add(syn2)
session.add(syn3)
clump= Clump()
session.add(clump)
clump.syntaxs.append(syn1)
clump.syntaxs.append(syn2)
clump.syntaxs.append(syn3)
session.commit()
session.query(ClumpSyntax).count() # print out 3
session.query(Syntax).count() # print out 3