Использование аннотаций и агрегатов без получения дубликатов - PullRequest
2 голосов
/ 14 мая 2019

У меня есть queryset в моем приложении Django. Набор запросов обеспечивает доступ к информации из нескольких таблиц, заполненных информацией о продуктах ( цена на одного поставщика , название, запас и т. Д.) обычный материал, который можно найти в связанных с покупками товарах приложение).

Поскольку один продукт может иметь несколько цен, я получаю дубликаты. Это на самом деле хорошо и логично, так как SQL просто показывает мне столько же продублированных продуктов, сколько и цены каждого продукта.

Вот где я использую агрегаты:

queryset.annotate(
   annotate_min_price=Min("product_prices__price"),
)

Это заставляет мой набор запросов возвращать только самую низкую цену для каждого продукта, что предотвращает дублирование продуктов.

На данный момент запрос выглядит так:

SELECT DISTINCT 
    "prod_prod"."id",
    ...
    MIN ( "monetary_prodprice"."system_all_included_price" ) AS "annotate_min_price" 
FROM
    "prod_prod"
    INNER JOIN "monetary_prodprice" ON ( "prod_prod"."id" = "monetary_prodprice"."prod_id" )
    INNER JOIN "monetary_pricelist" ON ( "monetary_prodprice"."pricelist_id" = "monetary_pricelist"."id" )
    INNER JOIN "monetary_pricelistdestinations" ON ( "monetary_pricelist"."id" = "monetary_pricelistdestinations"."pricelist_id" )
    INNER JOIN "prodtransaction_carrier_pricelists" ON ( "monetary_pricelist"."id" = "prodtransaction_carrier_pricelists"."pricelist_id" )
    INNER JOIN "prodtransaction_carrier" ON ( "prodtransaction_carrier_pricelists"."carrier_id" = "prodtransaction_carrier"."id" )
    INNER JOIN "prodtransaction_carrierdelivery" ON ( "prodtransaction_carrier"."id" = "prodtransaction_carrierdelivery"."carrier_id" )
    INNER JOIN "monetary_pricelistcountry" ON ( "monetary_pricelist"."id" = "monetary_pricelistcountry"."pricelist_id" ) 
WHERE
    (
        ...
    ) 
GROUP BY
    "prod_prod"."id",
ORDER BY
    "annotate_min_price" DESC

Проблема в том, что, кроме самой низкой цены, мне нужно получить действительную ID этой цены. Поэтому я изменяю свой набор запросов соответственно:

queryset.annotate(
    annotate_min_price=Min("prod_prices__system_all_included_price"),
    annotate_best_price=F('prod_prices__pk')).order_by(ordering)

Вот тут я и решаю проблему. Это даст следующий запрос:

SELECT DISTINCT 
    "prod_prod"."id",
    ...
    MIN ( "monetary_prodprice"."system_all_included_price" ) AS "annotate_min_price",
    "monetary_prodprice"."id" ) AS "annotate_best_price" 
FROM
    "prod_prod"
    INNER JOIN "monetary_prodprice" ON ( "prod_prod"."id" = "monetary_prodprice"."prod_id" )
    INNER JOIN "monetary_pricelist" ON ( "monetary_prodprice"."pricelist_id" = "monetary_pricelist"."id" )
    INNER JOIN "monetary_pricelistdestinations" ON ( "monetary_pricelist"."id" = "monetary_pricelistdestinations"."pricelist_id" )
    INNER JOIN "prodtransaction_carrier_pricelists" ON ( "monetary_pricelist"."id" = "prodtransaction_carrier_pricelists"."pricelist_id" )
    INNER JOIN "prodtransaction_carrier" ON ( "prodtransaction_carrier_pricelists"."carrier_id" = "prodtransaction_carrier"."id" )
    INNER JOIN "prodtransaction_carrierdelivery" ON ( "prodtransaction_carrier"."id" = "prodtransaction_carrierdelivery"."carrier_id" )
    INNER JOIN "monetary_pricelistcountry" ON ( "monetary_pricelist"."id" = "monetary_pricelistcountry"."pricelist_id" ) 
WHERE
    (
        ...
    ) 
GROUP BY
    "prod_prod"."id",
    "monetary_prodprice"."id"
ORDER BY
    "annotate_min_price" DESC

Это заставляет мой набор запросов дублировать продукты. Я понимаю, что это происходит потому, что я прошу PostgreSQL добавить ID цен к каждой строке (продукту), и это как-то нарушает агрегатор MIN.

Мой вопрос: как я могу заставить Django возвращать только продукты с самой низкой ценой и ID этой цены одновременно?

1 Ответ

1 голос
/ 14 мая 2019

вы можете использовать подзапросы

min_query =
    ProductPrice.objects.filter(product_id=OuterRef('id'))
    .order_by('system_all_included_price')
    .values('system_all_included_price', 'id')[:1]

queryset.annotate(
    annotate_min_price=Subquery(
        min_query.values('system_all_included_price')
    ).annotate(
    annotate_min_id=Subquery(
        min_query.values('id')
    )
).order_by(ordering)

Решение ниже не работает, как указано в комментариях, потому что вы не можете ссылаться на оконную функцию в фильтре

Вы должны иметь возможность комментировать каждый продукт с минимальной ценой

queryset.annotate(
    annotate_min_price=Window(
        expression=Min("prod_prices__system_all_included_price"),
        partition_by=F('prod_prices__pk'),
        order_by=ordering,
    ),
).filter(annotate_min_price=F('prod_prices__system_all_included_price)

если в некоторых случаях у вас могут быть равные цены, возможно, вы захотите снова пройти через раздел аннотирования, чтобы определить самый низкий идентификатор на этот раз, а затем отфильтровать идентификатор, соответствующий наименьшему.

queryset.annotate(
    annotate_min_id=Window(
        expression=Min("prod_prices__pk"),
        partition_by=F('prod_prices__system_all_included_price'),
        order_by=ordering,
    ),
).filter(annotate_min_id=F('prod_prices__pk'))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...