I have two tables.
Products [id, name, category]
Sales [product, date, price]
I want to get every Product with the latest row from Sales. If there is no row in sales, I still want to get that product row. The rows are foreign key related.
What is the Django way of doing such a left join?
Probably you can use Subquery:
from django.db.models import Subquery, OuterRef
sales = Sales.objects.filter(product=OuterRef('pk')).order_by('-date')
products = Product.objects.annotate(latest_sale = Subquery(sales.values('price')[:1]))
you can get the Sales object in view and render the object in template. for example you can do this.
models.py
class Course(models.Model):
name = models.CharField(max_length=100)
category = models.CharField(max_length=100)
def __str__(self):
return self.title
class Sales(models.Model):
product = models.ForeignKey(Course, on_delete=models.CASCADE, null=True)
price = models.IntegerField()
date = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
return self.product.title
views.py
order_by(-date) gets you the latest item from the database
def sales_view(request):
sales = Sales.objects.all().order_by(-date)
return render(request, 'template.html', {'sales':sales})
Now, in your template you can load the template.
{% for sale in sales %}
{{sale.product.name}}
{{sale.price}}
{{sale.date}}
{% endfor %}
This way you get the product object even if there are no sales object. Hope you get some idea from it.
Related
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
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')
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')
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))
I have two models, a Project and an Action:
class Project(models.Model):
name = models.CharField("Project Name", max_length=200, unique = True)
class Action(models.Model):
name = models.CharField("Action Name", max_length=200)
project = models.ForeignKey(Project, blank=True, null=True, verbose_name="Project")
notes = models.TextField("Notes", blank=True)
complete = models.BooleanField(default=False, verbose_name="Complete?")
status = models.IntegerField("Action Status", choices = STATUS, default=0)
I need a query that returns all the Projects for which there are no actions with status < 2.
I tried:
Project.objects.filter(action__status__gt = 1)
But this returns all the Projects because in each Project, there are some actions with status 2 and some actions with status less than 2. Also, it repeated Projects in the resulting query. My current solution is below:
Project.objects.filter(action__status__gt =1).exclude(action__status__lt =2).annotate()
This collapses the repeating results and shows only actions with action statuses greater than 1. But is this the correct way to construct such a query? What if I wanted to return Projects with actions statuses greater than 1 OR Projects with no actions?
I might have misunderstood your requirement, but I think you can do that using annotations.
Project.objects.annotate(m = Min('action__status')).filter(Q(m = None) | Q(m__gt = 1))
The SQL generated is:
SELECT
"testapp_project"."id", "testapp_project"."name",
MIN("testapp_action"."status") AS "m"
FROM "testapp_project"
LEFT OUTER JOIN "testapp_action" ON ("testapp_project"."id" = "testapp_action"."project_id")
GROUP BY "testapp_project"."id", "testapp_project"."name"
HAVING(
MIN("testapp_action"."status") IS NULL
OR MIN("testapp_action"."status") > 1
)
Which is pretty self-explanatory.
Django's ORM is not capable of expressing this. You will need to use a raw query in order to perform this.