Django'da SELECT COUNT (*) GROUP BY ve ORDER BY nasıl yapılır?


99

Sistemden geçen tüm olayları takip etmek için bir işlem modeli kullanıyorum

class Transaction(models.Model):
    actor = models.ForeignKey(User, related_name="actor")
    acted = models.ForeignKey(User, related_name="acted", null=True, blank=True)
    action_id = models.IntegerField() 
    ......

sistemimdeki en iyi 5 oyuncuyu nasıl edinirim?

Sql'de temelde

SELECT actor, COUNT(*) as total 
FROM Transaction 
GROUP BY actor 
ORDER BY total DESC

Yanıtlar:


182

Belgelere göre şunları kullanmalısınız:

from django.db.models import Count
Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total')

değerler (): "gruplama ölçütü" için hangi sütunların kullanılacağını belirtir

Django belgeleri:

"Sonuç kümesinde döndürülen sütunları sınırlandırmak için bir values ​​() deyimi kullanıldığında, ek açıklamaları değerlendirme yöntemi biraz farklıdır. Orijinal QuerySet'teki her sonuç için açıklamalı bir sonuç döndürmek yerine, orijinal sonuçlar buna göre gruplanır. values ​​() yan tümcesinde belirtilen alanların benzersiz kombinasyonlarına "

annotate (): gruplanmış değerler üzerinde bir işlemi belirtir

Django belgeleri:

Özet değerleri oluşturmanın ikinci yolu, bir Sorgu Kümesindeki her nesne için bağımsız bir özet oluşturmaktır. Örneğin, bir kitap listesi alıyorsanız, her kitaba kaç yazarın katkıda bulunduğunu bilmek isteyebilirsiniz. Her Kitabın Yazar ile çoka çok ilişkisi vardır; QuerySet'teki her kitap için bu ilişkiyi özetlemek istiyoruz.

Nesne başına özetler, annotate () cümlesi kullanılarak oluşturulabilir. Bir annotate () cümlesi belirtildiğinde, QuerySet'teki her nesneye belirtilen değerlerle açıklama eklenir.

Maddeye göre sıralama kendi kendini açıklayıcıdır.

Özetlemek gerekirse: yazarlardan oluşan bir sorgu kümesi oluşturarak gruplandırırsınız, ek açıklamayı eklersiniz (bu, döndürülen değerlere fazladan bir alan ekleyecektir) ve son olarak, bunları bu değere göre sıralarsınız

Daha fazla bilgi için https://docs.djangoproject.com/en/dev/topics/db/aggregation/ adresine bakın

Dikkat edilmesi gereken nokta: Count kullanılıyorsa, Count'a iletilen değer toplamayı etkilemez, yalnızca son değere verilen addır. Toplayıcı, Count'a iletilen değere göre değil, benzersiz değer kombinasyonlarına göre (yukarıda belirtildiği gibi) gruplandırır. Aşağıdaki sorgular aynıdır:

Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total')
Transaction.objects.all().values('actor').annotate(total=Count('id')).order_by('total')

Benim için şu şekilde çalıştı Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total'), Count django.db.models'ten içe aktarmayı unutmayın. Teşekkürler
Ivancho

3
Dikkat edilmesi gereken nokta: Count(ve belki başka toplayıcılar) kullanılıyorsa, iletilen değer Counttoplamayı etkilemez, yalnızca son değere verilen adı etkiler. Toplayıcı values, iletilen değere göre değil (yukarıda belirtildiği gibi) benzersiz kombinasyonlarına göre gruplandırır Count.
kronosapiens

Bunu, faceting elde etmek için postgres arama sonucu sorgu kümeleri için bile kullanabilirsiniz!
yekta

2
@kronosapiens En azından bugünlerde bunu etkiliyor (Django 2.1.4 kullanıyorum). Örnekte, totalverilen ad ve sql'de kullanılan sayı COUNT('actor'), bu durumda önemli değildir, ancak örneğin values('x', 'y').annotate(count=Count('x')), alacaksınız COUNT(x), alamayacaksınız COUNT(*)veya COUNT(x, y)sadece denediniz./manage.py shell
timdiels

35

Tıpkı @Alvaro'nun Django'nun GROUP BYifade için doğrudan karşılığını yanıtlaması gibi :

SELECT actor, COUNT(*) AS total 
FROM Transaction 
GROUP BY actor

aşağıdaki yöntemlerin kullanımı values()ve annotate()yöntemidir:

Transaction.objects.values('actor').annotate(total=Count('actor')).order_by()

Ancak bir şeye daha dikkat çekilmelidir:

Model tanımlanan varsayılan bir sipariş varsa class Meta, .order_by()fıkra uygun sonuçlar için zorunludur. Hiçbir sipariş amaçlanmadığında bile atlayamazsınız.

Ayrıca, yüksek kaliteli bir kod için , hiç olmasa bile her zaman .order_by()sonra bir cümle koymanız önerilir . Bu tür bir yaklaşım, ifadeyi geleceğe hazır hale getirecektir: gelecekteki değişikliklerden bağımsız olarak, amaçlandığı gibi çalışacaktır .annotate()class Meta: orderingclass Meta: ordering


Size bir örnek vereyim. Modelin olsaydı:

class Transaction(models.Model):
    actor = models.ForeignKey(User, related_name="actor")
    acted = models.ForeignKey(User, related_name="acted", null=True, blank=True)
    action_id = models.IntegerField()

    class Meta:
        ordering = ['id']

O halde böyle bir yaklaşım işe yaramaz:

Transaction.objects.values('actor').annotate(total=Count('actor'))

Django ilave gerçekleştirir Çünkü GROUP BYher alanda üzerindeclass Meta: ordering

Sorguyu yazdırırsanız:

>>> print Transaction.objects.values('actor').annotate(total=Count('actor')).query
  SELECT "Transaction"."actor_id", COUNT("Transaction"."actor_id") AS "total"
  FROM "Transaction"
  GROUP BY "Transaction"."actor_id", "Transaction"."id"

Toplamanın amaçlandığı gibi çalışmayacağı ve bu nedenle .order_by()maddenin bu davranışı temizlemek ve uygun toplama sonuçları elde etmek için kullanılması gerektiği açık olacaktır.

Bkz .: Resmi Django belgelerinde varsayılan sıralama veya order_by () ile etkileşim .


3
.order_by()Beni orderingMeta'dan kurtardı .
Babken Vardanyan
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.