Django ORM: Joining a table to itself - python

I have a table with cryptocurrency prices:
id | price | pair_id | exchange_id | date
---+--------+---------+-------------+---------------------------
1 | 8232.7 | 1 | 1 | 2018-02-09 09:31:00.160837
2 | 8523.8 | 1 | 2 | 2018-02-09 09:31:01.353998
3 | 240.45 | 2 | 1 | 2018-02-09 09:31:02.524333
I want to get the latest prices of a single pair from different exchanges. In raw SQL, I do it like this:
SELECT b.price, b.date, k.price, k.date, AVG((b.price + k.price) / 2)
FROM converter_price b JOIN converter_price k
WHERE b.exchange_id=1 AND k.exchange_id=2 AND b.pair_id=1 AND k.pair_id=1
ORDER BY b.date DESC, k.date DESC LIMIT 1;
8320.1|2018-02-09 11:23:00.369810|8318.2|2018-02-09 11:23:06.467424|8245.05199328066
How to do such query in the Django ORM?
EDIT: Adding models.py. I understand that it's quite likely that I'll need to update it.
from django.db import models
# Create your models here.
class Pair(models.Model):
identifier = models.CharField('Pair identifier', max_length=6)
def __str__(self):
return self.identifier
class Exchange(models.Model):
name = models.CharField('Exchange name', max_length=20)
def __str__(self):
return self.name
class Price(models.Model):
pair = models.ForeignKey(Pair, on_delete=models.CASCADE)
exchange = models.ForeignKey(Exchange, on_delete=models.CASCADE)
price = models.FloatField(default=0)
date = models.DateTimeField(auto_now=True, db_index=True)
def __str__(self):
return '{} - {}: {} ({})'.format(
self.date, self.pair, self.price, self.exchange)
EDIT2: To clarify what I'm really after.
I want the latest price with pair_id=1 and exchange_id=1, and the latest price with pair_id=1 and exchange_id=2. With a single query, and without any subsequent processing in Python - of course I can get Price.objects.all() and then search for it myself, but that's not the right way to use the ORM.
The way to do this with raw SQL is joining the table to itself. Showing how to join a table to itself using the Django ORM would (probably) answer the question.

Related

Django annotate count for another queryset

Models:
class Regions(models.Model):
name = models.CharField(max_length=255, unique=True)
class Owners(models.Model):
name = models.CharField(max_length=255, null=False, unique=True)
url = models.URLField(null=True)
class Lands(models.Model):
region = models.ForeignKey(Regions, on_delete=models.CASCADE)
owner = models.ForeignKey(Owners, on_delete=models.PROTECT, null=True)
description = models.TextField(max_length=2000, null=True)
class LandChangeHistory(models.Model):
land = models.ForeignKey(Lands, on_delete=models.CASCADE, null=False, related_name='lands')
price = models.IntegerField()
size = models.IntegerField()
date_added = models.DateField(auto_now_add=True)
Queryset that works but i need it to be annotated in another queryset somehow:
lands_in_region = Lands.objects.values('region__name').annotate(count=Count('region_id'))
returns for example:
{'region__name': 'New York', 'count': 3}, {'region__name':
'Chicago', 'count': 2}
In the 2nd queryset i need count of lands available in region. But instead of real count, i always get count = 1. How to combine them? Im pretty sure i could do it in raw sql by joining two tables on field "region__id", but dont know how to do it in django orm.
f = LandFilter(request.GET, queryset=LandChangeHistory.objects.all()
.select_related('land', 'land__region', 'land__owner')
.annotate(usd_per_size=ExpressionWrapper(F('price') * 1.0 / F('size'), output_field=FloatField(max_length=3)))
.annotate(count=Count('land__region_id'))
)
For example. If it returns:
land1 | 100$ | 100m2 | New York
land2 | 105$ | 105m2 | New York
land3 | 102$ | 102m2 | Chicago
i need 1 more column, that counts for each land how many NewYork's and Chicago's are there
land1 | 100$ | 100m2 | New York | 2
land2 | 105$ | 105m2 | New York | 2
land3 | 102$ | 102m2 | Chicago | 1
This worked for me. Hope helps somebody.
First i tried to simply call Count() method right after filter, but that breaks query, since it tries to get data from DB immediately. But that was the correct way to think, so i added count annotate and selected it and it worked.
f = LandFilter(request.GET, queryset=LandChangeHistory.objects.all()
.select_related('land', 'land__region', 'land__owner')
.annotate(usd_per_size=ExpressionWrapper(F('price') * 1.0 / F('size'), output_field=FloatField(max_length=3)))
.annotate(count=Subquery(
Lands.objects.filter(region_id=OuterRef('land__region_id'))
.values('region_id')
.annotate(count=Count('pk'))
.values('count')))
)

