django order by aggregate value from non-related table - django

I have three models, Accrual and Member, and the common field of these two models is register_no, but this field is not a foreign key
class Accrual(models.Model):
register_no = models.PositiveIntegerField(verbose_name=_('Register No'))
amount=models.DecimalField(decimal_places=2, max_digits=17, verbose_name=_('Total Amount'))
class Member(models.Model):
register_no = models.PositiveIntegerField(unique=True, verbose_name=_('Register No'))
class Driver(models.Model):
register_no = models.PositiveIntegerField(unique=True, verbose_name=_('Register No'))
I want to list the debt each member/driver has. It can be done with #property;
class Member(models.Model):
register_no = models.PositiveIntegerField(unique=True, verbose_name=_('Register No'))
#property
def debt(self):
ret_val = Accrual.objects.filter(register_no=self.register_no).aggregate(
debt=Sum('amount'))
debt = ret_val.get('debt', 0)
return debt if debt else 0
but I can't use order_by this way. I want to sort each member by debt. How can I solve this problem?

You can use a subquery to add all the related accruals based on the register_no:
from django.db.models import OuterRef, Subquery, Sum
accruals = Accrual.objects.filter(
register_no=OuterRef('register_no')
).values('register_no').annotate(debt=Sum('amount'))
Member.objects.annotate(
debt=Subquery(accruals.values('debt'))
).order_by('debt')
which produces this sql:
SELECT "id", "register_no", (
SELECT SUM(U0."amount") AS "debt"
FROM "accrual" U0
WHERE U0."register_no" = ("register_no")
GROUP BY U0."register_no"
) AS "debt"
FROM "member"
ORDER BY "debt" ASC

Related

How to know the count of many to many fields and reverse relationship of an random model?

I have an random model:
class Model(models.Model):
other_field1 = models.SomeField(...)
m2m_field1 = models.ManyToManyField(...)
other_field2 = models.SomeField(...)
m2m_field2 = models.ManyToManyField(...)
other_field3 = models.SomeField(...)
I want to know the count of fields that correspond to the relation many to many and the count of other fields.
In the example above, I have 2 fields with a many-to-many relationship and 3 other fields.
EDIT
How to calculate the number of reverse relationships?
class OtherModel1(models.Model):
field = models.ForeginKey(Model)
class OtherModel2(models.Model):
field = models.ForeginKey(Model)
You can work with the ._meta option, and thus determine the number of items with:
from django.db.models import ManyToManyField
from django.db.models.fields.reverse_related import ForeignObjectRel
number_of_m2m_fields = sum(
isinstance(m, ManyToManyField) for m in Model._meta.get_fields()
)
number_of_other_fields = sum(
not isinstance(m, ManyToManyField) for m in Model._meta.get_fields()
)
number_of_reverse_relations = sum(
isinstance(mto, ForeignObjectRel) for mto in Model._meta.get_fields()
)

Django Select across three models with filter criteria

I have three models:
class Vehicle(models.Model):
Vehicle_ID = models.AutoField('ID', primary_key= True)
Vehicle_VIN = models.CharField('FIN', max_length=30)
Vehicle_DELETED = models.BooleanField('Gelöscht',default=False)
class Recall(models.Model):
Recall_ID = models.AutoField('ID', primary_key= True)
Recall_CODE = models.CharField('Rückruf-Code',max_length=500, unique= True)
class Vehicle_Recall(models.Model):
Vehicle_Recall_ID = models.AutoField('ID', primary_key=True)
Vehicle = models.ForeignKey(Vehicle, on_delete=models.CASCADE)
Recall = models.ForeignKey(Recall, on_delete=models.CASCADE)
I want to make a Select Statement like this:
SELECT * FROM Vehicle INNER JOIN(Recall INNER JOIN Vehicle_Recall ON Recall.ID = Vehicle_Recall.Recall) ON Vehicle.ID = Vehicle_Recall.Vehicle WHERE Recall.ID=1 AND Vehicle.DELETED)=FALSE;
Is there a way to make such query in django?
You canuse the Django's ORM as follows (it is an example):
vehicles = Vehicle.objects.filter(Vehicle_DELETED=False).filter(
Q(vehicle_recall__Recall__Recall_ID=1)
)
which generates the following SQL query:
SQL SELECT "vehicle"."Vehicle_ID", "vehicle"."Vehicle_VIN", "vehicle"."Vehicle_DELETED" FROM "vehicle" INNER JOIN "vehicle_recall" ON ("vehicle"."Vehicle_ID" = "vehicle_recall"."Vehicle_id") WHERE ("vehicle"."Vehicle_DELETED" = False AND "vehicle_recall"."Recall_id" = 1)
You can use the raw query as follows:
vehicles = Vehicle.objects.raw(f"""
SELECT * FROM Vehicle
INNER JOIN(Recall INNER JOIN Vehicle_Recall ON Recall.ID = Vehicle_Recall.Recall)
ON Vehicle.ID = Vehicle_Recall.Vehicle
WHERE Recall.ID=1 AND Vehicle.DELETED=FALSE;
""")
and remember to adjust name of tables.
I propose the first solution, but the appropriate code depends on needs. I prepared only some example to present its simplicity.

How do I filter by occurrences count in another table in Django?

