get count of a Subquery - django

I want to annotate a count field in my queryset with a subquery filter:
My code:
module_attempts = Subquery(
ModuleProgressAttempt.objects.filter(
module_progress__module__id=OuterRef("pk")
).only("pk")
)
real_instances = VirtualClassRoomModule.objects.filter(
id__in=[vc.id for vc in vc_classrooms]
).annotate(
attendees_count=Count(module_attempts),
)
Here module_progress__module__id is the id of the current VirtualClassRoomModule of the annotation iteration. The count is basically the length of the ModuleProgressAttempt filtered queryset. Currently the count is always 1 , eventhough the
ModuleProgressAttempt.objects.filter(
module_progress__module__id=<Current-module-id>
)
returns more than one object.

This solution worked for me:
module_attempts_count = Subquery(
ModuleProgressAttempt.objects.filter(module_progress__module__id=OuterRef("id"))
.values("module_progress__module__id")
.annotate(cnt=Count("id"))
.values("cnt")
)
real_instances = VirtualClassRoomModule.objects.filter(
id__in=[vc.id for vc in vc_classrooms]
).annotate(
attendees_count=module_attempts_count,
)

Related

How to get 1 value from DAX FILTER?

I have Users table containing user_email, user_name, user_category.
The following DAX filter returns a table:
FILTER(Users,[User_Email] = userprincipalname())
I want to get the user_category.
1 approach is SELECTEDCOLUMNS( FILTER(Users,[User_Email] = userprincipalname()), "User_category", [User_Category] ). This returns the result as a column.
Is there any alternate approach to return just 1 value? For example:
SELECTEDVALUE ( SELECTEDCOLUMNS( FILTER(Users,[User_Email] = userprincipalname()), "User_category", [User_Category] ) )
OR
VALUES ( SELECTEDCOLUMNS( FILTER(Users,[User_Email] = userprincipalname()), "User_category", [User_Category] ) )
You can use MAXX on the table generated by FILTER.
MAXX(
FILTER(Users,[User_Email] = userprincipalname()),
[User_Category]
)
You can do this with CALCULATE assuming you don't expect there to be multiple values to choose from (if there are, this will return a blank).
CALCULATE (
SELECTEDVALUE ( Users[User_Category] ),
FILTER ( Users, Users[User_Email] = userprincipalname() )
)

How to exclude values in django aggregation?

I have done this:
groups = group1.objects.filter(User=request.user, Company=company_details.pk, ledgergroups__Creation_Date__gte=selectdatefield_details.Start_Date, ledgergroups__Creation_Date__lte=selectdatefield_details.End_Date).exclude(group_Name__icontains='Capital A/c')
groups_cb = groups.annotate(
closing = Coalesce(Sum('ledgergroups__Closing_balance'), 0),
opening = Coalesce(Sum('ledgergroups__Balance_opening'), 0),
)
I want to perform aggregation in 'closing' and 'opening' by interchanging the negative values of annotation...
I mean the negative value which will come through annotation in 'closing', that value should be added with the aggregated value of 'opening'....
For example:
if the values of closing are 2500,5000,-8000
The total value will be 7500(aggregated value)
and -8000 will be added in the aggregated value of 'opening'.
Do anyone have any idea how to solve this?
You can use a Case and When within a Sum annotation:
Sum(
Case(
When(
another_model__field_value__lt=0,
then=F('another_model__field_value'),
),
default=F('another_model__other_field_value'),
output_field=IntegerField(),
)
)

Django .annotate with When

I am trying to do a query and get a sum of values based on a condition.
I need all the 'shares' when the 'action' == '+'
I have to group by the issues.
qs = Trades.objects.all ().filter ( id = id )
.annotate (
d_shrs = Sum ( When (
action = '+', then = 'shares'
) )
).order_by ( 'issue' )
Where am I going wrong?
You need to use Case (it allows you to use if-else kind of constructs in your queries) with a When, F will get you the actual value of the field
from django.db.models import Case, When, F, Sum
qs = Trades.objects.filter(id=id).annotate(
d_shrs=Sum(
Case(
When (
action='+', then=F('shares')
)
)
)
).order_by('issue')

Django Query Get Last Record with a Group By

