Django의 ORM을 사용하여 무작위 레코드를 가져 오는 방법은 무엇입니까?


176

내 사이트에 제시 한 그림을 나타내는 모델이 있습니다. 메인 웹 페이지에서 가장 최근에 방문하지 않은 최신 웹 사이트, 가장 인기있는 웹 사이트 및 임의의 웹 사이트 중 일부를 표시하고 싶습니다.

장고 1.0.2를 사용하고 있습니다.

django 모델을 사용하면 처음 3 개를 쉽게 가져올 수 있지만 마지막 모델 (임의)은 문제가됩니다. 내 견해로는 다음과 같이 코드를 작성할 수 있습니다.

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

그것은 내가보기에 갖고 싶은 것과 같지 않습니다-이것은 전적으로 데이터베이스 추상화의 일부이며 모델에 있어야합니다. 또한 여기서는 제거 된 레코드 (모든 레코드 수가 가능한 모든 키 값을 다루지는 않습니다)와 다른 많은 것들을 처리해야합니다.

다른 옵션은 어떻게 할 수 있습니까? 어떻게 모델 추상화 내부에서 가능합니까?


사물을 표시하는 방법과 어떤 사물을 표시하는 것은 MVC의 "컨트롤러"레벨에 들어가야하는 "보기"레벨 또는 비즈니스 로직의 일부입니다.
Gabriele D' Antona

장고에서는 컨트롤러가 뷰입니다. docs.djangoproject.com/en/dev/faq/general/…

답변:


169

를 사용 order_by('?')하면 프로덕션의 두 번째 날에 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]

45
의 장점은 무엇입니까 model.objects.aggregate(count=Count('id'))['count']이상model.objects.all().count()
라이언 색스는

11
허용 된 답변보다 훨씬 낫지 만이 접근법은 두 개의 SQL 쿼리를 만듭니다. 카운트가 사이에 변경되면 범위를 벗어난 오류가 발생할 수 있습니다.
Nelo Mitranim

2
이것은 잘못된 해결책입니다. ID가 0에서 시작하지 않으면 작동하지 않습니다. 또한 ID가 인접하지 않은 경우에는 작동하지 않습니다. 첫 번째 레코드는 500에서 시작하고 마지막 레코드는 599입니다 (연속성을 가정). 그러면 count는 54950이됩니다. queryst의 길이가 100이기 때문에 list [54950]는 존재하지 않습니다. 인덱스를 바운드 예외에서 제외시킵니다. 나는 왜 그렇게 많은 사람들이 이것을 찬성했는지 모른다.
sajid

1
@ 사지 드 : 왜, 당신은 정확히 나에게 묻는가? 이 질문에 대한 내 기여의 총계를 쉽게 알 수 있습니다. 썩은 후에 아카이브를 가리 키도록 링크를 편집합니다. 나는 어떤 대답에도 투표하지 않았습니다. 그러나 나는이 대답과 당신이 훨씬 더 잘 사용한다고 주장하는 것이 .all()[randint(0, count - 1)]실제로 유용하다는 것을 알게 되었습니다. 어쩌면 당신은 우리를 위해 "오류 별"을 재정의하고 어리석은 유권자들에게 고함을 지르기보다는 대답의 어떤 부분이 잘못되었거나 약한지를 식별하는 데 집중해야 할 것입니다. (아마도 사용하지 .objects않습니까?)
Nathan Tuggy

3
@NathanTuggy. 알았어 내 나쁜 미안
sajid

260

간단히 사용하십시오 :

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

QuerySet API에 문서화되어 있습니다.


71
문서화 된 바와
같이이

6
"사용중인 데이터베이스 백엔드에 따라 비싸고 느릴 수 있습니다." -다른 DB 백엔드에 대한 경험이 있습니까? (sqlite / mysql / postgres)?
Kender

4
나는 그것을 테스트하지 않았으므로 이것은 순수한 추측입니다 : 왜 모든 항목을 검색하고 파이썬에서 무작위 화를 수행하는 것보다 느릴까요?
muhuk

