Django аннотация для составных sh первичный ключ с фильтром, игнорирующим изменение первичного ключа в слишком большом количестве аннотированных элементов - PullRequest
0 голосов
/ 26 мая 2020

См. Также EDIT1 ниже.

Использование Django 3.0.6 и python3 .8, учитывая следующие модели

class Plants(models.Model):
    plantid = models.TextField(primary_key=True, unique=True)

class Pollutions(models.Model):
    pollutionsid = models.IntegerField(unique=True, primary_key=True)
    year = models.IntegerField()
    plantid = models.ForeignKey(Plants, models.DO_NOTHING, db_column='plantid')
    pollutant = models.TextField()
    releasesto = models.TextField(blank=True, null=True)
    amount = models.FloatField(db_column="amount", blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'pollutions'
        unique_together = (('plantid', 'releasesto', 'pollutant', 'year'))

class Monthp(models.Model):
    monthpid = models.IntegerField(unique=True, primary_key=True)
    year = models.IntegerField()
    month = models.IntegerField()
    plantid = models.ForeignKey(Plants, models.DO_NOTHING, db_column='plantid')
    power = models.IntegerField(null=False)

    class Meta:
        managed = False
        db_table = 'monthp'
        unique_together = ('plantid', 'year', 'month')

Я хотел бы добавить аннотацию - на основе отношения внешнего ключа и подходящего значения, в частности - для каждого завода количество СО2 и Сумма его мощности за данный год. Для отладки заменив Sum на Count с помощью следующего запроса:

annotated = tmp.all().annotate(
energy=Count('monthp__power', filter=Q(monthp__year=YEAR)),
co2=Count('pollutions__amount', filter=Q(pollutions__year=YEAR, pollutions__pollutant="CO2", pollutions__releasesto="Air")))

Однако это возвращает слишком много элементов (неправильное число с помощью Sum, соответственно)

annotated.first().co2 # 60, but it should be 1
annotated.first().energy # 252, but it should be 1

хотя моя база данных гарантии - как указано, что (плантация, год, месяц) и (плантация, выбросы, загрязнитель, год) уникальны вместе, что можно легко продемонстрировать:

pl = annotated.first().plantid
testplant = Plants.objects.get(pk=pl) # plant object
pco2 = Pollutions.objects.filter(plantid=testplant, year=YEAR, pollutant="CO2", releasesto="Air")
len(pco2) # 1, as expected

Почему django возвращается ко многим результатов и как я могу указать django ограничить аннотируемые элементы «текущим первичным ключом», другими словами, аннотировать только те элементы, внешний ключ которых соответствует первичному ключу?

Я могу добиться того, что я намереваются сделать, используя отдельные и Max:

energy=Sum('yearly__power', distinct=True, filter=Q(yearly__year=YEAR)),
co2=Max('pollutions__amount', ...

Однако производительность неприемлема.

Я протестировал использование model_to_dict и добавление требуемых значений «вручную» к dict, что работает для самих значений, но не для сортировки полученного словаря (например, по энергии), и он действительно быстрее, чем обходной путь, указанный выше.

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

Это ограничение функции django или мне что-то не хватает?

EDIT1: Поведение известно как ошибка с 11 лет. Даже другие «потратили на это целый день».

Сейчас я пробую это с подзапросами. Однако используемый мной внешний ключ не является первичным ключом его таблицы. Так что "обычный" подход к использованию "pk = ''" не работает. Более ясно, попытка:

tmp = Plants.objects.filter(somefilter)

subq1 = Subquery(Yearly.objects.filter(pk=OuterRef('plantid'), year=YEAR))  tmp1 = tmp.all().annotate(
        energy=Count(Subquery(subq1))
    )

возвращает

OperationalError at /xyz
no such column: U0.yid

Что определенно имеет смысл, потому что Plants понятия не имеет, что такое жид, он знает только плантиды. Как мне настроить подзапрос на это?

...