web-dev-qa-db-ja.com

DjangoのORMを使用してランダムレコードを取得する方法は?

私のサイトにある絵画を表すモデルがあります。メインのWebページでは、そのうちのいくつかを表示したいと思います。最新のもの、ほとんどの時間アクセスされなかったもの、最も人気のあるもの、ランダムなものです。

Django 1.0.2を使用しています。

最初の3つはDjangoモデルを使用して簡単にプルできますが、最後の1つ(ランダム)は問題を引き起こします。私はこれを私の意見で次のようなものにコーディングすることができます:

number_of_records = models.Painting.objects.count()
random_index = int(random.random()*number_of_records)+1
random_Paint = models.Painting.get(pk = random_index)

私の見解で私が持ちたいもののようには見えません-これは完全にデータベース抽象化の一部であり、モデルにあるべきです。また、ここでは、削除されたレコード(すべてのレコードの数が考えられるすべてのキー値をカバーするわけではありません)の世話をする必要があります。

私がそれを行うことができる他のオプション、できれば何らかの形でモデルの抽象化の中で?

166
kender

order_by('?')を使用すると、実稼働の2日目にdbサーバーが強制終了されます。より良い方法は、 リレーショナルデータベースからランダムな行を取得する で説明されているようなものです。

from Django.db.models.aggregates import Count
from random import randint

class PaintingManager(models.Manager):
    def random(self):
        count = self.aggregate(count=Count('id'))['count']
        random_index = randint(0, count - 1)
        return self.all()[random_index]
156
Emil Ivanov

単に使用する:

MyModel.objects.order_by('?').first()

QuerySet API で文書化されています。

240
muhuk

MySQLを使用する場合、order_by( '?')[:N]を使用したソリューションは、中規模のテーブルでも非常に遅くなります(他のデータベースについては知りません)。

order_by('?')[:N]SELECT ... FROM ... WHERE ... ORDER BY Rand() LIMIT Nクエリに変換されます。

つまり、テーブル内のすべての行に対してRand()関数が実行され、この関数の値に従ってテーブル全体がソートされ、最初のNレコードが返されます。テーブルが小さい場合、これで問題ありません。しかし、ほとんどの場合、これは非常に遅いクエリです。

Idに穴がある場合でも機能する単純な関数を作成しました(一部の行は削除されています):

def get_random_item(model, max_id=None):
    if max_id is None:
        max_id = model.objects.aggregate(Max('id')).values()[0]
    min_id = math.ceil(max_id*random.random())
    return model.objects.filter(id__gte=min_id)[0]

ほとんどの場合、order_by( '?')よりも高速です。

26
Mikhail Korobov

簡単な解決策は次のとおりです。

from random import randint

count = Model.objects.count()
random_object = Model.objects.all()[randint(0, count - 1)] #single random object
10
Maulik Patel

モデルに manager を作成して、このようなことを行うことができます。マネージャーが何であるかを最初に理解するために、Painting.objectsメソッドは、all()filter()get()などを含むマネージャーです。独自のマネージャーを作成すると、事前にフィルターをかけることができます独自のカスタムメソッドと同様に、これらすべてのメソッドが結果に作用します。

EDITorder_by['?']メソッドを反映するようにコードを変更しました。マネージャーは、無制限の数のランダムモデルを返すことに注意してください。このため、単一のモデルを取得する方法を示すために、使用コードを少し含めました。

from Django.db import models

class RandomManager(models.Manager):
    def get_query_set(self):
        return super(RandomManager, self).get_query_set().order_by('?')

class Painting(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

    objects = models.Manager() # The default manager.
    randoms = RandomManager() # The random-specific manager.

使用法

random_painting = Painting.randoms.all()[0]

最後に、モデルに多くのマネージャーを置くことができるので、LeastViewsManager()またはMostPopularManager()を自由に作成してください。

10
Soviut

他の回答は、潜在的に遅い(order_by('?')を使用)か、複数のSQLクエリを使用しています。順序付けを行わず、クエリを1つだけ使用したサンプルソリューションを次に示します(Postgresを想定)。

Model.objects.raw('''
    select * from {0} limit 1
    offset floor(random() * (select count(*) from {0}))
'''.format(Model._meta.db_table))[0]

テーブルが空の場合、これによりインデックスエラーが発生することに注意してください。モデルに依存しないヘルパー関数を作成して、それを確認します。

5
Nelo Mitranim

モデルマネージャーを作成しました

models.py(例)

from Django.db import models

class RandomManager(models.Manager):

  def get_random(self, items=1):
    '''
    items is integer value
    By default it returns 1 random item
    '''
    if isinstance(items, int):
        return self.model.objects.order_by('?')[:items]
    return self.all()


class Category(models.Model):
  name = models.CharField(max_length=100)

  objects = RandomManager()

  class Meta:
    default_related_name = 'categories'
    verbose_name = 'category'
    verbose_name_plural = 'categories'

そして、例えばデータベースからランダムなアイテムを取得できます

Category.objects.get_random(5) #  To get 5 random items 

私は非常に簡単な解決策を得ました、カスタムマネージャーを作ります:

class RandomManager(models.Manager):
    def random(self):
        return random.choice(self.all())

そして、モデルを追加します:

class Example(models.Model):
    name = models.CharField(max_length=128)
    objects = RandomManager()

これで使用できます:

Example.objects.random()
2
LagRange

私がそれを行う方法だけの簡単なアイデア:

def _get_random_service(self, professional):
    services = Service.objects.filter(professional=professional)
    i = randint(0, services.count()-1)
    return services[i]
2
Valter Silva

これは非常にお勧めです リレーショナルデータベースからランダムな行を取得する

Django ormを使用してそのようなことを行うと、ビッグデータテーブルがある場合、dbサーバーが特別に怒ります:|

そして、ソリューションはモデルマネージャーを提供し、SQLクエリを手動で記述します;)

