Bagaimana melakukan SELECT COUNT (*) GROUP BY dan ORDER BY di Django?

99

Saya menggunakan model transaksi untuk melacak semua peristiwa yang terjadi melalui sistem

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

bagaimana cara mendapatkan 5 aktor teratas di sistem saya?

Di sql pada dasarnya akan

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

Jawaban:

181

Menurut dokumentasi, Anda harus menggunakan:

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

values ​​(): menentukan kolom mana yang akan digunakan untuk "group by"

Django dokumen:

"Saat klausa values ​​() digunakan untuk membatasi kolom yang dikembalikan dalam kumpulan hasil, metode untuk mengevaluasi anotasi sedikit berbeda. Daripada mengembalikan hasil beranotasi untuk setiap hasil di QuerySet asli, hasil asli dikelompokkan sesuai ke kombinasi unik dari bidang yang ditentukan dalam klausa values ​​() "

annotate (): menentukan operasi atas nilai yang dikelompokkan

Django dokumen:

Cara kedua untuk menghasilkan nilai ringkasan adalah dengan menghasilkan ringkasan independen untuk setiap objek di QuerySet. Misalnya, jika Anda mengambil daftar buku, Anda mungkin ingin mengetahui berapa banyak penulis yang berkontribusi untuk setiap buku. Setiap Buku memiliki hubungan banyak-ke-banyak dengan Penulis; kami ingin meringkas hubungan ini untuk setiap buku di QuerySet.

Ringkasan per objek dapat dibuat menggunakan klausa annotate (). Saat klausa annotate () ditentukan, setiap objek di QuerySet akan dianotasi dengan nilai yang ditentukan.

Urutan menurut klausul sudah cukup jelas.

Untuk meringkas: Anda mengelompokkan berdasarkan, menghasilkan queryset penulis, menambahkan anotasi (ini akan menambahkan bidang ekstra ke nilai yang dikembalikan) dan terakhir, Anda mengurutkan mereka dengan nilai ini

Lihat https://docs.djangoproject.com/en/dev/topics/db/aggregation/ untuk wawasan lebih lanjut

Sebaiknya diperhatikan: jika menggunakan Hitung, nilai yang diteruskan ke Hitung tidak memengaruhi agregasi, hanya nama yang diberikan ke nilai akhir. Agregator mengelompokkan berdasarkan kombinasi unik dari nilai-nilai (seperti yang disebutkan di atas), bukan berdasarkan nilai yang diteruskan ke Hitung. Kueri berikut ini sama:

Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total')
Transaction.objects.all().values('actor').annotate(total=Count('id')).order_by('total')
Alvaro
sumber
Bagi saya ini berfungsi sebagai Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total'), jangan lupa untuk mengimpor Hitungan dari django.db.models. Terima kasih
Ivancho
3
Sebaiknya diperhatikan: jika menggunakan Count(dan mungkin agregator lain), nilai yang diteruskan ke Counttidak memengaruhi agregasi, hanya nama yang diberikan ke nilai akhir. Agregator mengelompokkan berdasarkan kombinasi unik values(seperti yang disebutkan di atas), bukan berdasarkan nilai yang diteruskan Count.
kronosapiens
Anda bahkan dapat menggunakan ini untuk kumpulan kueri hasil pencarian postgres untuk mendapatkan faceting!
yekta
2
@kronosapiens Itu memang mempengaruhinya, paling tidak saat ini (saya menggunakan Django 2.1.4). Dalam contoh, totaladalah nama yang diberikan dan jumlah yang digunakan dalam sql adalah COUNT('actor')yang dalam hal ini tidak masalah, tetapi jika misalnya values('x', 'y').annotate(count=Count('x')), Anda akan mendapatkan COUNT(x), tidak COUNT(*)atau COUNT(x, y), hanya mencobanya di./manage.py shell
timdiels
35

Seperti @Alvaro telah menjawab padanan langsung Django untuk GROUP BYpernyataan:

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

adalah melalui penggunaan values()dan annotate()metode sebagai berikut:

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

Namun satu hal lagi yang harus diperhatikan:

Jika model memiliki pengurutan default yang ditentukan class Meta, .order_by()klausul wajib untuk hasil yang sesuai. Anda tidak bisa melewatkannya bahkan ketika tidak ada tujuan pemesanan.

Selanjutnya, untuk kode kualitas tinggi, disarankan untuk selalu meletakkan .order_by()klausul setelahnya annotate(), bahkan jika tidak ada class Meta: ordering. Pendekatan seperti itu akan membuat pernyataan tahan di masa depan: itu akan berfungsi seperti yang dimaksudkan, terlepas dari perubahan apa pun di masa mendatang class Meta: ordering.


Izinkan saya memberi Anda sebuah contoh. Jika model memiliki:

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']

Maka pendekatan seperti itu TIDAK AKAN berhasil:

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

Itu karena Django melakukan tambahan GROUP BYpada setiap bidang diclass Meta: ordering

Jika Anda ingin mencetak kueri:

>>> 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"

Akan jelas bahwa agregasi TIDAK akan berfungsi sebagaimana mestinya dan oleh karena itu .order_by()klausul harus digunakan untuk menghapus perilaku ini dan mendapatkan hasil agregasi yang sesuai.

Lihat: Interaksi dengan pengurutan default atau order_by () dalam dokumentasi resmi Django.

Krzysiek
sumber
3
.order_by()menyelamatkan saya dari orderingdi Meta.
Babken Vardanyan