Làm cách nào để lấy bản ghi ngẫu nhiên bằng ORM của Django?


176

Tôi có một mô hình đại diện cho các bức tranh tôi trình bày trên trang web của tôi. Trên trang web chính tôi muốn hiển thị một số trong số họ: mới nhất, một trang không được truy cập trong hầu hết thời gian, phổ biến nhất và ngẫu nhiên.

Tôi đang sử dụng Django 1.0.2.

Mặc dù 3 người đầu tiên dễ dàng sử dụng các mô hình django, nhưng lần cuối (ngẫu nhiên) gây ra cho tôi một số rắc rối. Tôi có thể mã hóa nó theo quan điểm của tôi, để một cái gì đó như thế này:

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

Nó không giống như một cái gì đó tôi muốn có trong quan điểm của mình tho - đây hoàn toàn là một phần của sự trừu tượng hóa cơ sở dữ liệu và nên có trong mô hình. Ngoài ra, ở đây tôi cần chăm sóc các bản ghi bị xóa (sau đó số lượng tất cả các bản ghi sẽ không bao gồm cho tôi tất cả các giá trị chính có thể) và có thể rất nhiều thứ khác.

Bất kỳ tùy chọn nào khác làm thế nào tôi có thể làm điều đó, tốt nhất là bằng cách nào đó bên trong mô hình trừu tượng?


Theo tôi, cách bạn hiển thị mọi thứ và những thứ bạn hiển thị là một phần của cấp độ "Chế độ xem" hoặc logic nghiệp vụ sẽ đi theo cấp độ "Trình điều khiển" của MVC, theo ý kiến ​​của tôi.
Gabriele D'Antona

Trong Django bộ điều khiển là khung nhìn. docs.djangoproject.com/en/dev/faq/general/ từ

Câu trả lời:


169

Sử dụng order_by('?')sẽ giết máy chủ db vào ngày thứ hai trong sản xuất. Một cách tốt hơn là một cái gì đó giống như những gì được mô tả trong Lấy một hàng ngẫu nhiên từ cơ sở dữ liệu quan hệ .

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
Những lợi ích của model.objects.aggregate(count=Count('id'))['count']hơnmodel.objects.all().count()
Ryan Saxe

11
Mặc dù tốt hơn nhiều so với câu trả lời được chấp nhận, lưu ý rằng phương pháp này tạo ra hai truy vấn SQL. Nếu số lượng thay đổi ở giữa, có thể có lỗi ngoài giới hạn.
Nelo Mitranim

2
Đây là một giải pháp sai. Nó sẽ không hoạt động nếu id của bạn không bắt đầu từ 0. Và cả khi id không liền kề nhau. Giả sử, bản ghi đầu tiên bắt đầu từ 500 và bản cuối cùng là 599 (giả sử tiếp giáp). Sau đó, số đếm sẽ là 54950. Chắc chắn danh sách [54950] không tồn tại vì độ dài truy vấn của bạn là 100. Nó sẽ ném chỉ mục ra khỏi ngoại lệ bị ràng buộc. Tôi không biết tại sao rất nhiều người ủng hộ điều này và điều này được đánh dấu là câu trả lời được chấp nhận.
sajid

1
@sajid: Tại sao, chính xác, bạn đang hỏi tôi? Khá dễ dàng để thấy tổng số đóng góp của tôi cho câu hỏi này: chỉnh sửa một liên kết để trỏ đến một kho lưu trữ sau khi nó bị mục nát. Tôi thậm chí đã không bỏ phiếu cho bất kỳ câu trả lời. Nhưng tôi thấy thật thú vị khi câu trả lời này và câu trả lời mà bạn cho là tốt hơn nhiều cả hai đều .all()[randint(0, count - 1)]có hiệu lực. Có lẽ bạn nên tập trung vào việc xác định phần nào của câu trả lời là sai hoặc yếu, thay vì xác định lại "lỗi một lần" cho chúng tôi và la mắng những cử tri ngu ngốc. (Có lẽ đó là nó không sử dụng .objects?)
Nathan Tuggy

3
@NathanTuggy. Ok xấu của tôi. Xin lỗi
sajid

