Query difference between db.session.query and Model.query in flask-SQLAlchemy - flask

The database is near 5 millions rows. I declare a model like below:
class Amodel(db.Model):
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.String)
money = db.Column(db.String)
I made a index of money column and it doesn't affect result.
Way 1 - session.query:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app)
s1 = db.session.query(Amodel).filter(Amodel.money=='1000').all()
Way 2 - Model.query:
s2 = Amodel.query.filter(Amodel.money=='1000').all()
Time consumption
test1:
s1:0.06102442741394043
s2:0.6709990501403809
test2:
s1:0.0010263919830322266
s2:0.6235842704772949
test3:
s1:0.0029985904693603516
s2:0.5942485332489014
They got the same result but time consumption is so different. I usually use way2 for query because I think it's more readble. Could someone explain what's happen inside and how to optimize?

Related

Displaying / Testing outputs are correct. Sanity Check

I am still learning Django and slowly improving but I have a few questions, I have my whole model below:
from django.db import models
from datetime import datetime, timedelta
# Create your models here.
year_choice = [
('year1','1-Year'),
('year3','3-Year')
]
weeksinyear = 52
hours = 6.5
current_year = datetime.year
class AdminData(models.Model):
year1 = models.IntegerField()
year3 = models.IntegerField()
#property
def day_rate_year1(self):
return self.year1 / weeksinyear / hours
class Price(models.Model):
name = models.CharField(max_length=100)
contract = models.CharField(max_length=5, choices=year_choice)
start_date = models.DateField(default=datetime.now)
end_date = models.DateField(default=datetime(2021,3,31))
def __str__(self):
return self.name
The main concern for me at the moment is trying to understand if my function def day_rate_year1(self): is working correctly, if someone could point me in the right direction to understand either how I display this as a string value in a template or test in the shell to see if the value pulls through as the values for year1 and year3 can change based on user input.
I am trying to work out the day rate so I can then use the start and end dates and work out the number of days between the two to calculate a price which is then displayed to the user which can again change depending on the number of days and the contract type which is a 3 year option or 1 year option.
Let me know if you need the views or templates as well.
Thanks for the help!
if someone could point me in the right direction to understand either how I display this as a string value in a template or test in the shell to see if the value pulls through as the values for year1 and year3 can change based on user input
if you launch a shell session as below, you should see the output of your property.
python manage.py shell
Expected output:
>>> from app_name.models import AdminData
>>> test = AdminData.objects.create(year1=2010, year3=2016)
>>> print(test.day_rate_year1)
5.946745562
>>>

count the number of rows with a condition with SQLAlchemy

