Optymalne pobieranie losowych wpisów z bazy danych w Django ORM

Data publikacji: 2012-08-27 | Tagi:

Dokumentacja Django mówi, że aby uzyskać losowy wiersz z bazy danych należy użyć metody:

Entry.objects.order_by('?')[0]
Jednakże zaznacza, że w niektórych silnikach baz danych takie zapytanie może być powolne i zasobożerne. Do tego typu silników należy niestety chyba najpopularniejszy z nich czyli MySQL.

Jak zatem pobierać losowe wpisy w sposób oszczędny i bez ryzykowania znacznym spadkiem wydajności?

Sposobów jest zapewne kilka - mniej lub bardziej spełniających zasady losowania wpisu. Mi udało się odnaleźć chyba najlepszy z nich: Każde zapytanie o losowy rekord należy rozbić na dwa podzapytania:
count = Entry.objects.aggregate(count=models.Count('id'))['count']
random_index = random.randint(0, count - 1)
Entry.objects.all()[random_index]
Taka konstrukcja najpierw zlicza wszystkie dostępne wiersze, następnie przy pomocy modułu random losuje liczbę z zakresu 0, ilość wierszy zmniejszona o jeden. Tak wylosowany indeks służy do pobrania dokładnie jednego, losowego wpisu. Zmiana konstrukcji zapytań powoduje, że nie jest używana funkcja RAND() MySQL. Zamiast niej używamy szybkiego COUNT() w pierwszym zapytaniu i równie szybkiego LIMIT 1 OFFSET x w drugim zapytaniu. Warto tę konstrukcję upakować do menedżera, by mieć ją zawsze pod ręką, np. w taki sposób:
# -*- coding: utf-8 -*-
from django.db import models
import random


class EntryManager(models.Manager):

    def random(self):
        count = self.get_query_set().aggregate(count=models.Count('id'))['count']
        random_index = random.randint(0, count - 1)
        return self.get_query_set()[random_index]
Oczywiście zamiast get_query_set() może być również inna metoda oznaczająca np. opublikowane wpisy itp. Wszystko zależy od inwencji.


Oceń ten post:
Podziel się:

comments powered by Disqus

IT w obrazkach: