Django queryset - Добавьте ограничение HAVING после annotate(F())

У меня возникла вроде бы обычная ситуация с добавлением HAVING в запрос. Я прочитал здесь и здесь, но мне это не помогло

Мне нужно добавить HAVING к моему запросу

МОДЕЛИ :

class Package(models.Model):
    status = models.IntegerField()


class Product(models.Model):
    title = models.CharField(max_length=10)
    packages = models.ManyToManyField(Package)

Продукты:

|id|title|
| - | - |
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | D |

Пакеты:

|id|status|
| - | - |
| 1 | 1 |
| 2 | 2 |
| 3 | 1 |
| 4 | 2 |

Product_Packages:

|product_id|package_id|
| - | - |
| 1 | 1 |
| 2 | 1 |
| 2 | 2 |
| 3 | 2 |
| 2 | 3 |
| 4 | 3 |
| 4 | 4 |

визуальный

  • пакет_1 (A, B) состояние ОК
  • пакет_2 (B, C) состояние не в порядке
  • пакет_3 (B, D) состояние ОК
  • пакет_4 (D) состояние не в порядке

Моя задача состоит в том, чтобы выбрать те продукты, у которых последний пакет в статусе = 1

Ожидаемый результат - : A, B

Мой запрос выглядит следующим образом

SELECT prod.title, max(tp.id)
FROM "product" as prod
INNER JOIN "product_packages" as p_p ON (p.id = p_p.product_id)
INNER JOIN "package" as pack ON (pack.id = p_p.package_id)
GROUP BY prod.title
HAVING pack.status = 1 

он возвращает именно то, что мне нужно

|title|max(pack.id)|
| - | - |
| A | 1 |
| B | 3 |

НО мой орм не работает правильно Я пытаюсь так

p = Product.objects.values('id').annotate(pack_id = Max('packages')).annotate(my_status = F('packages__status')).filter(my_status=1).values('id', 'pack_id')

p.query

SELECT "product". "id", MAX("product_packages". "package_id") AS "pack_id"
FROM "product" LEFT OUTER JOIN "product_packages" ON ("product". "id" = "product_packages". "product_id") LEFT OUTER JOIN "package" ON ("product_packages". "package_id" = "package". "id")
WHERE "package". "status" = 1
. GROUP BY "product". "id"

помогите мне сделать правильный ORM

Как выглядит запрос при удалении

.values('id', 'pack_id')

В конце?

Если я правильно помню, то:

p = Product.objects.values('id').annotate(pack_id = Max('packages')).annotate(my_status = F('packages__status')).filter(my_status=1)

и

    p = Product.objects.annotate(pack_id = Max('packages')).annotate(my_status = F('packages__status')).filter(my_status=1).values('id')

Будет результат при различных запросах

У Хаки Бениты есть отличный сайт, который создан для гуру баз данных и о том, как использовать Django по максимуму.

Вы можете взглянуть на этот пост: https://hakibenita.com/django-group-by-sql#how-to-use-having

Django имеет очень специфический способ добавления оператора "HAVING", т.е. ваш набор запросов должен быть структурирован так, чтобы за вашей аннотацией следовал вызов 'values' для выделения столбца, по которому вы хотите сгруппироваться, затем аннотация Max или любого другого агрегата, который вы хотите.

Также кажется, что эта аннотация не работает annotate(my_status = F('packages__status') вы хотите приписать несколько статусов к одной аннотации.

Возможно, вы захотите попробовать подзапрос, чтобы аннотировать так, как вам нужно.

e.g.

Product.objects.annotate(
    latest_pack_id=Subquery(
        Package.objects.order_by('-pk').filter(status=1).values('pk')[:1]
    )
).filter(
    packages__in=F('latest_pack_id')
)

Или что-то в этом роде, я не проверял это

Думаю, вы можете попробовать вот так с помощью subquery:

from django.db.models import OuterRef, Subquery
sub_query = Package.objects.filter(product=OuterRef('pk')).order_by('-pk')
products = Product.objects.annotate(latest_package_status=Subquery(sub_query.values('status')[0])).filter(latest_package_status=1)

Сначала я подготавливаю подзапрос, фильтруя модель Package первичным ключом Product и упорядочивая их по первичному ключу Package. Затем я взял последнее значение из подзапроса и аннотировал его с помощью Product queryset, а статус отфильтровал с помощью 1.

Вернуться на верх