260

Đơn giản chỉ cần sử dụng:

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

Nó được ghi lại trong API Queryset .


71
Xin lưu ý rằng phương pháp này có thể rất chậm, như được ghi lại :)
Nicolas Dumazet

6
"có thể tốn kém và chậm, tùy thuộc vào cơ sở dữ liệu phụ trợ bạn đang sử dụng." - có kinh nghiệm nào về các phụ trợ DB khác nhau không? (sqlite / mysql / postgres)?
kender

4
Tôi chưa thử nghiệm nó, vì vậy đây là suy đoán thuần túy: tại sao nó phải chậm hơn lấy tất cả các mục và thực hiện ngẫu nhiên trong Python?
muhuk

8
Tôi đọc được rằng nó chậm trong mysql, vì mysql có thứ tự ngẫu nhiên cực kỳ kém hiệu quả.
Brandon Henry

33
Tại sao không chỉ random.choice(Model.objects.all())?
Jamey 17/03/13

25

Các giải pháp với order_by ('?') [: N] cực kỳ chậm ngay cả đối với các bảng có kích thước trung bình nếu bạn sử dụng MySQL (không biết về các cơ sở dữ liệu khác).

order_by('?')[:N]sẽ được dịch sang SELECT ... FROM ... WHERE ... ORDER BY RAND() LIMIT Ntruy vấn.

Điều đó có nghĩa là với mỗi hàng trong bảng, hàm RAND () sẽ được thực thi, sau đó toàn bộ bảng sẽ được sắp xếp theo giá trị của hàm này và sau đó các bản ghi N đầu tiên sẽ được trả về. Nếu bàn của bạn nhỏ, điều này là tốt. Nhưng trong hầu hết các trường hợp, đây là một truy vấn rất chậm.

Tôi đã viết hàm đơn giản hoạt động ngay cả khi id có lỗ (một số hàng bị xóa):

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]

Nó nhanh hơn order_by ('?') Trong hầu hết các trường hợp.


30
Ngoài ra, thật đáng buồn, đó là xa ngẫu nhiên. Nếu bạn có một bản ghi với id 1 và một bản ghi khác với id 100, thì nó sẽ trả lại bản thứ hai 99% thời gian.
DS.

16

Đây là một giải pháp đơn giản:

from random import randint

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

10

Bạn có thể tạo một trình quản lý trên mô hình của mình để thực hiện loại điều này. Để đầu hiểu những gì một người quản lý là, Painting.objectsphương pháp là một người quản lý có chứa all(), filter(), get()vv Tạo quản lý riêng của bạn cho phép bạn pre-lọc kết quả và có tất cả những phương pháp tương tự, cũng như các phương pháp riêng của bạn, làm việc trên các kết quả .

EDIT : Tôi đã sửa đổi mã của mình để phản ánh order_by['?']phương thức. Lưu ý rằng người quản lý trả về số lượng mô hình ngẫu nhiên không giới hạn. Do đó, tôi đã bao gồm một chút mã sử dụng để chỉ cách lấy một mô hình duy nhất.

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.

Sử dụng

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

Cuối cùng, bạn có thể có nhiều người quản lý trên các mô hình của mình, vì vậy hãy thoải mái tạo một LeastViewsManager()hoặc MostPopularManager().


3
Sử dụng get () sẽ chỉ hoạt động nếu pks của bạn liên tiếp, tức là bạn không bao giờ xóa bất kỳ mục nào. Nếu không, bạn có thể thử và nhận được một pk không tồn tại. Sử dụng .all () [Random_index] không gặp phải vấn đề này và không hiệu quả hơn.
Daniel Roseman

Tôi hiểu rằng đó là lý do tại sao ví dụ của tôi chỉ đơn giản là sao chép mã câu hỏi với người quản lý. Nó vẫn sẽ tùy thuộc vào OP để kiểm tra giới hạn của anh ấy.
Soviut

1
thay vì sử dụng .get (id = Random_index) sẽ tốt hơn nếu sử dụng .filter (id__gte = Random_index) [0: 1]? Đầu tiên, nó giúp giải quyết vấn đề với pks không liên tiếp. Thứ hai, get_query_set sẽ trả về ... một Truy vấn. Và trong ví dụ của bạn, nó không.
Nicolas Dumazet

