다른 범주에서 가장 최근의 개체를 가져 오는 Django 쿼리


79

두 가지 모델 AB. 모든 B개체에는 개체에 대한 외래 키가 A있습니다. A개체 집합이 주어지면 ORM을 사용하여 BA개체 에 대해 생성 된 가장 최근 개체를 포함하는 개체 집합을 가져올 수 있습니까 ?

다음은 간단한 예입니다.

class Bakery(models.Model):
    town = models.CharField(max_length=255)

class Cake(models.Model):
    bakery = models.ForeignKey(Bakery, on_delete=models.CASCADE)
    baked_at = models.DateTimeField()

그래서 저는 미국 Anytown의 각 빵집에서 구운 최신 케이크를 반환하는 쿼리를 찾고 있습니다.


6
저도보고 싶습니다 :-)
gruszczy

답변:


35

내가 아는 한, Django ORM에서이 작업을 수행하는 한 단계의 방법은 없습니다.

그러나 두 가지 쿼리로 분할 할 수 있습니다.

bakeries = Bakery.objects.annotate(
    hottest_cake_baked_at=Max('cake__baked_at')
) 
hottest_cakes = Cake.objects.filter(
    baked_at__in=[b.hottest_cake_baked_at for b in bakeries]
)

케이크의 id가 bake_at 타임 스탬프와 함께 진행되는 경우 위 코드를 단순화하고 명확하게 할 수 있습니다 (두 개의 케이크가 동시에 도착하는 경우 둘 다 얻을 수 있습니다).

hottest_cake_ids = Bakery.objects.annotate(
    hottest_cake_id=Max('cake__id')
).values_list('hottest_cak‌​e_id', flat=True)

hottest_cakes = Cake.objects.filter(id__in=hottest_cake_ids)

이에 대한 BTW 크레딧은 한때 저의 비슷한 질문에 답한 Daniel Roseman에게 돌아갑니다.

http://groups.google.pl/group/django-users/browse_thread/thread/3b3cd4cbad478d34/3e4c87f336696054?hl=pl&q=

위의 방법이 너무 느리면 두 번째 방법도 알고 있습니다. 관련 베이커리에서 가장 인기있는 Cakes 만 생성하는 사용자 지정 SQL을 작성하고 데이터베이스 VIEW로 정의한 다음 관리되지 않는 Django 모델을 작성할 수 있습니다. 위의 django-users 스레드에서도 언급되었습니다. 원래 개념에 대한 직접 링크는 다음과 같습니다.

http://web.archive.org/web/20130203180037/http://wolfram.kriesing.de/blog/index.php/2007/django-nice-and-critical-article#comment-48425

도움이 되었기를 바랍니다.


나는 아마도 당신이 제안한 두 번째 쿼리 세트로 갈 것입니다. 감사.
Zach

첫 번째 쿼리에 value_list를 사용하면 더 효율적입니다 .hottest_cake_ids = Bakery.objects.annotate (hottest_cake_id = Max ( 'cake__id')). values_list ( 'hottest_cake_id', flat = True); hottest_cakes = Cake.objects.filter (id__in = hottest_cake_ids)
dbn

또한 PostGreSQL을 사용하는 경우 한 단계 솔루션이 있습니다.
dbn

2
첫 번째 해결책은 하나의 최신 날짜가 다른 날짜보다 이전 날짜이지만 다른 날짜에는 존재하는 문제를 일으키지 않습니까? A = [1, 2, 3], B = [1, 2]. A 최신 = 3, B 최신 = 2. 첫 번째 쿼리는 A의 2와 3, B의 2를 얻는 것 같습니다.
kaungst

1
Django 1.11지금 부터는 일방향 단계가 있습니다. 내 대답을 확인하십시오.
Todor

32

에서 시작 Django 1.11및 덕분에 하위 쿼리OuterRef 우리는 마침내 구축 할 수 latest-per-group를 사용하여 쿼리를 ORM.

hottest_cakes = Cake.objects.filter(
    baked_at=Subquery(
        (Cake.objects
            .filter(bakery=OuterRef('bakery'))
            .values('bakery')
            .annotate(last_bake=Max('baked_at'))
            .values('last_bake')[:1]
        )
    )
)

#BONUS, we can now use this for prefetch_related()
bakeries = Bakery.objects.all().prefetch_related(
    Prefetch('cake_set',
        queryset=hottest_cakes,
        to_attr='hottest_cakes'
    )
)

#usage
for bakery in bakeries:
    print 'Bakery %s has %s hottest_cakes' % (bakery, len(bakery.hottest_cakes))

내 사용 사례가 약간 달랐지만 이것은 완벽하게 작동했습니다. 이 접근 방식에 대해 내가 좋아하는 점은 1) 결과 쿼리 세트를 대상 모델 인스턴스에 유지하고 2) 관련 데이터가없는 모델 인스턴스를 제외하지 않는다는 것입니다 (질문의 맥락에서 그렇지 않은 빵집). 아직 구운 것).
Supra621

당신은 똑똑한 사람이다
양 저우

19

PostGreSQL을 사용하는 경우 Django의 인터페이스를 사용 하여 DISTINCT ON 할 수 있습니다 .

recent_cakes = Cake.objects.order_by('bakery__id', '-baked_at').distinct('bakery__id')