I have a sqlite table like this. Which has 3 columns. I want to
count the number of rows where user_id = 1
is this possible with SQLAlchemy?
class UserImage(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
photo = db.Column(db.String(250))
This will return all the rows. How to modify this to get my expected result.
rows = db.session.query(func.count(UserImage.user_id)).scalar()
Thank you so much in advance.
You can do it like this:
UserImage.query.filter(UserImage.user_id == 1).count()
or
db.session.query(UserImage).filter(UserImage.user_id == 1).count()
For SQLAlchemy 2.x style, you can get the count number like this:
from sqlalchemy import select, func
db.session.execute(
select(func.count()).
select_from(User)
).one()
# or
db.session.execute(select(func.count(User.id))).scalars().one()
Now with the condition:
db.session.execute(
select(func.count()).
select_from(UserImage).
filter(UserImage.user_id == 1)
).scalars().one()
# or
db.session.execute(
select(func.count(UserImage.id)).
filter(UserImage.user_id == 1)
).scalars().one()
P.S. If you are using pure SQLAlchemy instead of Flask-SQlAlchemy, just replace db.session with session.

save() on a model instance not update the changed field on related model side

My models:
class InventoryItem(models.Model):
quantity = models.IntegerField()
...
class Requisition(models.Model):
from_inventoryitem = models.ForeignKey(InventoryItem)
quantity = models.IntegerField()
...
Assuming that requisition has an OneToMany relation with inventory_item below, and the initial value of inventory_item.quantity is 0, When I excute:
>>> requisition = Requisition.objects.get(id=1)
>>> requisition.from_inventoryitem.quantity = 500
>>> requisition.save()
>>> requisition.from_inventoryitem.quantity
500
>>> inventory_item.quantity
0
>>> requisition.from_inventoryitem == inventory_item
True
The inventory_item.quantity in database and InventoryItem side is still 0.
How can I update that change to the database?
This is by design. You have to save each instance seperately. requisition.from_inventoryitem actually queries the InventoryItem instance from the database, just to set the quantity.
requisition = Requisition.objects.get(id=1)
requisition.from_inventoryitem.quantity = 500 # this generates another query here!
requisition.from_inventoryitem.save()
or even better, with a single query, single save
inv_item = InventoryItem.objects.get(requisition_set__id=1)
inv_item.quantity = 500
inv_item.save()
best way. single database call:
InventoryItem.objects.filter(requisition_set__id=1).update(quantity=500)

complex aggregate in Django

I have the following models:
class Item(models.model):
price = models.floatField()
...
and :
class purchase(models.model):
user = models.ForeignKey(User)
item = models.ForeignKey(Item)
date = models.DateField()
I would like to make a query which will provide top 10 users who purchased the most in the last week .I would also like to have the Sum of the purchased they completed in that week.
So the output I would like to show on my page is similar to this :
Top buyers of the week :
1. User_1 | 150 Points
2. User_2 | 130 Points
...
10. User_10 | 10 Points
Is it possible to use annotate for that ? or it should be break ot several queries ?
Well let's give it a go (still needs to be tested for optimized SQL):
from datetime import timedelta
from django.db.models import Sum
from django.contrib.auth.models import User
from django.utils import timezone
some_day_last_week = timezone.now().date() - timedelta(days=7)
rows = User.objects.filter(purchases__date__gte=some_day_last_week)\
.annotate(item_sum=Sum('purchases__item__price'))\
.order_by('item_sum')[:10]
print [(u.username, u.item_sum) for u in rows]

Custom model method in Django

I'm developing a web app in Django that manages chores on a reoccurring weekly basis. These are the models I've come up with so far. Chores need to be able to be assigned multiple weekdays and times. So the chore of laundry could be Sunday # 8:00 am and Wednesday # 5:30 pm. I first want to confirm the models below are the best way to represent this. Secondly, I'm a little confused about model relationships and custom model methods. Since these chores are on a reoccurring basis, I need to be able to check if there has been a CompletedEvent in this week. Since this is row level functionality, that would be a model method correct? Based on the models below, how would I check for this? It has me scratching my head.
models.py:
from django.db import models
from datetime import date
class ChoreManager(models.Manager):
def by_day(self, day_name):
return self.filter(scheduledday__day_name = day_name)
def today(self):
todays_day_name = date.today().strftime('%A')
return self.filter(scheduledday__day_name = todays_day_name)
class Chore(models.Model):
objects = ChoreManager()
name = models.CharField(max_length=50)
notes = models.TextField()
class Meta:
ordering = ['scheduledday__time']
class ScheduledDay(models.Model):
day_name = models.CharField(max_length=8)
time = models.TimeField()
chore = models.ForeignKey('Chore')
class CompletedEvent(models.Model):
date_completed = DateTimeField(auto_now_add=True)
chore = models.ForeignKey('Chore')
Then all you need to do is:
monday_of_week = some_date - datetime.timedetla(days=some_date.weekday())
end_of_week = date + datetime.timedelta(days=7)
chore = Chore.objects.get(name='The chore your looking for')
ScheduledDay.objects.filter(completed_date__gte=monday_of_week,
completed_date__lt=end_of_week,
chore=chore)
A neater (and faster) option is to use Bitmasks!
Think of the days of the week you want a chore to be repeated on as a binary number—a bit for each day. For example, if you wanted a chore repeated every Tuesday, Friday and Sunday then you would get the binary number 1010010 (or 82 in decimal):
S S F T W T M
1 0 1 0 0 1 0 = 1010010
Days are reversed for sake of illustration
And to check if a chore should be done today, simply get the number of that day and do an &:
from datetime import datetime as dt
if dt.today().weekday() & 0b1010100:
print("Do chores!")
Models
Your models.py would look a bit like this:
from django.contrib.auth.models import User
from django.db import models
from django.utils.functional import cached_property
class Chore(models.Model):
name = models.CharField(max_length=128)
notes = models.TextField()
class ChoreUser(models.Model):
chore_detail = models.ForeignKey('ChoreDetail')
user = models.ForeignKey('ChoreDetail')
completed_time = models.DateTimeField(null=True, blank=True)
class ChoreDetail(models.Model):
chore = models.ForeignKey('Chore')
chore_users = models.ManyToManyField('User', through=ChoreUser)
time = models.DateTimeField()
date_begin = models.DateField()
date_end = models.DateField()
schedule = models.IntegerField(help_text="Bitmask of Weekdays")
#cached_property
def happens_today(self):
return bool(dt.today().weekday() & self.weekly_schedule)
This schema has a M2M relationship between a User and a Chore's Schedule. So you can extend your idea, like record the duration of the chore (if you want to), or even have many users participating in the same chore.
And to answer your question, if you'd like to get the list of completed events this week, you could could put this in a Model Manager for ChoreUser:
from datetime import datetime as dt, timedelta
week_start = dt.today() - timedelta(days=dt.weekday())
week_end = week_start + timedelta(days=6)
chore_users = ChoreUser.objects.filter(completed_time__range=(week_start, week_end))
Now you have all the information you need in a single DB call:
user = chore_users[0].user
time = chore_users[0].chore_detail.time
name = chore_users[0].chore_detail.chore.name
happens_today = chore_users[0].chore_detail.happens_today
You could also get all the completed chores for a user easily:
some_user.choreuser_set.filter(completed_time__range=(week_start, week_end))