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.

Изменить. Моя цель - получить контакты, у которых нет отношения "клиент" в дилерском контакте, но имеют отношение "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)
+3
источник поделиться
4 ответа

Я понял это, добавив два бинарных поля в DealerContact: is_ref и is_customer.

Если type='ref', то is_ref=1 и is_customer=0. Иначе, если type='customer', затем is_ref=0 и is_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)
+7
источник

На самом деле вы можете добавить свои собственные HAVING и GROUP BY предложения, если вам нужно.

Просто используйте мой пример с осторожностью - если код Django ORM/пути изменится в будущих версиях Django, вам также придется обновить свой код.

У вас есть модели книг и изданий, в которых для каждой книги может быть несколько выпусков, и вы хотите выбрать первую дату выпуска в США в рамках запроса на бронирование.

Добавление пользовательских предложений HAVING и GROUP BY в Django 1.5 +:

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.

+3
источник

Моя цель - получить контакты, у которых нет отношения "клиент" в dealercontact, но имеют отношение "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"))
0
источник

Вы не используете необработанный запрос только потому, что хотите объект orm? Используя Contact.objects.raw() генерируйте экземпляры, похожие на filter. Подробнее см. https://docs.djangoproject.com/en/dev/topics/db/sql/.

0
источник

Посмотрите другие вопросы по меткам или Задайте вопрос