2
Tôi sẽ không tạo một người quản lý mới chỉ để xây dựng một phương thức. Tôi sẽ thêm "get_random" vào trình quản lý mặc định để bạn không phải trải qua tất cả () [0] hoop mỗi khi bạn cần hình ảnh ngẫu nhiên. Hơn nữa, nếu tác giả là ForeignKey cho mô hình Người dùng, bạn có thể nói user.painting_set.get_random ().
Antti Rasinen

Tôi thường tạo một người quản lý mới khi tôi muốn có một hành động trùm chăn, như lấy danh sách các bản ghi ngẫu nhiên. Tôi sẽ tạo một phương thức trên trình quản lý mặc định nếu tôi đang thực hiện một tác vụ cụ thể hơn với các bản ghi tôi đã có.
Soviut

6

Các câu trả lời khác có khả năng chậm (sử dụng order_by('?')) hoặc sử dụng nhiều hơn một truy vấn SQL. Đây là một giải pháp mẫu không có thứ tự và chỉ có một truy vấn (giả sử Postgres):

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

Xin lưu ý rằng điều này sẽ gây ra lỗi chỉ mục nếu bảng trống. Viết cho mình một hàm trợ giúp mô hình bất khả tri để kiểm tra điều đó.


Một bằng chứng tốt về khái niệm, nhưng đây là hai truy vấn bên trong cơ sở dữ liệu, những gì bạn lưu là một vòng cho cơ sở dữ liệu. Bạn sẽ phải thực hiện điều này rất nhiều lần để viết và duy trì một truy vấn thô có giá trị. Và nếu bạn muốn bảo vệ chống lại các bảng trống, bạn cũng có thể chạy count()trước và phân phối với truy vấn thô.
Endre Cả

2

Chỉ là một ý tưởng đơn giản làm thế nào tôi làm điều đó:

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

1

Chỉ cần lưu ý một trường hợp đặc biệt (khá phổ biến), nếu có một cột tăng tự động được lập chỉ mục trong bảng mà không xóa, cách tối ưu để thực hiện chọn ngẫu nhiên là một truy vấn như:

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

giả sử một cột có tên id cho bảng. Trong django bạn có thể làm điều này bằng cách:

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

trong đó bạn phải thay thế tên ứng dụng bằng tên ứng dụng của bạn.

Nói chung, với một cột id, order_by ('?') Có thể được thực hiện nhanh hơn nhiều với:

Paiting.objects.raw(
        'SELECT * FROM auth_user WHERE id>=RAND() * (SELECT MAX(id) FROM auth_user) LIMIT %d' 
    % needed_count)

1

Đây là đề xuất cao Nhận một hàng ngẫu nhiên từ cơ sở dữ liệu quan hệ

Bởi vì sử dụng django orm để làm một việc như vậy, sẽ khiến máy chủ db của bạn tức giận đặc biệt nếu bạn có bảng dữ liệu lớn: |

Và giải pháp là cung cấp Trình quản lý mô hình và viết truy vấn SQL bằng tay;)

Cập nhật :

Một giải pháp khác hoạt động trên bất kỳ phụ trợ cơ sở dữ liệu nào ngay cả những người không quan hệ mà không cần viết tùy chỉnh ModelManager. Lấy các đối tượng ngẫu nhiên từ một Queryset trong Django


1

Bạn có thể muốn sử dụng cùng một cách tiếp cận mà bạn sử dụng để lấy mẫu bất kỳ trình lặp nào, đặc biệt nếu bạn dự định lấy mẫu nhiều mục để tạo một bộ mẫu . @MatijnPieters và @DzinX suy nghĩ rất nhiều về điều này:

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

Giải pháp của Matijn và DxinX dành cho các bộ dữ liệu không cung cấp quyền truy cập ngẫu nhiên. Đối với các tập dữ liệu thực hiện (và SQL thực hiện với OFFSET), điều này không hiệu quả một cách không cần thiết.
Endre Cả

