Django queryset - 添加HAVING约束

时间:2013-12-02 14:39:13

标签: django group-by sum django-queryset having

我已经使用Django几年了,但是我今天在为HAVING添加GROUP BY约束时遇到了困难。

我的查询集如下:

crm_models.Contact.objects\
.filter(dealercontact__dealer__pk__in=(265,),
         dealercontact__activity='gardening',
         date_data_collected__gte=datetime.date(2012,10,1),
         date_data_collected__lt=datetime.date(2013,10,1))\
.annotate(nb_rels=Count('dealercontact'))

它给了我以下MySQL查询:

SELECT *
FROM `contact` 
LEFT OUTER JOIN `dealer_contact` ON (`contact`.`id_contact` = `dealer_contact`.`id_contact`) 
WHERE (`dealer_contact`.`active` = True 
   AND `dealer_contact`.`activity` = 'gardening'  
   AND `contact`.`date_data_collected` >= '2012-10-01'  
   AND `contact`.`date_data_collected` < '2013-10-01'
   AND `dealer_contact`.`id_dealer` IN (265)) 
GROUP BY `contact`.`id_contact`
ORDER BY NULL;

我会得到这个HAVING约束所需的确切内容:

HAVING SUM(IF(`dealer_contact`.`type`='customer', 1, 0)) = 0 

如何使用Django Queryset修复此问题?我需要一个查询集。

此处我仅使用注释来获取GROUP BY上的contact.id_contact

编辑:我的目标是获得在dealercontact中没有“客户”关系但具有“ref”关系的联系人(当然,根据WHERE子句)。

模型

class Contact(models.Model):
    id_contact = models.AutoField(primary_key=True)
    title = models.CharField(max_length=255L, blank=True, choices=choices_custom_sort(TITLE_CHOICES))
    last_name = models.CharField(max_length=255L, blank=True)
    first_name = models.CharField(max_length=255L, blank=True)
    [...]
    date_data_collected = models.DateField(null=True, db_index=True)

class Dealer(models.Model):
    id_dealer = models.AutoField(primary_key=True)
    address1 = models.CharField(max_length=45L, blank=True)
    [...]

class DealerContact(Auditable):
    id_dealer_contact = models.AutoField(primary_key=True)
    contact = models.ForeignKey(Contact, db_column='id_contact')
    dealer = models.ForeignKey(Dealer, db_column='id_dealer')
    activity = models.CharField(max_length=32, choices=choices_custom_sort(ACTIVITIES), db_index=True)
    type = models.CharField(max_length=32, choices=choices_custom_sort(DEALER_CONTACT_TYPE), db_index=True)

4 个答案:

答案 0 :(得分:7)

我通过在DealerContact中添加两个二进制字段来解决这个问题:is_refis_customer

如果type='ref'is_ref=1is_customer=0。 否则type='customer'然后is_ref=0is_customer=1

因此,我现在可以使用annotate(nb_customers=Sum('is_customer')),然后使用filter(nb_customers=0)

最终的查询集包括:

Contact.objects.filter(dealercontact__dealer__pk__in=(265,),  
                       dealercontact__activity='gardening', 
                       date_data_collected__gte=datetime.date(2012,10,1),
                       date_data_collected__lt=datetime.date(2013,10,1))\
               .annotate(nb_customers=Sum('dealercontact__is_customer'))\
               .filter(nb_customers=0)

答案 1 :(得分:3)

实际上,如果需要,您可以添加自己的自定义HAVINGGROUP BY条款。

谨慎使用我的示例 - 如果Django ORM代码/路径在将来的Django版本中发生变化,您也必须更新代码。

图片您有Book和Edition型号,每本书可以有多个版本,您想在Book queryset中选择第一个美国版本日期。

在Django 1.5 +中添加自定义HAVINGGROUP BY子句:

from django.db.models import Min
from django.db.models.sql.where import ExtraWhere, AND

qs = Book.objects.all()

# Standard annotate
qs = qs.annotate(first_edition_date=Min("edition__date"))

# Custom HAVING clause, to limit annotation by US country only
qs.query.having.add(ExtraWhere(['"app_edition"."country"=%s'], ["US"]), AND)

# Custom GROUP BY clause will be needed too
qs.query.group_by.append(("app_edition", "country"))

ExtraWhere不仅可以包含字段,还可以包含任何原始sql条件和函数。

答案 2 :(得分:0)

  

我的目标是让那些没有“客户”关系的联系人   经销商联系,但有“ref”关系(根据WHERE   当然是条款。

这个简单的查询满足了这个要求:

Contact.objects.filter(dealercontact__type="ref").exclude(dealercontact__type="customer")

这是否足够,或者你需要它做更多的事情吗?

更新:如果您的要求是

  

具有“ref”关系但没有“客户”的联系人   与同一经销商的关系

你可以这样做:

from django.db.models import Q
Contact.objects.filter(Q(dealercontact__type="ref") & ~Q(dealercontact__type="customer"))

答案 3 :(得分:0)

您是否因为想要orm对象而不使用原始查询?使用Contact.objects.raw()生成类似filter的实例。有关更多帮助,请参阅https://docs.djangoproject.com/en/dev/topics/db/sql/