으로 워드 프로세서는 , 당신이해야 말 order by같은 필드 당신 그 distinct on. Simon이 아래에서 지적했듯이 추가 정렬을 수행하려면 Python 공간에서 수행해야합니다.


접근 방식을 좋아하십시오-감사합니다. 최종 주문과 관련하여 사소한 수정을했습니다. QS의 전체 크기에 따라 이것은 허용 된 답변보다 좋거나 나쁠 수 있습니다. 내 경우 : 더 나은 :)
사이먼 스타 인 버거

나는 그것이 코드에 대한 불필요한 복잡성이라고 생각하며 대답하는 것 이상입니다. 사람들이 결과 데이터를 정렬하는 방법을 알아낼 수 있다고 가정하겠습니다.
dbn 2015 년

나는 비슷한 문제로 많이 놀았고 Max주석을 시도 하고 필터링했지만 django optimizer가 order_by를 제거한 후 부적절한 SQL 때문에 결국 db 측에서 실패했습니다 (결과를 필터 하위 쿼리로 사용하거나 집계 할 때 ex .count()). 이 솔루션은 가져올 때 모든 것을 깨뜨리지 recent_cakes.count()않고 오류를 발생시키지 않습니다 Cake.objects.filter(pk__in=recent_cackes).filter(other_conditions). 그러나 최신 예제 는 django 가 하위 쿼리에서 제거하기 때문에 other_conditions (가장 인기가 아님!)를 충족하는 빵집 당 임의의 케이크 를 반환 합니다. order_by:(
Ivan Klass

예, 그렇기 때문에 postGreSQL을 사용하지 않는다면 Tomasz Zielinski의 대답이 갈 길이라고 생각합니다.
dbn

5

이 작업을 수행해야합니다.

from django.db.models import Max
Bakery.objects.annotate(Max('cake__baked_at'))

5
아직 테스트하지 않았지만 각 제과점에서 가장 최근에 케이크를 구운 시간이 표시되는 것 같습니다. 실제 케이크 개체를 찾고 있습니다. 내가 당신의 대답을 잘못 해석하고 있습니까?
Zach

네 말이 맞아. 나는 Tomasz에 대해 게시 한 이전 답변을 잊었습니다. :-)
Daniel Roseman

1
나는 이것이 ID와 날짜별로 케이크를 정렬하는 것이 동일한 순서를 갖는 경우에만 작동한다고 믿습니다. 기본 키 시퀀스가 ​​날짜 필드에 정의 된 시간 순서와 일치하지 않는 일반적인 경우에는 작동하지 않습니다.
Dmitry B.

3

나는 비슷한 문제로 싸우고 마침내 해결책을 찾았습니다. 그것은에 의존하지 않는 order_bydistinct그래서 DB 측에 요구하고 또한 필터링 중첩 된 쿼리로 사용할 수 있습니다로 정렬 할 수 있습니다. 또한이 구현은 표준 SQL HAVING절을 기반으로하기 때문에 db 엔진에 독립적이라고 생각합니다 . 유일한 단점은 베이커리에서 정확히 동시에 구운 경우 베이커리 당 여러 개의 가장 뜨거운 케이크를 반환한다는 것입니다.

from django.db.models import Max, F

Cake.objects.annotate(
    # annotate with MAX "baked_at" over all cakes in bakery
    latest_baketime_in_bakery=Max('bakery__cake_set__baked_at')
    # compare this cake "baked_at" with annotated latest in bakery
).filter(latest_baketime_in_bakery__eq=F('baked_at'))

0
Cake.objects.filter(bakery__town="Anytown").order_by("-created_at")[:1]

나는 내 목적으로 모델을 구축하지 않았지만 이론적으로 이것은 작동합니다. 세분화 :

  • Cake.objects.filter(bakery__town="Anytown")국가가 문자열의 일부가 아니라고 가정하고 "Anytown"에 속한 모든 케이크를 반환해야합니다. bakery와 사이의 이중 밑줄 town을 사용하면의 town속성에 액세스 할 수 있습니다 bakery.
  • .order_by("-created_at")생성 된 날짜, 가장 최근의 순서로 결과를 정렬합니다 -((마이너스) 기호에 유의하십시오 "-created_at". 빼기 기호가 없으면 가장 오래된 것부터 가장 최근의 것 순 으로 정렬됩니다.
  • [:1] 마지막에는 반환되는 목록의 첫 번째 항목 만 반환합니다 (Anytown의 케이크 목록이 가장 최근의 순서로 정렬 됨).

참고 :이 답변은 Django 1.11 용입니다. 이 답변 은 Django 1.11 문서에 표시된 쿼리에서 수정되었습니다 .


0

위의 @Tomasz Zieliński 솔루션이 문제를 해결했지만 여전히 Cake를 필터링해야하므로 내 문제를 해결하지 못했습니다. 그래서 여기 내 해결책이 있습니다.

from django.db.models import Q, Max

hottest_yellow_round_cake = Max('cake__baked_at', filter=Q(cake__color='yellow', cake__shape='round'))

bakeries = Bakery.objects.filter(town='Chicago').annotate(
    hottest_cake_baked_at=hottest_yellow_round_cake
)

hottest_cakes = Cake.objects.filter(
    baked_at__in=[b.hottest_cake_baked_at for b in bakeries]
)

이 접근 방식을 사용하면 Filter, Ordering, Pagination for Cakes와 같은 다른 것들을 구현할 수도 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.