@EndreBoth thực sự. Tôi chỉ thích "hiệu quả" mã hóa của việc sử dụng cùng một cách tiếp cận bất kể nguồn dữ liệu. Đôi khi hiệu quả lấy mẫu dữ liệu không ảnh hưởng đáng kể đến hiệu suất của đường ống bị giới hạn bởi các quy trình khác (bất cứ điều gì bạn thực sự làm với dữ liệu, như đào tạo ML).
hobs

1

Một cách tiếp cận dễ dàng hơn nhiều cho việc này chỉ đơn giản là lọc xuống tập bản ghi quan tâm và sử dụng random.sampleđể chọn bao nhiêu tùy ý:

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

Lưu ý rằng bạn nên có một số mã tại chỗ để xác minh rằng my_querysetnó không trống; random.sampletrả về ValueError: sample larger than populationnếu đối số đầu tiên chứa quá ít phần tử.


2
Điều này sẽ gây ra toàn bộ truy vấn được lấy?
perrohunter

@perrohunter Nó thậm chí sẽ không hoạt động với Queryset(ít nhất là với Python 3.7 và Django 2.1); trước tiên bạn phải chuyển đổi nó thành một danh sách, điều này rõ ràng lấy toàn bộ bộ truy vấn.
Endre Cả

@EndreBoth - điều này đã được viết vào năm 2016, khi cả hai không tồn tại.
Eykanal

Đó là lý do tại sao tôi thêm thông tin phiên bản. Nhưng nếu nó hoạt động vào năm 2016, nó đã làm như vậy bằng cách kéo toàn bộ bộ truy vấn vào một danh sách, phải không?
Endre Cả

@EndreBoth Đúng.
Eykanal

1

Xin chào Tôi cần chọn một bản ghi ngẫu nhiên từ một bộ truy vấn mà tôi cũng cần báo cáo (tức là trang web được tạo ra mục được mô tả và các bản ghi còn lại)

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

mất một nửa thời gian (0,7 giây so với 1,7 giây) là:

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

Tôi đoán nó sẽ tránh việc kéo xuống toàn bộ truy vấn trước khi chọn mục nhập ngẫu nhiên và làm cho hệ thống của tôi đủ phản hồi cho một trang được truy cập nhiều lần cho một nhiệm vụ lặp đi lặp lại trong đó người dùng muốn xem mục đếm ngược.


0

Phương pháp tự động tăng khóa chính không có xóa

Nếu bạn có một bảng trong đó khóa chính là số nguyên tuần tự không có khoảng trống, thì phương thức sau sẽ hoạt động:

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

Phương thức này hiệu quả hơn nhiều so với các phương thức khác ở đây lặp đi lặp lại qua tất cả các hàng của bảng. Trong khi nó yêu cầu hai truy vấn cơ sở dữ liệu, cả hai đều không quan trọng. Hơn nữa, nó đơn giản và không yêu cầu xác định bất kỳ lớp học thêm nào. Tuy nhiên, khả năng ứng dụng của nó bị giới hạn trong các bảng có khóa chính tăng tự động trong đó các hàng chưa bao giờ bị xóa, do đó không có khoảng trống trong chuỗi id.

Trong trường hợp các hàng đã bị xóa như vậy là các khoảng trống, phương thức này vẫn có thể hoạt động nếu được thử lại cho đến khi một khóa chính hiện có được chọn ngẫu nhiên.

Người giới thiệu


0

Tôi có giải pháp rất đơn giản, làm cho người quản lý tùy chỉnh:

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

và sau đó thêm vào mô hình:

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

Bây giờ, bạn có thể sử dụng nó:

Example.objects.random()

từ lựa chọn nhập ngẫu nhiên
Adam Starrh

3
Xin vui lòng, không sử dụng phương pháp này, nếu bạn muốn tốc độ. Giải pháp này RẤT chậm. Tôi đã kiểm tra. Nó chậm hơn order_by('?').first()hơn 60 lần.
LagRange

@ Alex78191 không, "?" cũng tệ, nhưng phương pháp của tôi là EXTRA chậm. Tôi đã sử dụng giải pháp trả lời hàng đầu.
LagRange
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.