@KaraBaras

Как в Django ORM сделать несколько join к одной и той же таблице?

Есть такие модели:
class Goods(models.Model):
     art = models.CharField(max_length=20, blank=True, null=True)
     name = models.CharField(max_length=255, blank=True, null=True)

class PriceGroups(models.Model):
    name = models.CharField(max_length=255, blank=True, null=True)

class Price(models.Model):
   price = models.DecimalField(max_digits=10, decimal_places=2)
   goods = models.ForeignKey(Goods, on_delete=models.CASCADE,)
   pricegroup = models.ForeignKey(PriceGroups, on_delete=models.CASCADE)


Соответственно в таблице Price записи выглядят следующим образом:

|--------|--------------|-----|
|Goods_id|PriceGroups_id|Price|
|--------|--------------|-----|
|    1   |       1      |  20 |
|--------|--------------|-----|
|    1   |       2      |  25 |
|--------|--------------|-----|
|    1   |       3      |  30 |
|--------|--------------|-----|
|    2   |       1      |  40 |
|--------|--------------|-----|
|    2   |       2      |  45 |
|--------|--------------|-----|
|    2   |       3      |  50 |
|--------|--------------|-----|


Мне нужно получить результат выборки в виде:
|--------|----------------------|----------------------|----------------------|
|Goods_id|PriceGroups_id 1 price|PriceGroups_id 2 price|PriceGroups_id 3 price|
|--------|----------------------|----------------------|----------------------|
|    1   |           20         |           25         |          30          |
|--------|----------------------|----------------------|----------------------|
|    2   |           40         |           45         |          50          |
|--------|----------------------|----------------------|----------------------|


Используя objects.raw и запрос:
SELECT goods.id, PriceGroups_id_1.price, riceGroups_id_2.price, 
    PriceGroups_id_3.price
    FROM price_goods AS goods 
    LEFT JOIN price_price AS PriceGroups_id_1 ON goods.id = PriceGroups_id_1.goods_id
    LEFT JOIN price_price AS PriceGroups_id_2 ON goods.id = PriceGroups_id_2.goods_id
    LEFT JOIN price_price AS PriceGroups_id_3 ON goods.id = PriceGroups_id_3.goods_id
    WHERE PriceGroups_id_1.pricegroup_id = 1
    AND PriceGroups_id_2.pricegroup_id = 2
    AND PriceGroups_id_3.pricegroup_id = 3

Я получаю нужный результат.
Можно ли как-то сделать это с помощью ORM?
Для одного вида цены получается через такой код:
Goods.objects.filter(price__pricegroup_id = 1).annotate(PriceGroups_id_1_price = F('price__price'))

Но как это сделать для нескольких видов цен не понимаю.
  • Вопрос задан
  • 176 просмотров
Решения вопроса 1
@gimntut
Если использовать много join, то запрос становится медленным. Гораздо быстрее работают подзапросы, особенно в сочетании с пагинацией.
Goods.objects.annotate(
    PriceGroups_id_1_price = Subquery(Price.objects.filter(goods_id=OuterRef('id'), pricegroup_id=1).values('price')[:1]),
    PriceGroups_id_2_price = Subquery(Price.objects.filter(goods_id=OuterRef('id'), pricegroup_id=2).values('price')[:1]),
    PriceGroups_id_3_price = Subquery(Price.objects.filter(goods_id=OuterRef('id'), pricegroup_id=3).values('price')[:1]),
)


В целом архитектура БД кажется странной, т.к. если групп всего и всегда три, то лучше цены хранить в отдельных полях модели Goods.
Если количество групп меняется, то кроме проблемы с которой уже столкнулись, будет ещё множество проблем и не факт, что можно будет найти их решение.

То же самое решение, но для переменного числа групп:
group_count = 3
subquery = Subquery(Price.objects.filter(goods_id=OuterRef('id')))
qs = Goods.objects.annotate(
    **{
        f'PriceGroups_id_{group_id}_price': subquery.filter(pricegroup_id=group_id).values(
            'price')[:1]
        for group_id in range(1, group_count + 1)
    }
)
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

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

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