I am trying to get all the last records based on the id, sorted by the month.
this gives me the last record,
qs = Cashflow.objects.filter ( acct_id = a.id ).order_by('-month')[:1]
And this groups the accounts,
qs = Cashflow.objects
.filter ( acct_id = a.id )
.values ( 'acct_id' )
.annotate ( count = Count ( 'acct_id' ) )
.values ( 'acct_id', 'count' )
.order_by ( )
How how can I combine the two queries into one?
Group by acct_id, sort by "month" and get last record.
is this even possible? thanks
EDIT:
this is the sql version of what I am trying to do.
select *
from cashflow t
inner join (
select acct_id, max ( `month` ) as MaxDate
from cashflow
where acct_id IN ( 1,2,3,... )
group by acct_id
) tm on t.acct_id = tm.acct_id and t.month = tm.MaxDate
order by acct_id
Can this be done in pure Django of should I just do a Raw query?
cheers.
Best solution I found online https://gist.github.com/ryanpitts/1304725
'''
given a Model with:
category = models.CharField(max_length=32, choices=CATEGORY_CHOICES)
pubdate = models.DateTimeField(default=datetime.now)
<other fields>
Fetch the item from each category with the latest pubdate.
'''
model_max_set = Model.objects.values('category').annotate(max_pubdate=Max('pubdate')).order_by()
q_statement = Q()
for pair in model_max_set:
q_statement |= (Q(category__exact=pair['category']) & Q(pubdate=pair['max_pubdate']))
model_set = Model.objects.filter(q_statement)
Instead .order_by('-month')[:1] it's better to use .order_by('month').last() or .order_by('-month').first() (or earliest/latest for dates).
Of course when grouping you can use order_by:
last_record = Cashflow.objects \
.values('acct_id') \
.annotate(count=Count('id')) \
.order_by('month') \
.last()

Translating MySql query into Django ORM query

I have a query in MySql that I need translated into Django ORM. It involves joining on two tables with two counts on one of the tables. I'm pretty close to it in Django but I get duplicate results. Here's the query:
SELECT au.id,
au.username,
COALESCE(orders_ct, 0) AS orders_ct,
COALESCE(clean_ct, 0) AS clean_ct,
COALESCE(wash_ct, 0) AS wash_ct
FROM auth_user AS au
LEFT OUTER JOIN
( SELECT user_id,
Count(*) AS orders_ct
FROM `order`
GROUP BY user_id
) AS o
ON au.id = o.user_id
LEFT OUTER JOIN
( SELECT user_id,
Count(CASE WHEN service = 'clean' THEN 1
END) AS clean_ct,
Count(CASE WHEN service = 'wash' THEN 1
END) AS wash_ct
FROM job
GROUP BY user_id
) AS j
ON au.id = j.user_id
ORDER BY au.id DESC
LIMIT 100 ;
My current Django query (which brings back unwanted duplicates):
User.objects.annotate(
orders_ct = Count( 'orders', distinct = True )
).annotate(
clean_ct = Count( Case(
When( job__service__exact = 'clean', then = 1 )
) )
).annotate(
wash_ct = Count( Case(
When( job__service__exact = 'wash', then = 1 )
) )
)
The above Django code produces the following query which is close but not right:
SELECT DISTINCT `auth_user`.`id`,
`auth_user`.`username`,
Count(DISTINCT `order`.`id`) AS `orders_ct`,
Count(CASE
WHEN `job`.`service` = 'clean' THEN 1
ELSE NULL
end) AS `clean_ct`,
Count(CASE
WHEN `job`.`service` = 'wash' THEN 1
ELSE NULL
end) AS `wash_ct`
FROM `auth_user`
LEFT OUTER JOIN `order`
ON ( `auth_user`.`id` = `order`.`user_id` )
LEFT OUTER JOIN `job`
ON ( `auth_user`.`id` = `job`.`user_id` )
GROUP BY `auth_user`.`id`
ORDER BY `auth_user`.`id` DESC
LIMIT 100
I could probably achieve it by doing some raw sql subqueries but I would like to remain as abstract as possible.
Based on this answer, you can write:
User.objects.annotate(
orders_ct = Count( 'orders', distinct = True ),
clean_ct = Count( Case(
When( job__service__exact = 'clean', then = F('job__pk') )
), distinct = True ),
wash_ct = Count( Case(
When( job__service__exact = 'wash', then = F('job__pk') )
), distinct = True )
)
Table (after joins):
user.id order.id job.id job.service your case/when my case/when
1 1 1 wash 1 1
1 1 2 wash 1 2
1 1 3 clean NULL NULL
1 1 4 other NULL NULL
1 2 1 wash 1 1
1 2 2 wash 1 2
1 2 3 clean NULL NULL
1 2 4 other NULL NULL
Desired output for wash_ct is 2. Counting distinct values in my case/when, we will get 2.
I think this will work, the chained annotation of job might have produced duplicate users.
If not can you elaborate on duplicates you are seeing.
User.objects.annotate(
orders_ct = Count( 'orders', distinct = True )
).annotate(
clean_ct = Count( Case(
When( job__service__exact = 'clean', then = 1 )
) ),
wash_ct = Count( Case(
When( job__service__exact = 'wash', then = 1 )
) )
)
Try adding values(), also when distinct=True you can combine Count()'s in one annotation().
Users.objects.values("id").annotate(
orders_ct = Count('orders', distinct = True)
).annotate(
clean_ct = Count(Case(When(job__service__exact='clean', then=1)),
distinct = True),
wash_ct = Count(Case(When(job__service__exact='wash',then=1)),
distinct = True)
).values("id", "username", "orders_ct", "clean_ct", "wash_сt")
Using values("id") should add GROUP BY 'id' for annotations and therefore prevent duplicates, see docs.
Also, there's Coalesce, but it doesn't look like it's needed, since Count() returns int anyway. And distinct, but again the distinct in Count() should be enough.
Not sure if Case needed inside Count() as it should count them anyway.