2013-03-01 32 views
7

Giả sử tôi muốn hiển thị danh sách các vận động viên được sắp xếp theo thời gian chạy nước rút mới nhất của họ.Django: Đặt hàng QuerySet dựa trên trường mô hình con mới nhất

class Runner(models.Model): 
    name = models.CharField(max_length=255) 

class Sprint(models.Model): 
    runner = models.ForeignKey(Runner) 
    time = models.PositiveIntegerField() 
    created = models.DateTimeField(auto_now_add=True) 

Đây là một phác thảo nhanh chóng của những gì tôi sẽ làm gì trong SQL:

SELECT runner.id, runner.name, sprint.time 
FROM runner 
LEFT JOIN sprint ON (sprint.runner_id = runner.id) 
WHERE 
    sprint.id = (
    SELECT sprint_inner.id 
    FROM sprint as sprint_inner 
    WHERE sprint_inner.runner_id = runner.id 
    ORDER BY sprint_inner.created DESC 
    LIMIT 1 
) 
    OR sprint.id = NULL 
ORDER BY sprint.time ASC 

Các Django QuerySet documentation trạng thái:

Nó được phép chỉ định một lĩnh vực có nhiều giá trị ra lệnh kết quả bởi (ví dụ, trường ManyToManyField). Thông thường, điều này sẽ không phải là một điều hợp lý để thực hiện và nó thực sự là một tính năng sử dụng nâng cao . Tuy nhiên, nếu bạn biết rằng bộ lọc hoặc dữ liệu có sẵn của bộ truy vấn ngụ ý rằng sẽ chỉ có một phần dữ liệu cho mỗi các mục chính bạn đang chọn, thứ tự có thể chính xác là những gì bạn muốn làm. Sử dụng đặt hàng trên các lĩnh vực đa giá trị cẩn thận và đảm bảo kết quả là những gì bạn mong đợi.

Tôi đoán tôi cần phải áp dụng một số bộ lọc ở đây, nhưng tôi không chắc chắn chính xác những gì Django hy vọng ...

Một lưu ý vì nó không phải là rõ ràng trong ví dụ này: bảng Runner sẽ có nhiều hàng trăm mục, chạy nước rút cũng sẽ có hàng trăm và trong một số ngày sau đó có thể vài nghìn mục. Dữ liệu sẽ được hiển thị trong chế độ xem được phân trang, do đó việc sắp xếp trong Python không phải là một tùy chọn.

Khả năng duy nhất khác tôi thấy là tự viết SQL, nhưng tôi muốn tránh điều này bằng mọi giá.

Trả lời

2

Tôi không nghĩ rằng có một cách để làm điều này thông qua ORM chỉ với một truy vấn, bạn có thể lấy một danh sách các vận động viên và sử dụng annotate để thêm mới nhất của id chạy nước rút của họ - sau đó lọc và ra lệnh những người chạy nước rút .

>>> from django.db.models import Max 

# all runners now have a `last_race` attribute, 
# which is the `id` of the last sprint they ran 
>>> runners = Runner.objects.annotate(last_race=Max("sprint__id")) 

# a list of each runner's last sprint ordered by the the sprint's time, 
# we use `select_related` to limit lookup queries later on 
>>> results = Sprint.objects.filter(id__in=[runner.last_race for runner in runners]) 
...       .order_by("time") 
...       .select_related("runner") 

# grab the first result 
>>> first_result = results[0] 

# you can access the runner's details via `.runner`, e.g. `first_result.runner.name` 
>>> isinstance(first_result.runner, Runner) 
True 

# this should only ever execute 2 queries, no matter what you do with the results 
>>> from django.db import connection 
>>> len(connection.queries) 
2 

Điều này khá nhanh và vẫn sẽ sử dụng các chỉ mục và bộ nhớ đệm của cơ sở dữ liệu.

Một vài nghìn bản ghi không phải là tất cả những gì nhiều, điều này sẽ làm việc khá tốt cho những loại số. Nếu bạn bắt đầu gặp sự cố, tôi khuyên bạn nên cắn viên đạn và sử dụng SQL thô.

+0

Điều này có gây ra việc sử dụng bộ nhớ tương đối cao không? Theo như tôi có thể thấy nó kéo ít nhất mỗi người chạy vào bộ nhớ và xây dựng một danh sách khá lớn các id chạy nước rút của họ. Làm điều này trên mỗi lần xem trang với hàng trăm người chạy trong DB khiến tôi cảm thấy một chút * không thoải mái. Đây là nơi mà bộ nhớ đệm đá vào, tôi đoán vậy. – Strayer

+1

Sau khi thử nghiệm điều này với 10.000 người chạy nó đã sử dụng RAM dưới 10MB (3MB thực sự…). Nếu bạn nghĩ rằng bạn sẽ cần nhiều hơn thế, bạn thực sự nên sử dụng SQL thô. Như mọi khi, cách tiếp cận tốt nhất để làm điều này là để hồ sơ đầu tiên - không suy đoán. Tối ưu hóa sớm và tất cả những điều đó ... – Matt

+0

Và, một vài trăm bản ghi thực sự không nhiều ... chắc chắn không đủ để đảm bảo lo lắng về tối ưu hóa hiệu suất. Một vài trăm nghìn bản ghi thường là nơi bạn sẽ bắt đầu suy nghĩ về nó, và thậm chí sau đó nó thường không nhiều của một vấn đề (quăng trong một hoặc hai chỉ số và nó được giải quyết). – Matt

0
def view_name(request): 
    spr = Sprint.objects.values('runner', flat=True).order_by(-created).distinct() 
    runners = [] 
    for s in spr: 
     latest_sprint = Sprint.objects.filter(runner=s.runner).order_by(-created)[:1] 
     for latest in latest_sprint: 
      runners.append({'runner': s.runner, 'time': latest.time}) 

    return render(request, 'page.html', { 
      'runners': runners, 
    }) 


{% for runner in runners %} 
    {{runner.runner}} - {{runner.time}} 
{% endfor %} 
+0

Sự cố không nhận được lần chạy nước rút mới nhất nhưng đặt hàng Runner QuerySet bằng trường 'thời gian' chạy nước rút mới nhất. – Strayer

+0

Tính năng này hoạt động, có. Vấn đề là điều này di chuyển thứ tự của các runners vào ứng dụng, mà gây ra ít nhất một bộ nhớ lớn sử dụng và sử dụng CPU tương đối cao. Vui lòng xem câu hỏi được cập nhật về kích thước bảng. Một vấn đề khác với cách tiếp cận này là nó sẽ không hiển thị bất kỳ vận động viên nào mà không có chạy nước rút chút nào. Trong khi điều này cũng có thể được giải quyết trong mã python, đây là một công việc hoàn hảo cho cơ sở dữ liệu vì nó có thể sử dụng các chỉ mục và bộ đệm của nó. Điều này làm việc cho các cơ sở dữ liệu nhỏ, nhưng SysAdmin của chúng tôi sẽ giết tôi nếu tôi làm theo cách này;) – Strayer

+0

hmmm ... điều này là khó khăn. Và chúng tôi cũng vậy, tôi cẩn thận viết mã nếu về công việc của tôi vì sự mong đợi của chủ nhân của tôi. :) – catherine

Các vấn đề liên quan