Python, Django: Query on combined models?

inside my app I have multiple models, like:
models.py:
class Company(models.Model):
name = models.CharField(max_length=100)
class Coworker(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
company = models.ForeignKey(Company, null=False, blank=False, on_delete=models.CASCADE)
As you can see, a Company can contain one, multiple or no Coworker! Is it possible to query Company but also receive the data from connected Coworker? For example something like this:
id | Company | Coworker
1 | Company_A | Coworker_A
2 | Company_B |
3 | Company_C | Coworker_B
4 | Company_C | Coworker_C
5 | Company_C | Coworker_D
6 | Company_D | Coworker_E
7 | Company_D | Coworker_F
8 | Company_E |
9 | Company_F | Coworker_G
10 | Company_F | Coworker_H
...
My problem is, that I can't query on the Coworker, because I don't want to miss those Companies that have no related data!
Thanks for your help and have a great day!
Quite simply, query by company and prefetch results for workers :
Company.objects.prefetch_related("coworker_set").all()
What this will do is give you a list of companies containing their list of workers in the attribute coworker_set. It will also populate those attributes in a single query (that's the whole point of prefetch_related).
More specifically, here is how you could use such a query :
for company in Company.objects.prefetch_related("coworker_set").all():
print(company.name)
for coworker in company.coworker_set.all():
print(coworker.first_name)
print(coworker.last_name)
This guarantees that you will iterate through all companies as well as through all coworkers, and you will only iterate through each one once (indeed, if you queried by coworkers you would see some companies multiple times and others none at all).

Django: How to retrieve a Queryset of the latest, uniquely named records with a MySQL database

I require a query that can search my model for the latest, unique records and return a queryset of the results.
I need distinct on setup_name and latest on updated
models.py
class VehicleSetupModel(models.Model):
inertia = models.IntegerField()
tyre_pressure = models.IntegerField()
class StandardVehicleSetupModel(VehicleSetupModel):
setup_name = models.CharField(max_length=20)
updated = models.DateTimeField(auto_now=True)
vehicle = models.ForeignKey(VehicleModel, on_delete=models.CASCADE)
What I've tried
I tried the following:
StandardVehicleSetupModel.objects.all().order_by('updated').distinct('setup_name')
but MySQL does not support DISTINCT ON querys.
Table Layout (DD-MM-YYYY)
setup_name | updated | vehicle
----------------------------------------------------------
TEH 10-10-2020 1
TEH 11-10-2020 1
TEL 10-10-2020 1
TEL 08-10-2020 1
TEL 01-10-2020 1
TEP 01-10-2020 1
Expected Result (DD-MM-YYYY)
setup_name | updated | vehicle
----------------------------------------------------------
TEH 11-10-2020 1
TEL 10-10-2020 1
TEP 01-10-2020 1
Yes, MySQL doesn't support distinct() with arguments. We can use another way:
from django.db.models import Max
results = (
StandardVehicleSetupModel.objects
.values('setup_name')
.annotate(max_id=Max('id'))
.values_list('max_id', 'setup_name')
.order_by('max_id')
)
Not tested yet, but it should work ^^

Django queryset with bidirectional relations

I'm struggling with making an efficient queryset with the following model:
class Connection(models.Model):
from = models.ForeignKey(User, related_name='from_connections')
to = models.ForeignKey(User, related_name='to_connections')
status = models.SmallIntegerField()
What I'm trying to do is, for a specified user, fetch a list of all his connections (so where he is either from or to) and annotate the status where he is from but also annotate the reverse status where he is to.
So for the example:
from | to | status
------------------------
A | B | 1
B | A | 0
C | A | 2
A | D | 2
the results would be something like this:
user | status | reverse_status
-----------------------------------
B | 1 | 0
C | None | 2
D | 2 | None
The closest solution I've got to so far is something like this:
qs = Connection.objects.filter(from_id=a_id)
reverse_qs = Connection.objects.filter(from_id=OuterRef("to_id"), to_id=a_id)
qs = qs.annotate(reverse_status=Subquery(reverse_status.values("status")[:1]))
This almost gives me what I need but since the queryset is including only results where user A is from, the results obviously don't contain anything for user C (from the example table above).
I also tried exploring the route with using related_names like
User.objects.filter(Q(from_connections__to=a_id)|Q(to_connections__from=a_id).annotate...
but this approach didn't get me far.
Does anyone have any ideas how to solve this using Django ORM? Much appreciated.
When you create a model having 2 ForeignKeys, you have to specify a related_name, Example :
class Connection(models.Model):
from = models.ForeignKey(User, related_name = 'from_connection')
to = models.ForeignKey(User, related_name = 'to_connection')
status = models.SmallIntegerField()
That will help in backwards relationships, more details in the docs

How to write a query with many ANDs and ORs with sum in Python?

I'm trying to write the following query in Django/Python:
in this query, I don't have a simple or, but I have many ands also and a sum
SELECT sum(value) FROM myapp_category
WHERE (name='difficulty' AND key='hard')
OR (name= 'success' AND key='yes')
OR (name= 'alternative' AND key='yes')
OR (name= 'processing' AND key='good')
OR (name= 'personal_touch' AND key='yes') `
and here's my model:
class Category(models.Model):
name = models.CharField(max_length=50)
key = models.CharField(max_length=30)
value = models.FloatField(blank=True, default=0)
def __str__(self):
return self.name.encode('utf_8') + "_" + self.key.encode('utf_8')
and I don't want to use the raw sql, so what can I use for this ?
Update Answer:
Thanks for your answers, this is the complete answer:
sum = Category.objects.filter(Q(name='difficulty',key=evaluation.difficulty) |
Q(name='nogos',key=evaluation.nogos) |
Q(name='success',key=evaluation.success) |
Q(name='alternative',key=evaluation.alternative) |
Q(name='processing',key=evaluation.processing) |
Q(name='personal_touch',key=evaluation.personal_touch))
.aggregate(result=Sum('value'))
score = float(sum['result'])
Try this;
from django.db.models import Q, Sum
Category.objects.filter(Q(name='difficulty',key='hard') | Q(name='success',key='yes') | Q(name='alternative',key='yes') | Q(name='processing',key='good') | Q(name='personal_touch',key='yes')).aggregate(Sum('value'))
UPD
from django.db.models import Q
results = Category.objects.filter(
Q(name="difficulty", key="hard") | Q(name= "success", key="yes") |
Q(name="alternative", key="yes")|Q(name="processing" AND key="good") |
Q(name="personal_touch",key="yes"))
Not really a solution but maybe input for other ideas:
SELECT sum(value) FROM myapp_category
WHERE name+"/"+key IN (
'difficulty/hard',
'success/yes',
'alternative/yes',
'processing/good',
'personal_touch/yes'
)
Problems:
Also lists cannot be provided using parameters
The query is slower because indexes cannot be used

Categories

Resources