winordie
@winordie
Лучшая документация -- исходники

Как оптимально отфильтровать по необязательной связаной таблице?

Имеется 2 таблицы:
class Sale(models.Model):
  percent = models.FloatField()

class Product(models.Model):
  sale = models.ForeignKey(Sale, null=True, blank=True)
  price = models.FloatField()

Необходимо отфильтровать товары по диапазону цены. При этом на цену может влиять скидка, т.е. если фильтруем по цене от $10 до $80, то товар ценой в $120 с 50% скидкой должен попасть в выборку, тк со скидкой он будет стоить $60.
products = Product.objects.filter(price__range=(10, 80))

не подходит, т.к. не учитывает скидку.

products = Product.objects.annotate(price_with_sale=F('price')*(1-F('sale__percent')))
products = products.filter(price_with_sale__range=(10, 80))

не подходит т.к. не у всех товаров есть скидка.

Вариант создать по умолчанию для товаров нулевую скидку не рассматривается.
Есть вариант разбить выборку на две: товары со скидкой и товары без скидки. Первую по одному отфильтровать, а вторую по другому. И в конце их объединить. Это два дополнительных запроса. Он мне не кажется оптимальным.
def filter_prices(self, products, price_from, price_to):
  products1 = Product.objects.filter(sale__isnull=True)
  products1 = products1.filter(price__range=(price_from, price_to))

  products2 = Product.objects.filter(sale__isnull=False)
  products2 = products2.annotate(new_price=F('price')*(1-F('sale__percent')))
  products2 = products2.filter(new_price__range=(price_from, price_to))

  ids = list(chain(products1.values_list('id', flat=True), products2.values_list('id', flat=True)))
  return products.filter(id__in=ids)


Откровенно говоря я даже не представляю как такое можно сделать sql запросом, следовательно и orm тут ничего не сделает.

Я хочу невозможного или упускаю какой то элементарный способ из виду?
  • Вопрос задан
  • 183 просмотра
Решения вопроса 1
Product.objects.annotate(
    price_with_sale=F('price')*(1-F('sale__percent'))
).filter(
    Q(price__range=(10, 80)) | Q(price_with_sale__range=(10, 80))
)


всё должно нормально выбраться
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы