Sum multiple annotates in django orm - mysql

Here's my model:
class Product:
name = models.CharField(max_length=80)
price = models.FloatField()
category = models.CharField(max_length=40)
class ProductInventory:
inventory = models.IntegerField()
product = models.OneToOneField(Product, on_delete=models.RESTRICT)
and here's raw sql of what I want to achieve but how do I write this in django ORM?
SELECT product.category, SUM(price) * SUM(product_inventory.inventory)
FROM product LEFT JOIN product_inventory
ON product.id=product_inventory.product_id group by product.category;
Thank you.

You can try by using Sum in annotate and make a dummy field of name sum_product and pass it in values like this. Hope this will work for you.
from django.db.models import Sum
Product.objects.annotate(sum_product=Sum('price') * Sum('productinventory__inventory')).values('category', 'sum_product')

Related

joinedload and load_only but with filtering

I have two models with a simple FK relationship, Stock and Restriction (Restriction.stock_id FK to Stock).
class Restriction(Model):
__tablename__ = "restrictions"
id = db.Column(db.Integer, primary_key=True)
stock_id = FK("stocks.id", nullable=True)
name = db.Column(db.String(50), nullable=False)
class Stock(Model):
__tablename__ = "stocks"
id = db.Column(db.Integer, primary_key=True)
ticker = db.Column(db.String(50), nullable=False, index=True)
I would like to retrieve Restriction object and related Stock but only Stock's ticker (there are other fields omitted here). I can simply do this with:
from sqlalchemy.orm import *
my_query = Restriction.query.options(
joinedload(Restriction.stock).load_only(Stock.ticker)
)
r = my_query.first()
I get all columns for Restriction and only ticker for Stocks with above. I can see this in the SQL query run and also I can access r.stock.ticker and see no new queries run as it is loaded eagerly.
The problem is I cannot filter on stocks now, SQLAlchemy adds another FROM clause if I do my_query.filter(Stock.ticker == 'GME'). This means there is a cross product and it is not what I want.
On the other hand, I cannot load selected columns from relationship using join. ie.
Restriction.query.join(Restriction.stock).options(load_only(Restriction.stock))
does not seem to work with relationship property. How can I have a single filter and have it load selected columns from relationship table?
SQL I want run is:
SELECT restrictions.*, stocks.ticker
FROM restrictions LEFT OUTER JOIN stocks ON stocks.id = restrictions.stock_id
WHERE stocks.ticker = 'GME'
And I'd like to get a Restriction object back with its stock and stock's ticker. Is this possible?
joinedload basically should not be used with filter. You probably need to take contains_eager option.
from sqlalchemy.orm import *
my_query = Restriction.query.join(Restriction.stock).options(
contains_eager(Restriction.stock).load_only(Stock.ticker)
).filter(Stock.ticker == 'GME')
r = my_query.first()
Because you are joining using stock_id it will also be in the results as Stock.id beside Stock.ticker. But other fields would be omitted as you wish.
I have written short post about it recently if you are interested: https://jorzel.hashnode.dev/an-orm-can-bite-you

SQLAlchemy : how to apply distinct before a filter in a join query

Is there a way to apply distinct before filtering in a join query ?
I have 2 tables :
class User(db.Model):
id = db.Column(db.Integer(), primary_key=True)
class Event(db.Model):
id = db.Column(db.Integer(), primary_key=True)
date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
description = db.Column(db.String(80))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
user = db.relationship('User', backref=db.backref('events', lazy=True))
I want to select all users where the most recent event description is empty.
I thought this would work:
User.query.join('events').order_by(Event.date).distinct(User.id).filter(Event.description==None)
However, it seems that distinct is called after filter. Is there a way to force to apply distinct before filtering ?
DISTINCT is used to return different values.
In this case you need a subquery to create a table using GROUP BY where you group Event by user_id and calculating the most recent event :
last_events = db.session.query(
Event.user_id,
db.func.max(Event.date).label('last_event_date'),
Event.description)\
.group_by(Event.user_id)
.subquery()
Then you can join this table to User and filter on the last_event_description :
User.query.join(last_events,User.id==last_events.c.bac_id)
.filter(last_events.c.description==None)
Et voilà !
The .c comes from the way you access the columns of the subquery in SQLAlchemy.

Django ORM: Tried to do inner join with foreign key but causes FieldError

I am new to django orm.
I've tables look like this.
class Product(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=60)
class ProductOption(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
product_id = models.ForeignKey(Product, on_delete=models.CASCADE, null=True, blank=True)
I would like to query productoption id that related to product. I made query like this to do inner join.
Query = Product.select_related(‘product_id’).filter(name='a')
And it gaves me error message saying
django.core.exceptions.FieldError: Invalid field name(s) given in select_related: 'product_id'. Choices are: (none)
I want to know if there is something wrong in models or query.
Use prefetch_related
Product.objects.filter(name='a').prefetch_related('productoption_set')
This is not how you query a related object. Since you used a foreign key and if I understand correctly, you probably want to use something like this:
Product.objects.filter(name='a').productoption_set.all()

How can I make my query set order by descending in django based on auto increment id field

Django Model
class logdetail(models.Model):
scantype = models.CharField(max_length=100)
scanrange = models.CharField(max_length=100)
scandate = models.DateTimeField(max_length=100)
status = models.CharField(max_length=100)
Views.py
logDBObj = logdetail.objects.all().filter(id=logdetail_id).order_by('-logdetail_id')
I have to collect all the records in logdetail table by descending order of autoincrement id.
I could not find where I am missing. Any help would be appreciated.
You haven't shown your model but by the looks of it you just need to remove the logdetail in your order_by:
logdetail.objects.all().order_by('-id')

Convert SQL query to SQLAlchemy query

I have two SQLALchemy models with a many-to-many relationship between them:
class Contact(db.Model):
id = db.Column(db.Integer, primary_key=True)
customer_id = db.Column(db.Integer)
users = db.relationship(ContactUser, cascade='all, delete-orphan')
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
class ContactUser(db.Model):
contact_id = db.Column(db.Integer, db.ForeignKey('contact.id'),
primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'),
primary_key=True)
user = db.relationship(User)
I want to convert this SQL:
SELECT
c.*
FROM
contact c LEFT JOIN
user_contact uc ON c.id = uc.contact_id AND uc.user_id='456'
WHERE
c.customer_id = '123' AND
uc.contact_id IS NULL
Into an SQLAlchemy query, where I can specify customer_id and user_id.
I can't figure out how to tell SQLAlchemy to add the AND uc.user_id='456' to the ON clause.
Current query:
contacts = (Contact.query.join(ContactUser)
.filter(Contact.customer_id == customer.id)
.filter(ContactUser.contact_id == None)
The docs mention something about being able to use a "two argument calling of join", but it seems that only allows me to specify one thing, and I need two in my ON clause.
Also, I think I need to use outerjoin instead of join?
With the help of the guys in #sqlalchemy, we came up with:
from sqlalchemy import and_
...
Contact.query.filter(
Contact.customer_id==customer_id)
.outerjoin(ContactUser, and_(Contact.id==ContactUser.contact_id,
ContactUser.user_id!=user_id))
.filter(ContactUser.contact_id == None))