Как сделать обратный внешний ключ в нескольких таблицах с аннотацией - PullRequest
1 голос
/ 18 февраля 2020

У меня есть 3 модели, подключенные к одной по внешнему ключу:

class A(models.Model):
    name = models.CharField(max_length=20)

class B(models.Model):
    a = models.ForeignKey(A, on_delete=models.CASCADE)
    amount_b = models.FloatField()

class C(models.Model):
    a = models.ForeignKey(A, on_delete=models.CASCADE)
    amount_c = models.FloatField()

class D(models.Model):
    a = models.ForeignKey(A, on_delete=models.CASCADE)
    amount_d = models.FloatField()

И я хочу сделать запрос JOIN на основе внешних ключей, используя Django ORM, чтобы получить сумму каждой суммы.

В SQL я бы сделал что-то вроде:

SELECT 
  a.name, 
  b_sum,
  c_sum,
  d_sum
FROM A AS a
  LEFT JOIN (SELECT b.a, sum(amount_b) as b_sum
              from B as b
              group by b.a) as AB AB.a = a.id
  LEFT JOIN (SELECT c.a, sum(amount_c) as c_sum
              from C as c
              group by a) AS AC ON AC.a = a.id
  LEFT JOIN (SELECT d.a, sum(amount_d) as d_sum
              from D as d
              group by a) AS AD ON AD.a = a.id
WHERE a.name LIKE 'init%'

Но я не нашел ничего подобного в django ORM.

Ответы [ 3 ]

4 голосов
/ 20 февраля 2020

Вы можете сделать это

q = A.objects.annotate(
    b_sum=Subquery(
        B.objects.filter(a=OuterRef('pk'))
                 .values('a')
                 .annotate(b_sum=Sum('amount_b'))
                 .values('b_sum')
    ),
    c_sum=Subquery(
        C.objects.filter(a=OuterRef('pk'))
                 .values('a')
                 .annotate(c_sum=Sum('amount_c'))
                 .values('c_sum')
    ),
    d_sum=Subquery(
        D.objects.filter(a=OuterRef('pk'))
                 .values('a')
                 .annotate(d_sum=Sum('amount_d'))
                 .values('d_sum')
    )
)

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

Вот sql генерирует.

SELECT "A"."id",
       "A"."name",
       (SELECT SUM(U0."amount_b") AS "b_sum" FROM "B" U0 WHERE U0."a_id" = ("A"."id") GROUP BY U0."a_id") AS "b_sum",
       (SELECT SUM(U0."amount_c") AS "c_sum" FROM "C" U0 WHERE U0."a_id" = ("A"."id") GROUP BY U0."a_id") AS "c_sum",
       (SELECT SUM(U0."amount_d") AS "d_sum" FROM "D" U0 WHERE U0."a_id" = ("A"."id") GROUP BY U0."a_id") AS "d_sum"
FROM "A"
0 голосов
/ 26 февраля 2020

Если вы обнаружите, что ваш запрос сложен для DJango ORM, то есть способы напрямую выполнить необработанные запросы.

>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):
...     print(p)
John Smith
Jane Jones

Этот фрагмент кода взят с самого сайта DJango. Полный URL-адрес выглядит следующим образом.

https://docs.djangoproject.com/en/3.0/topics/db/sql/

Приведенная ссылка также содержит пользовательские параметры запроса и курсоры и т. Д. c, которые помогут вам решить вашу проблему.

0 голосов
/ 18 февраля 2020

B.amount_b и D.amount_d - это CharField с, выполнение сумм в этих полях может вызвать проблемы, если они не содержат числовых значений c. Вам следует подумать о том, чтобы поменять их на числовые c поля

. Что-то вроде следующего даст вам набор запросов объектов A, помеченных этими суммами

queryset = A.objects.annotate(
    b_sum=Sum('b__amount_b'),  # It's either "b" or "b_set"
    c_sum=Sum('c__amount_c'),
    d_sum=Sum('c__amount_d'),
)
for a in queryset:
    print(a.name, a.b_sum, a.c_sum, a.d_sum)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...