8
mysql에는 엄청나게 비효율적 인 임의 순서가 있기 때문에 mysql에서 느리다는 것을 읽었습니다.
브랜든 헨리

33
왜 안돼 random.choice(Model.objects.all())?
Jamey

25

order_by ( '?') [: N] 솔루션은 MySQL을 사용하는 경우 중간 크기 테이블에서도 매우 느립니다 (다른 데이터베이스에 대해 알지 못함).

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 ( '?')보다 빠릅니다.


30
또한 슬프게도 무작위와는 거리가 멀다. id 1의 레코드와 id 100의 레코드가 있으면 두 번째 99 %의 시간을 반환합니다.
DS.

16

간단한 해결책은 다음과 같습니다.

from random import randint

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

10

이런 종류의 작업을 수행하기 위해 모델에 관리자 를 만들 수 있습니다 . 먼저 관리자가 무엇인지 이해하려면 Painting.objects방법이 포함 된 매니저 all(), filter(), get()자신의 매니저를 생성 등이 - 프리 필터 결과와 결과에 대한 모든 같은 방법뿐만 아니라 자신 만의 방법, 작업을 할 수 있습니다 .

편집 : order_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().


3
get ()을 사용하면 pks가 연속적인 경우에만 작동합니다. 즉, 항목을 삭제하지 않습니다. 그렇지 않으면 존재하지 않는 pk를 시도하고 얻을 가능성이 있습니다. .all () [random_index]를 사용하면이 문제가 발생하지 않으며 덜 효율적이지 않습니다.
Daniel Roseman

나는 내 예제가 단순히 질문 코드를 관리자와 복제하는 이유를 이해했습니다. 그의 경계 점검을 수행하는 것은 여전히 ​​OP에 달려 있습니다.
Soviut 2016 년

1
.get (id = random_index)를 사용하는 대신 .filter (id__gte = random_index) [0 : 1]를 사용하는 것이 좋습니다. 첫째, 비 연속 pks로 문제를 해결하는 데 도움이됩니다. 둘째, get_query_set은 QuerySet을 반환해야합니다. 그리고 귀하의 예에서는 그렇지 않습니다.
Nicolas Dumazet

2
한 가지 방법 만 사용하기 위해 새 관리자를 만들지는 않습니다. 임의의 이미지가 필요할 때마다 all () [0] 후프를 거치지 않아도되도록 기본 관리자에 "get_random"을 추가했습니다. 또한 저자가 사용자 모델의 ForeignKey 인 경우 user.painting_set.get_random ()이라고 말할 수 있습니다.
Antti Rasinen 2016 년

나는 무작위 레코드 목록을 얻는 것처럼 담요 작업을 원할 때 일반적으로 새 관리자를 만듭니다. 내가 가진 레코드로 더 구체적인 작업을 수행하는 경우 기본 관리자에서 메소드를 작성합니다.
Soviut 2016 년

6

다른 답변은 잠재적으로 느리거나 (사용 order_by('?')) 두 개 이상의 SQL 쿼리를 사용합니다. 다음은 순서가없고 하나의 쿼리 만있는 샘플 솔루션입니다 (Postgres 가정).

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

테이블이 비어 있으면 인덱스 오류가 발생합니다. 이를 확인하기 위해 모델에 독립적 인 도우미 함수를 작성하십시오.


좋은 개념 증명이지만 데이터베이스 내부에있는 두 개의 쿼리입니다. 데이터베이스에 대한 왕복은 하나입니다. 그럴만한 가치가있는 원시 쿼리를 작성하고 유지하려면이 작업을 여러 번 실행해야합니다. 그리고 빈 테이블을 방지하려면 count()미리 실행 하고 원시 쿼리를 생략하십시오.
Endre Both

2

내가 어떻게하는지 간단한 아이디어 :

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

1

삭제가없는 테이블에 인덱스 된 자동 증분 열이있는 경우 (일반적으로 일반적인) 특수한 경우를 고려하면 무작위 선택을 수행하는 가장 좋은 방법은 다음과 같은 쿼리입니다.

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

