Pomocí prefetch_related a agregací, aby se zabránilo n+1 problém s Django databáze dotazů pro model časové řady údajů

0

Otázka

Já se snažím, aby se zabránilo obscénní množství databázových dotazů v Django aplikace. V aplikaci jsem monotoring řadu návrhů (vzor: Návrh), které mohou být hlasoval pro (model: Hlasování).

Hlasování modelu neukládá každé jednotlivé hlasování. Místo toho celkový počet hlasů pro návrh jsou uloženy v pravidelných intervalech. Návrh "Lepší zmrzlinu," mohl "10 hlasů v 8:10", "12 hlasů v 8:20", "25 hlasů v 8:30", atd.

Vytvořil jsem velmi ineffecient smyčky s velkou n+1 otázek pro výpočet počet nových hlasů za den za návrh.

Hledám účinnější (asi jeden) queryset, než ty současné pro stejné funkce. Vím, že bych měla asi vytvořit nějakou anotaci dat hlasů na "návrhy" v views.py a pak komentovat, že tím, že moje agregované funkce, která počítá počet hlasů na každý den, ale nemůžu přijít na to, jak se vlastně řetězce dohromady.

Tady je můj současný pracovní, ale velmi neefektivní kód:

models.py:

class Suggestion(models.Model):
    unique_id = models.CharField(max_length=10, unique=True)
    title = models.CharField(max_length=500)
    suggested_date = models.DateField()
​
class Vote(models.Model):
    suggestion = models.ForeignKey('Suggestion', on_delete=models.CASCADE)
    timestamp = models.DateTimeField()
    votes = models.IntegerField()

views.py:

def index(request):
    # Proces votes per day per suggestion
    suggestions = Suggestion.objects.prefetch_related('vote_set')
    votes_per_day_per_suggestion = {}
    for suggestion in suggestions:
        votes_per_day_per_suggestion[suggestion.title] = {}
        votes = suggestion.vote_set
        suggestion_dates = votes.dates('timestamp', 'day') # n+1 issue
        for date in suggestion_dates:
            date_min_max = votes.filter(timestamp__date=date).aggregate(votes_on_date=(Max('votes') - Min('votes'))) # n+1 issue
            votes_per_day_per_suggestion[suggestion.title][date] = date_min_max['votes_on_date']
    context['votes_per_day_per_suggestion'] = votes_per_day_per_suggestion
    return render(request, 'borgerforslag/index.html', context)

Šablona výstup:

Better toilet paper (number of votes per day):
19. october 2021: 23
20. october 2021: 19
21. october 2021: 18
22. october 2021: 9
23. october 2021: 25
24. october 2021: 34
25. october 2021: 216
2

Nejlepší odpověď

1

Následující by měly dostat všechny návrhy, data a součet hlasů v hodnoty queryset

from django.db.models import Max, Min
from django.db.models.functions import TruncDate


def index(request):
    suggestions = Suggestion.objects.annotate(
        date=TruncDate('vote__timestamp')
    ).order_by(
        'id', 'date'
    ).annotate(
        sum=Max('vote__votes') - Min('vote__votes')
    )
    return render(request, 'borgerforslag/index.html', {'suggestions': suggestions})

Pak v šabloně použít seskupit do skupiny všechny ty výsledky, které návrh

{% regroup suggestions by title as suggestions_grouped %}

<ul>
{% for suggestion in suggestions_grouped %}
    <li>{{ suggestion.grouper }}
    <ul>
        {% for date in suggestion.list %}
          <li>{{ date.date }}: {{ date.sum }}</li>
        {% endfor %}
    </ul>
    </li>
{% endfor %}
</ul>
2021-11-13 16:50:35

Díky, to mě dostal. Navrhl jsem úpravu tak, aby odrážely, že nejsem šetří počet přidaných hlasů na časové razítko v Hlasování modelu, ale místo toho se celkový počet hlasů v době, časové razítko, takže místo toho, Sum('hlasovat__hlasů'), Max (hlasování__hlasů') - Min (hlasování__hlasů') by měla být vypočtena.
Morten

@Morten Ah, Hlasování obsahuje průběžný součet hlasů nebo snímek v čase počtu hlasů?
Iain Shelvington

Snímek v době celkový počet hlasů pro návrh. Pokud hlasování v 8:10 je 10, 10 lidí v celkem hlasovali pro návrh. Pokud hlasování v 8:20, 13, 13 osob v celkem hlasovali pro návrh.
Morten
0

Vše, co by měl potřebovat, je values(), annotate()a order_by() chcete-li získat počet hlasů za den za návrh. Tohle by mělo fungovat

Vote.objects.all() \
    .values('timestamp__date', 'suggestion') \
    .annotate(num_votes=Count('votes') \
    .order_by('timestamp__date')

I když, výstup příklad není z počtu hlasů za den za návrh, a zdá se být počet hlasů za den. To může být dosaženo tím, že odstraní návrh z dotazu jako je tento:

Vote.objects.all() \
    .values('timestamp__date') \
    .annotate(num_votes=Count('votes') \
    .order_by('timestamp__date')
2021-11-13 15:19:37

Díky, ale obávám se, že pomocí Počítat nefunguje s údaji model, ale možná, že můj dotaz by měl být přesnější. Hlasování modelu je ukládání celkového počtu hlasů v určitých časových razítek "Lepší zmrzlinu," mohl "10 CELKEM hlasů v 8:10", "CELKEM 12 hlasů v 8:20", "25 CELKEM hlasů v 8:30", atd.
Morten

V jiných jazycích

Tato stránka je v jiných jazycích

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................