Как правильно включить столбцы в предложение GROUP BY моего совокупного запроса Django? - PullRequest
1 голос
/ 07 января 2020

Я использую Django и Python 3.7. У меня есть следующая модель ...

class ArticleStat(models.Model):
    objects = ArticleStatManager()
    article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='articlestats')
    elapsed_time_in_seconds = models.IntegerField(default=0, null=False)
    score = models.FloatField(default=0, null=False)

Я хочу написать запрос MAX / GROUP BY при соблюдении определенных условий. В частности, я хочу, чтобы каждая строка содержала

MAX(ArticleStat.elapsed_time_in_seconds)
ArticleStat.Article.id
ArticleStat.Article.title
ArticleStat.score

, в которой столбцы "ArticleStat.Article.id", "ArticleStat.Article.title," и "ArticleStat.score" являются уникальными для каждой строки набора результатов. Итак, я попробовал это ...

def get_current_articles(self, article):
    qset = ArticleStat.objects.values('article__id', 'article__title', 'score').filter(
        article__article=article).values('elapsed_time_in_seconds').annotate(\
        max_date=Max('elapsed_time_in_seconds'))
    print(qset.query)
    return qset

Однако полученный SQL не включает значения, которые я хочу использовать в моем предложении GROUP BY (обратите внимание, что ни «article», ни «score» в GROUP BY) ...

SELECT "myproject_articlestat"."elapsed_time_in_seconds", 
MAX("myproject_articlestat"."elapsed_time_in_seconds") AS "max_date" 
FROM "myproject_articlestat" 
INNER JOIN "myproject_article" ON ("myproject_articlestat"."article_id" = "myproject_article"."id") 
WHERE ("myproject_article"."article_id" = 2) GROUP BY "myproject_articlestat"."elapsed_time_in_seconds"

Как мне изменить мой Django запрос, чтобы сгенерировать SQL в соответствии с тем, что я хочу?

Ответы [ 4 ]

0 голосов
/ 10 января 2020

Я не думаю, что ответ @Oleg будет работать, но он близок ..

Выражение подзапроса можно использовать для выбора одного значения из другого набора запросов. Для достижения sh этого вы сортируете по значению wi sh для цели, затем выбираете первое значение.

sq = (
    ArticleStat.objects
    .filter(article=OuterRef('pk'))
    .order_by('-elapsed_time_in_seconds')
)
articles = (
    Article.objects
    .annotate(
        max_date=Subquery(sq.values('elapsed_time_in_seconds')[:1]),
        score=Subquery(sq.values('score')[:1]),
    )
    # .values('id', 'path', 'title', 'score', 'max_date')
)
0 голосов
/ 09 января 2020

Насколько я вижу, в Django.

нет одношагового способа сделать это. Один из способов сделать это - 2 запроса и использование .annotate () для добавления максимальный идентификатор связанной версии объекта для каждой ревизии, затем получите все эти наблюдения объекта

Пример:

objects = Object.objects.all().annotate(revision_id=Max
('objectrevision__id'))
objectrevisions = ObjectRevision.objects.filter(id__in=
[o.revision_id for o in objects])

Это не проверено, а также немного медленно, так что, может быть, вы также можете попробовать написать пользовательский SQL, как упомянул Вольфрам Крисинг в блоге здесь

0 голосов
/ 10 января 2020

Если я правильно понял из всех комментариев:

В результате получается Articles ( id, path, title ), отфильтрованный по аргументу article метода get_current_articles с дополнительными данными - максимум elapsed_time_in_seconds от всех ArticleStats каждой отфильтрованной статьи, а также score ее ArticleStat с максимумом elapsed_time.

Если это так, когда базовый запрос может быть включен Article модель: Article.objects.filter(article=article).

Который мы можем аннотировать с помощью Max() соответствующего ArticleStats. Это можно сделать непосредственно по основному запросу .annotate(max_date=Max(articlestats__elapsed_time_in_seconds)) или с помощью Подзапрос для ArticleStat, также отфильтрованный так же, как базовый запрос к статье (мы хотим, чтобы подзапрос выполнялся на том же наборе объектов статьи, что и основной запрос), то есть

max_sq = ArticleStat.objects.filter(
    article__article=OuterRef('article')
).annotate(max_date=Max('elapsed_time_in_seconds'))

Теперь, чтобы добавить score столбец к результату. Max() является агрегатной функцией и не имеет информации о строке. Чтобы получить score для максимума elapsed_time, мы можем сделать еще один подзапрос и отфильтровать его по max elapsed_time из предыдущего столбца.

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

score_sq = ArticleStat.objects.filter(
    article__article=OuterRef('article'),
    elapsed_time_in_seconds=OuterRef('max_date')
)

И использовать наши подзапросы в основном запросе

qset = Article.objects.filter(
    article=article
).annotate(
    max_date=Max('articlestats__elapsed_time_in_seconds'),
""" or
    max_date=Subquery(
        max_sq.values('max_date')[:1]
    ),
"""
    score=Subquery(
        score_sq.values('score')[:1]
    )
).values(
    'id', 'path', 'title', 'score', 'max_date'
)

И несколько хитрый вариант без использования функции Max(), но эмуляция с ORDER BY и извлечение первого ряда.

artstat_sq = ArticleStat.objects.filter(
    article__article=OuterRef('article')
).order_by().order_by('-elapsed_time_in_seconds', '-score')
# empty order_by() to clear any base ordering

qset = Article.objects.filter(
    article=article
).annotate(
    max_date=Subquery(
        artstat_sq.values('elapsed_time_in_seconds')[:1]
    ),
    score=Subquery(
        artstat_sq.values('score')[:1]
    )
).values(
    'id', 'path', 'title', 'score', 'max_date'
)
0 голосов
/ 07 января 2020

Вы не должны использовать 'elapsed_time_in_seconds' в своем предложении .values(..) и добавлять свойство GROUP BY в предложении .order_by(..):

qset = ArticleStat.objects.filter(
    article__article=article
).values(
    <b>'article__path', 'article__title', 'score'</b>
).annotate(
    max_date=Max('elapsed_time_in_seconds')
).order_by(<b>'article__path', 'article__title', 'score'</b>)

Таким образом, вы получите QuerySet словарей так что каждый словарь содержит четыре элемента: 'article__path', 'article__title', 'score' и 'max_date'.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...