이것은 고도의 시스템 권장되는 관계형 데이터베이스에서 임의의 행을 얻기

django orm을 사용하여 이와 같은 작업을 수행하기 때문에 큰 데이터 테이블이 있으면 DB 서버를 특별히 화나게합니다.

솔루션은 Model Manager를 제공하고 손으로 SQL 쿼리를 작성하는 것입니다.)

업데이트 :

custom을 쓰지 않고 비 데이터베이스가 아닌 데이터베이스 백엔드에서도 작동하는 또 다른 솔루션 ModelManager. 장고의 Queryset에서 임의의 객체 얻기


1

특히 여러 항목을 샘플링 하여 샘플 세트 를 작성하려는 경우 반복자를 샘플링하는 데 사용하는 것과 동일한 접근 방식 을 사용할 수 있습니다 . @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

Matijn과 DxinX의 솔루션은 무작위 액세스를 제공하지 않는 데이터 세트를위한 것입니다. 수행하는 데이터 세트 (및 SQL의 경우 OFFSET)는 불필요하게 비효율적입니다.
Endre Both

@EndreBoth 정말. 데이터 소스에 관계없이 동일한 접근 방식을 사용하는 코딩 "효율성"을 좋아합니다. 때로는 데이터 샘플링 효율성이 다른 프로세스 (ML 교육과 같이 실제로 데이터로 수행하는 작업)에 의해 제한되는 파이프 라인의 성능에 큰 영향을 미치지 않습니다.
호브

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.sample반환 ValueError: sample larger than population합니다.


2
이로 인해 전체 쿼리 세트가 검색됩니까?
perrohunter

@perrohunter 작동하지 않습니다 Queryset(적어도 Python 3.7 및 Django 2.1에서는 작동하지 않음 ). 먼저 목록으로 변환해야하며, 이는 분명히 전체 쿼리 세트를 검색합니다.
Endre Both

@EndreBoth-2016 년에 작성되었지만 둘 중 어느 것도 존재하지 않았습니다.
eykanal

그래서 버전 정보를 추가했습니다. 그러나 2016 년에 효과가 있었다면 전체 쿼리 세트를 목록으로 가져 와서 그렇게 했습니까?
Endre Both

@EndreBoth 맞습니다.
eykanal

1

안녕하세요,보고해야 할 길이의 쿼리 세트에서 임의의 레코드를 선택해야했습니다 (예 : 웹 페이지에서 설명 된 항목을 생성하고 해당 레코드를 남겼습니다)

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 카운트 다운을보고 싶은 반복적 인 작업을 위해 반복적으로 액세스되는 페이지에 대해 시스템이 충분히 반응하도록 만들었습니다.


0

삭제하지 않고 기본 키를 자동 증가시키는 방법

기본 키가 공백이없는 순차 정수인 테이블이있는 경우 다음 방법이 작동합니다.

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

이 방법은 테이블의 모든 행을 반복하는 다른 방법보다 훨씬 효율적입니다. 두 개의 데이터베이스 쿼리가 필요하지만 둘 다 사소합니다. 또한 간단하고 추가 클래스를 정의 할 필요가 없습니다. 그러나 적용 가능성은 행이 삭제되지 않은 자동 증가 기본 키가있는 테이블로 제한되므로 ID 시퀀스에 간격이 없습니다.

간격과 같이 행이 삭제 된 경우 기존 기본 키가 임의로 선택 될 때까지이 방법을 다시 시도하면이 방법이 계속 작동 할 수 있습니다.

참고 문헌


0

나는 매우 간단한 해결책을 얻었고 맞춤 관리자를 만들었습니다.

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()

무작위 수입 선택에서
Adam Starrh

3
속도를 원하면이 방법을 사용하지 마십시오. 이 솔루션은 매우 느립니다. 확인했습니다. order_by('?').first()60 배 이상 느립니다 .
LagRange

@ Alex78191 아니오, "?" 나쁘지만 내 방법은 매우 느립니다. 최고 답변 솔루션을 사용했습니다.
LagRange
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.