更新

カスタムModelManagerを記述せずに、rel以外のデータベースバックエンドでも機能する別のソリューション。 Djangoのクエリセットからランダムオブジェクトを取得する

1
Alireza Savand

(かなり一般的な)特殊なケースに注意するために、削除されていないインデックス付き自動インクリメント列がテーブルにある場合、ランダム選択を行う最適な方法は次のようなクエリです:

SELECT * FROM table WHERE id = Rand() LIMIT 1

テーブルのidという名前の列を想定しています。 Djangoでは、次の方法でこれを実行できます。

Painting.objects.raw('SELECT * FROM appname_painting WHERE id = Rand() LIMIT 1')

appnameをアプリケーション名に置き換える必要があります。

一般に、id列を使用すると、order_by( '?')は次のように高速に実行できます。

Paiting.objects.raw(
        'SELECT * FROM auth_user WHERE id>=Rand() * (SELECT MAX(id) FROM auth_user) LIMIT %d' 
    % needed_count)
1
Amir Ali Akbari

これへのより簡単なアプローチの1つは、目的のレコードセットまで単純にフィルタリングし、random.sampleを使用して必要な数だけ選択することです。

from myapp.models import MyModel
import random

my_queryset = MyModel.objects.filter(criteria=True)  # Returns a QuerySet
my_object = random.sample(my_queryset, 1)  # get a single random element from my_queryset
my_objects = random.sample(my_queryset, 5)  # get five random elements from my_queryset

my_querysetが空でないことを確認するためのコードを用意する必要があることに注意してください。最初の引数に含まれる要素が少なすぎる場合、random.sampleValueError: sample larger than populationを返します。

1
eykanal

こんにちは、私は報告する必要がある長さのクエリセットからランダムなレコードを選択する必要がありました(つまり、Webページは説明されたアイテムを作成し、レコードは残っていました)

q = Entity.objects.filter(attribute_value='this or that')
item_count = q.count()
random_item = q[random.randomint(1,item_count+1)]

次の半分の長さ(0.7秒と1.7秒)でした。

item_count = q.count()
random_item = random.choice(q)

ランダムなエントリを選択する前にクエリ全体をプルダウンすることを避け、ユーザーがitem_countのカウントダウンを確認したい繰り返しタスクのために繰り返しアクセスされるページに対してシステムが十分に反応するようにします。

1
pjmnoble

特に、複数のアイテムをサンプリングしてサンプルセットを作成する場合は、イテレータのサンプリングに使用する 同じアプローチ を使用できます。 @MatijnPietersと@DzinXは、これについて多くの考えを入れました。

def random_sampling(qs, N=1):
    """Sample any iterable (like a Django QuerySet) to retrieve N random elements

    Arguments:
      qs (iterable): Any iterable (like a Django QuerySet)
      N (int): Number of samples to retrieve at random from the iterable

    References:
      @DZinX:  https://stackoverflow.com/a/12583436/623735
      @MartinPieters: https://stackoverflow.com/a/12581484/623735
    """
    samples = []
    iterator = iter(qs)
    # Get the first `N` elements and put them in your results list to preallocate memory
    try:
        for _ in xrange(N):
            samples.append(iterator.next())
    except StopIteration:
        raise ValueError("N, the number of reuested samples, is larger than the length of the iterable.")
    random.shuffle(samples)  # Randomize your list of N objects
    # Now replace each element by a truly random sample
    for i, v in enumerate(qs, N):
        r = random.randint(0, i)
        if r < N:
            samples[r] = v  # at a decreasing rate, replace random items
    return samples
1
hobs

削除なしで主キーを自動インクリメントする方法

主キーがギャップのない連続した整数であるテーブルがある場合、次の方法が機能するはずです。

import random
max_id = MyModel.objects.last().id
random_id = random.randint(0, max_id)
random_obj = MyModel.objects.get(pk=random_id)

このメソッドは、テーブルのすべての行を反復処理する他のメソッドよりもはるかに効率的です。 2つのデータベースクエリが必要ですが、どちらも簡単です。さらに、それは単純であり、追加のクラスを定義する必要はありません。ただし、適用可能なのは、IDのシーケンスにギャップがないように、行が削除されていない自動インクリメントの主キーを持つテーブルに限定されます。

ギャップがあるように行が削除された場合、このメソッドは、既存の主キーがランダムに選択されるまで再試行されると機能します。

参照資料

0