Here are my models:
class Zoo(TimeStampedModel):
id = models.AutoField(primary_key=True)
class Animal(models.Model):
id = models.AutoField(primary_key=True)
zoo = models.ForeignKey(Zoo, on_delete=models.PROTECT, related_name='diffbot_results')
I would like to run a query like this:
Zoo.objects.filter("WHERE zoo.id IN (select zoo_id from animal_table having count(*) > 10 group by zoo_id)")
One way is to use a raw queryset:
>>> from testapp.models import Zoo, Animal
>>> z1, z2 = Zoo(), Zoo()
>>> z1.save(), z2.save()
(None, None)
>>> z1_animals = [Animal(zoo=z1) for ii in range(5)]
>>> z2_animals = [Animal(zoo=z2) for ii in range(15)]
>>> x = [a.save() for a in z1_animals+z2_animals]
>>> qs = Zoo.objects.raw("select * from testapp_zoo zoo WHERE zoo.id IN (select zoo_id from testapp_animal group by zoo_id having count(1) > 10)")
>>> list(qs)
[<Zoo: Zoo object (2)>]
In theory, per these docs, it should be possible to pass in a queryset to a regular .filter(id__in=<queryset>), but the queryset must only select one column, and I can't find a way of adding the HAVING clause without also causing the queryset to select a num_animals column, preventing it from being used with an __in filter expression.

Aggregation on 'one to many' for matrix view in Django

I have two tables like below.
These are in 'one(History.testinfoid) to many(Result.testinfoid)' relationship.
(Result table is external database)
class History(models.Model): # default database
idx = models.AutoField(primary_key=True)
scenario_id = models.ForeignKey(Scenario)
executor = models.CharField(max_length=255)
createdate = models.DateTimeField()
testinfoid = models.IntegerField(unique=True)
class Result(models.Model): # external (Result.objects.using('external'))
idx = models.AutoField(primary_key=True)
testinfoid = models.ForeignKey(History, to_field='testinfoid', related_name='result')
testresult = models.CharField(max_length=10)
class Meta:
unique_together = (('idx', 'testinfoid'),)
So, I want to express the Count by 'testresult' field in Result table.
It has some condition such as 'Pass' or 'Fail'.
I want to express a count query set for each condition. like this.
[{'idx': 1, 'pass_count': 10, 'fail_count': 5, 'executor': 'someone', ...} ...
...
{'idx': 10, 'pass_count': 1, 'fail_count': 10, 'executor': 'someone', ...}]
Is it possible?
It is a two level aggregation where the second level should be displayed as table columns - "matrix view".
A) Solution with Python loop to create columns with annotations by the second level ("testresult").
from django.db.models import Count
from collections import OrderedDict
qs = (History.objects
.values('pk', 'executor', 'testinfoid',... 'result__testresult')
.annotate(result_count=Count('pk'))
)
qs = qs.filter(...).order_by(...)
data = OrderedDict()
count_columns = ('pass_count', 'fail_count', 'error_count',
'expected_failure_count', 'unexpected_success_count')
for row in qs:
data.setdefault(row.pk, dict.fromkeys(count_columns, 0)).update(
{(k if k != result_count else row['result__testresult'] + '_count'): v
for k, v in row_items()
if k != 'result__testresult'
}
)
out = list(data.values())
The class OrderedDict is used to preserve order_by().
B) Solution with Subquery in Django 1.11+ (if the result should be a queryset. e.g. to be sorted or filtered finally in an Admin view by clicking, and if a more complicated query is acceptable and number of columns *_count is very low.). I can write a solution with subquery, but I'm not sure if the query will be fast enough with different database backends. Maybe someone other answers.

Building up subqueries of derived django fields

I have a few transformations I need to perform on my table before I aggregate.
I need to multiply transaction_type (which is either 1 or -1) by amount to yield a signed_amount. Then I need to sum all signed_amounts by primary_category (which is a foreign key to secondary category which is a foreign key of my table).
DEBIT = -1
CREDIT = 1
TRANSACTION_TYPE_CHOICES = (
(DEBIT, 'debit'),
(CREDIT, 'credit'),
)
class Transaction(models.Model):
amount = models.DecimalField(max_digits=7, decimal_places=2)
transaction_type = models.IntegerField(choices=TRANSACTION_TYPE_CHOICES)
secondary_category = models.ForeignKey(Secondary_Category)
class Primary_Category(models.Model):
name = models.CharField("Category Name", max_length=30)
category = models.ForeignKey(Primary_Category_Bucket)
class Secondary_Category(models.Model):
name = models.CharField("Category Name", max_length=30)
primary_category = models.ForeignKey(Primary_Category)
I'm stuck on the first bit though.
from django.db.models import Sum, Count, F
original_transactions = Transaction.objects.all()
original_transactions.signed_amount = F('transaction_type') * F('amount')
for transaction in original_transactions:
print transaction.signed_amount
When I try to sanity check that signed_amount is being calculated, I get an error that 'Transaction' object has no attribute 'signed_amount'. I don't want to save signed_amount to the database. I just want to generate it as derived field so I can calculate my totals.
How do I calculate this derived field and subsequently aggregate by primary_category.name?
User python decorator property on a method for class Transaction:
class Transaction(models.Model):
amount = models.DecimalField(max_digits=7, decimal_places=2)
transaction_type = models.IntegerField(choices=TRANSACTION_TYPE_CHOICES)
secondary_category = models.ForeignKey(Secondary_Category)
#property
def signed_amount(self):
return self.amount * self.transaction_type
Then for each Transaction object you can do transaction.signed_amount.
I'm not sure if the aggregation part could be done using queries, but if you don't have that many PrimaryCategory, then python would be good enough to achieve it.
Or you can do this.
all_transactions = Transaction.objects.all().order_by('secondary_category__primary_category_id')
sum = 0
if all_transactions:
primary_category_id = all_transactions[0].secondary_category.primary_category_id
for transaction in all_transactions:
if primary_category_id == transaction.secondary_category.primary_category_id:
sum += (transaction.amount * transaction_type)
else:
sum = (transaction.amount * transaction_type)
print sum