Skip to content

Instantly share code, notes, and snippets.

@radiosilence
Last active August 14, 2016 11:16
Show Gist options
  • Save radiosilence/8022285 to your computer and use it in GitHub Desktop.
Save radiosilence/8022285 to your computer and use it in GitHub Desktop.
Django Raw Annotations. I wanted to add some complex aggregates, but .extra(select=) adds your raw SQL to GROUP BY, which doesn't work for aggregates. So, I trick the annotate() function by creating my own class that pretends to be an aggregate, but really just dumps SQL. Do not use for user inputted data.
SELECT "businesses_business"."id", "businesses_business"."user_id", "businesses_business"."slug", "businesses_business"."name", "businesses_business"."business_type_id", "businesses_business"."street", "businesses_business"."additional", "businesses_business"."town", "businesses_business"."county", "businesses_business"."postcode_id", "businesses_business"."phone_number", "businesses_business"."fax_number", "businesses_business"."email", "businesses_business"."website", "businesses_business"."strapline", "businesses_business"."description", "businesses_business"."active", "businesses_business"."deleted", "businesses_business"."activation_code", "businesses_business"."created_on", "businesses_business"."account_confirmed", "businesses_business"."preview", "businesses_business"."paid", "businesses_business"."last_updated", "businesses_business"."copy_id", "businesses_business"."logo", "businesses_business"."security_reference", "businesses_business"."video", "businesses_business"."embed_url", "businesses_business"."premium", "businesses_business"."auto_resave_code", "businesses_business"."created_during_funeral_announcement", "businesses_business"."geocode_cache", "businesses_business"."charity_no", "businesses_business"."bank_name", "businesses_business"."bank_account_no", "businesses_business"."bank_sort_code",
SUM("donation_donationamount"."amount") * 0.034 AS "total_charges",
SUM("donation_payment"."amount") AS "total_payment",
0.2 * COUNT("donation_donationamount"."amount") AS "total_20p",
SUM("donation_donationamount"."amount") - (SUM("donation_donationamount"."amount") * 0.034 + 0.2 * COUNT("donation_donationamount"."amount")) AS "total_net",
(SUM("donation_donationamount"."amount") - (SUM("donation_donationamount"."amount") * 0.034 + 0.2 * COUNT("donation_donationamount"."amount"))) - SUM("donation_payment"."amount") AS "total_balance",
SUM("donation_donationamount"."amount") AS "total_donation"
FROM "businesses_business"
INNER JOIN "businesses_businesstype" ON ("businesses_business"."business_type_id" = "businesses_businesstype"."id")
LEFT OUTER JOIN "donation_donationamount" ON ("businesses_business"."id" = "donation_donationamount"."business_id")
LEFT OUTER JOIN "donation_payment" ON ("businesses_business"."id" = "donation_payment"."business_id")
WHERE ("businesses_businesstype"."name" = Charity
AND "donation_donationamount"."id" IS NOT NULL)
GROUP BY "businesses_business"."id",
"businesses_business"."user_id",
"businesses_business"."slug",
"businesses_business"."name",
"businesses_business"."business_type_id",
"businesses_business"."street",
"businesses_business"."additional",
"businesses_business"."town",
"businesses_business"."county",
"businesses_business"."postcode_id",
"businesses_business"."phone_number",
"businesses_business"."fax_number",
"businesses_business"."email",
"businesses_business"."website",
"businesses_business"."strapline",
"businesses_business"."description",
"businesses_business"."active",
"businesses_business"."deleted",
"businesses_business"."activation_code",
"businesses_business"."created_on",
"businesses_business"."account_confirmed",
"businesses_business"."preview",
"businesses_business"."paid",
"businesses_business"."last_updated",
"businesses_business"."copy_id",
"businesses_business"."logo",
"businesses_business"."security_reference",
"businesses_business"."video",
"businesses_business"."embed_url",
"businesses_business"."premium",
"businesses_business"."auto_resave_code",
"businesses_business"."created_during_funeral_announcement",
"businesses_business"."geocode_cache",
"businesses_business"."charity_no",
"businesses_business"."bank_name",
"businesses_business"."bank_account_no",
"businesses_business"."bank_sort_code"
from myapp.models import RawAnnotation
def get_charities(data=None):
data = data or {}
totals = {}
charities = []
# Bits of SQL
total_donations = 'SUM("donation_donationamount"."amount")'
total_charges = '{} * 0.034'.format(total_donations)
total_20p = '0.2 * COUNT("donation_donationamount"."amount")'
total_net = '{} - ({} + {})'.format(total_donations, total_charges, total_20p)
total_payment = 'SUM("donation_payment"."amount")'
total_balance = '({}) - {}'.format(total_net, total_payment)
charities = (
Business.objects
.filter(business_type__name='Charity')
.filter(donationamount__isnull=False)
.annotate(
total_donation=Sum('donationamount__amount'),
total_payment=Sum('payment__amount'),
total_charges=RawAnnotation(total_charges),
total_20p=RawAnnotation(total_20p),
total_net=RawAnnotation(total_net),
total_balance=RawAnnotation(total_balance),
)
)
class RawAnnotation(object):
# Needs to be an existing field. Doesn't matter which.
lookup = 'pk'
def __init__(self, extra):
self.extra = extra
def add_to_query(self, query, alias, col, source, is_summary):
class FakeField(object):
def get_internal_type(self):
return None
class FakeQuery(object):
is_ordinal = False
field = FakeField()
is_computed = False
def __init__(self, sql):
self.sql = sql
def as_sql(self, *args):
return self.sql
query.aggregates[alias] = FakeQuery(self.extra)
@malinich
Copy link

please help me:
i use thank hack for create raw annotate but i catch exception ValueError: too many values to unpack
why this exception raise?

lte = 'COALESCE("finance_actitem"."total"/ NULLIF("finance_act"."total"-"finance_act"."discount", 0), 0)'  
items = ActItem.objects.filter(
     Q(act__user=user.id) 
).annotate(
     lt=RawAnnotation(lte)
 ).filter(total__lte=F('lt'))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment