Pengumpulan¶
Panduan topik pada Django’s database-abstraction API menggambarkan cara dimana anda dapat menggunakan permintaan Django yang membuat, mengambil, memperbaharui dan menghapus obyek tersendiri. Bagaimanapun, terkadang anda akan butuh mengambil nilai yang berasal oleh meringkas atau*mengumpulkan* kumpulan dari obyek. Topik panduan ini menggambarkan cara yang mengumpulkan nilai-nilai dapat dibangkitkan dan dikembalikan menggunakan permintaan Django.
Sepanjang panduan ini, kami akan mengacu ke model berikut. Model-model ini digunakan untuk melacak inventaris untuk rangkaian dari toko buku daring:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
class Publisher(models.Model):
name = models.CharField(max_length=300)
num_awards = models.IntegerField()
class Book(models.Model):
name = models.CharField(max_length=300)
pages = models.IntegerField()
price = models.DecimalField(max_digits=10, decimal_places=2)
rating = models.FloatField()
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
pubdate = models.DateField()
class Store(models.Model):
name = models.CharField(max_length=300)
books = models.ManyToManyField(Book)
registered_users = models.PositiveIntegerField()
Lembar curang¶
Terburu-buru? Ini adalah bagaimana melakukan permintaan pengumpulan umum, menganggap model diatas:
# Total number of books.
>>> Book.objects.count()
2452
# Total number of books with publisher=BaloneyPress
>>> Book.objects.filter(publisher__name='BaloneyPress').count()
73
# Average price across all books.
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}
# Max price across all books.
>>> from django.db.models import Max
>>> Book.objects.all().aggregate(Max('price'))
{'price__max': Decimal('81.20')}
# Cost per page
>>> from django.db.models import F, FloatField, Sum
>>> Book.objects.all().aggregate(
... price_per_page=Sum(F('price')/F('pages'), output_field=FloatField()))
{'price_per_page': 0.4470664529184653}
# All the following queries involve traversing the Book<->Publisher
# foreign key relationship backwards.
# Each publisher, each with a count of books as a "num_books" attribute.
>>> from django.db.models import Count
>>> pubs = Publisher.objects.annotate(num_books=Count('book'))
>>> pubs
<QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]>
>>> pubs[0].num_books
73
# The top 5 publishers, in order by number of books.
>>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5]
>>> pubs[0].num_books
1323
Membangkitkan pengumpulan terhadap QuerySet
¶
Django menyediakan dua cara membangkitkan pengumpulan. Cara pertama adalah membangkitkan nilai ringkasan terhadap keseluruhan QuerySet
. Sebagai contoh, katakan anda ingin menghitung rata-rata harga dari semua buku tersedia untuk dijual. Sintaksis permintaan Django menyediakan sarana untuk menggambarkan kumpulan dari semua buku:
>>> Book.objects.all()
Apa kami butuhkan adalah cara menghitung ringkasan nilai terhadap obyek yang memiliki QuerySet
ini. Ini diselesaikan dengan menambah sebuah klausa aggregate()
kedalam QuerySet
:
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}
all()
berulang di contoh ini, jadi ini dapat disederhanakan menjadi:
>>> Book.objects.aggregate(Avg('price'))
{'price__avg': 34.35}
Argumen pada klausa aggregate()
menggambarkan nilai pengumpulan yang kami ingin hitung - dalam kasus ini, rata-rata dari bidang price
pada model Book
. Sebuah daftar dari fungsi pengumpulan yang tersedia dapat ditemukan di QuerySet reference.
aggregate()
adalah klausa terminal untuk sebuah QuerySet
yang, ketika dipanggil, mengembalikan sebuah kamus dari pasangan nama-nilai. Nama adalah sebuah penciri untuk nilai pengumpulan; nilai adalah pengumpulan yang dihitung. Nama adalah otomatis dibangkitkan dari nama dari bidang dan fungsi pengumpulan. Jika anda ingin manual menentukan nama untuk nilai pengumpulan, anda dapat melakukannya dengan menyediakan nama itu ketika anda menentukan klausa pengumpulan:
>>> Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 34.35}
Jika anda ingin membangkitkan lebih dari satu pengumpulan, anda hanya menambah argumen lain ke klusa aggregate()
. Jadi, jika kami juga ingin mengetahui harga maksimal dan minimal dari semua buku, kami akan menerbitkan permintaan:
>>> from django.db.models import Avg, Max, Min
>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
Membangkitkan pengumpulan untuk setiap barang di QuerySet
¶
Cara kedua membangkitkan ringkasan nilai adalah membangkitkan sebuah ringkasan berdiri sendiri untuk setiap obyek dalam sebuah QuerySet
. Sebagai contoh, jika anda sedang mengambil sebuah daftar buku, anda mungkin ingin mengetahui seberapa banyak penulis membantu setiap buku. Setiap buku mempunyai hubungan many-to-many dengan Author; kami ingin meringkaskan hubungan ini untuk setiap buku dalam QuerySet
.
Ringkasan per-obyek dapat dibangkitkan menggunakan klausa annotate()
. Ketika sebuah klausa annotate()
ditentukan, stiap obyek dalam QuerySet
akan dicatat dengan nilai yang ditentukan.
Sintaksis untuk catatan ini mirip pada yang digunakan untuk klausa aggregate()
. Setiap argumen pada annotate()
menggambarkan sebuah pengumpulan yang untuk dihitung. Sebagai contoh, untuk mencatat buku dengan sejumlah penulis:
# Build an annotated queryset
>>> from django.db.models import Count
>>> q = Book.objects.annotate(Count('authors'))
# Interrogate the first object in the queryset
>>> q[0]
<Book: The Definitive Guide to Django>
>>> q[0].authors__count
2
# Interrogate the second object in the queryset
>>> q[1]
<Book: Practical Django Projects>
>>> q[1].authors__count
1
Seperti aggregate()
, nama untuk catatan otomatis berasal dari nama dari fungsi pengumpulan dan nama dari bidang sedang dikumpulkan. Anda dapat menimpa nama awalan ini dengan menyediakan sebuah nama lain ketika anda menentukan catatan:
>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2
>>> q[1].num_authors
1
Tidak seperti aggregate()
, annotate()
adalah bukan klausa terminal. Keluaran dari klausa annotate()
adalah sebuah QuerySet
; QuerySet
ini dapat dirubah menggunakan apapun selain operasi QuerySet
, termasuk filter()
, order_by()
, atau bahkan tambahan panggilan pada annotate()
.
Memadukan banyak pengumpulan¶
Memadukan banyak pengumpulan dengan annotate()
akan yield the wrong results karena join digunakan daripada sub permintaan:
>>> book = Book.objects.first()
>>> book.authors.count()
2
>>> book.store_set.count()
3
>>> q = Book.objects.annotate(Count('authors'), Count('store'))
>>> q[0].authors__count
6
>>> q[0].store__count
6
Untuk kebanyakan pengumpulan, tidak ada jalan menghindari masalah ini, bagaimanapun, pengumpulan Count
mempunai sebuah parameter distinct
yang mungkin membantu:
>>> q = Book.objects.annotate(Count('authors', distinct=True), Count('store', distinct=True))
>>> q[0].authors__count
2
>>> q[0].store__count
3
Jika ragu, periksa permintaan SQL!
Untuk memahami apa yang terjadi di permintaan anda, pertimbangkan memeriksa sifat query
dari QuerySet
anda.
Gabung dan kumpulkan¶
Sejauh ini, kami telah berurusan dengan pengumpulan terhadap bidang yang milik ke model sedang diminta. Bagaimanapun, terkadang nilai anda ingin kumpulkan akan milik ke model yang terkait ke model anda sedang meminta.
Ketika anda menentukan bidang untuk dikumpulkan dalam sebuah fungsi pengumpulan, Django akan mengizinkan anda menggunakan double underscore notation sama yang digunakan ketika mengacu ke bidang terkait dalam penyaring. Django akan kemudian menangani gabungan table apapun yang dibutuhkan untuk mengambil dan mengumpulkan nilai terkait.
Sebagai contoh, untuk menemukan jangkauan harga dari buku ditawarkan disetiap toko, anda dapat menggunakan catatan:
>>> from django.db.models import Max, Min
>>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))
Ini memberitahu Django untuk mengambil model Store
, gabung (melalui hubungan many-to-many) dengan model Book
, dan dikumpulkan pada bidang harga dari model buku untuk menghasilkan sebuah nilai minimal dan maksimal.
Aturan sama berlaku pada klausa aggregate()
. Jika anda ingin mengetahui harga terendah dan tertinggi dari buku apapun yang tersedia untuk dijual di toko apapun, anda dapat menggunakan pengumpulan:
>>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))
Ikatan join dapat sedalam seperti anda minta. Sebagai contoh, untuk mengeluarkan umur dari penulis termuda dari buku apapun yang tersedia untuk dijual, anda dapat menerbitkan permintaan:
>>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))
Mengikuti hubungan kebelakang¶
Dalam sebuah cara mirip pada Lookups that span relationships, pengumpulan dan catatan pada bidang dari model atau model yang terkait ke satu anda sedang meminta dapat menyertakan lintasan hubungan “reverse”. Nama huruf kecil dari model terkait dan garis bawah ganda digunakan disini juga.
Sebagai contoh, kami dapat meminta untuk semua penerbit, dicatat dengan masing-masing jumlah penghitung stok buku (catat bahwa kami menggunakan 'book'
untuk menentukan Publisher
-> Book
membalikkan lompatan foreign key):
>>> from django.db.models import Count, Min, Sum, Avg
>>> Publisher.objects.annotate(Count('book'))
(Setiap Publisher
dalam menghasilkan QuerySet
akan mempunyai sebuah atribut tambahan disebut book__count
.)
Kami dapat juga meminta untuk buku tertua dari setiap dari itu dikelola oleh setiap penerbit:
>>> Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate'))
(Hasil kamus akan mempunyai sebuah kunci dipanggil 'oldest_pubdate'
. Jika tidak ada nama lain seperti itu telah ditentukan, itu akan agak panjang 'book__pubdate__min'
.)
Ini tidak berlaku hanya pada foreign key. Itu juga bekerja dengan hubungan many-to-many. Sebagai contoh, kami dapat meminta untuk setiap penulis, dicatat dengan jumlah nomor dari halaman mempertimbangkan semua buku penulis mempunyai penulis(-bersama) (catat bagaimana menggunakan 'book'
untuk menentukan Author
-> Book
membalikkan lompatan many-to-many):
>>> Author.objects.annotate(total_pages=Sum('book__pages'))
(Setiap Author
dalam menghasilkan QuerySet
akan mempunyai sebuah atribut tambahan dipanggil total_pages
. Jika tidak ada nama lain itu, itu akan menjadi agak panjang book__pages__sum
.)
Atau minta untuk penilaian rata-rata dari semua buku ditulis oleh penulis kami punyai pada berkas:
>>> Author.objects.aggregate(average_rating=Avg('book__rating'))
(Hasil kamus akan mempunyai sebuah kunci dipanggil 'average_rating'
. Jika tidak ada nama lain seperti itu telah ditentukan, itu akan agak panjang 'book__rating__avg'
.)
Pengumpulan dan klausa QuerySet
lain¶
filter()
dan exclude()
¶
Pengumpulan dapat juga ikut serta dalam penyaring. filter()
(atau``exclude()``) apapun berlaku pada bidang model biasa akan mempunyai pengaruh dari membatasi obyek yang dianggap untuk pengumpulan.
Ketika digunakan dengan sebuah klausa annotate()
, sebuah penyaring mempunyai pengaruh dari membatasi obyek untuk dimana sebuah catatan dihitung. Sebagai contoh, anda dapat membangkitkan daftar dari semua buku yang mempunyai sebuah judul dimulai dengan “Django”” menggunakan permintaan:
>>> from django.db.models import Count, Avg
>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))
Ketika digunakan dengan sebuah klausa aggregate()
, sebuah penyaring mempunyai pengaruh dari membatasi obyek untuk dimana sebuah pengumpulan dihitung. Sebagai contoh, anda dapat membangkitkan rata-rata harga dari semua buku yang mempunyai sebuah judul dimulai dengan “Django”” menggunakan permintaan:
>>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))
Penyaringan pada keterangan¶
Nilai dicatat dapat juga disaring. Nama lain untuk catatan dapat digunaan dalam klausa filter()
dan exclude()
di cara sama seperti bidang model lainnya apapun.
Sebagai contoh, untuk membangkitkan sebuah daftar buku yang mempunyai lebih dari satu penulis, anda dapat menerbitkan permintaan:
>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)
Permintaan ini membangkitkan sebuah kumpulan hasil keterangan, dan kemudian membangkitkan sebuah penyaring berdasarkan pada keterangan itu.
Urutan dari klausa annotate()
dan filter()
¶
Ketika mengembangkan sebuah permintaan rumit yang melibatkan kedua klausa annotate()
dan filter()
, berikan perhatian khusus pada urutan dimana klausa diberlakukan pada QuerySet
.
Ketika sebuah klausa annotate()
diberlakukan pada sebuah permintaan, catatan dihitung terhadap keadaan dari permintaan sampai titik dimana catatan diminta. Impliaksi praktik dari ini adalah bahwa filter()
dan annotate()
bukan operasi komutatif.
Diberikan:
Penerbit A mempunyai dua buku dengan nilai 4 dan 5.
Penerbit B mempunyai dua buku dengan nilai 1 dan 4.
Penerbit C mempunyai satu buku dengan penilaian 1.
Ini adalah sebuah contoh dengan pengumpulan Count
:
>>> a, b = Publisher.objects.annotate(num_books=Count('book', distinct=True)).filter(book__rating__gt=3.0)
>>> a, a.num_books
(<Publisher: A>, 2)
>>> b, b.num_books
(<Publisher: B>, 2)
>>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count('book'))
>>> a, a.num_books
(<Publisher: A>, 2)
>>> b, b.num_books
(<Publisher: B>, 1)
Kedua permintaan mengembalikan sebuah daftar dari penerbit yang mempunyai setidaknya satu buku dengan penilaian melebihi 3.0, karenanya penerbit C tidak disertakan.
Dalam permintaan pertama, catatan mendahului penyaring, jadi penyaring tidak mempunyai pengaruh pada catatan. distinct=True
dibutuhkan untuk menghindari query bug.
Permintaan kedua menghitung jumlah buku yang mempunyai nilai melebihi 3.0 untuk setiap penerbit. Penyaring ini mendahului catatan, jadi batasan penyaring obyek dianggap ketika menghitung catatan.
Ini adalah sebuah contoh lain dengan pengumpulan Avg
:
>>> a, b = Publisher.objects.annotate(avg_rating=Avg('book__rating')).filter(book__rating__gt=3.0)
>>> a, a.avg_rating
(<Publisher: A>, 4.5) # (5+4)/2
>>> b, b.avg_rating
(<Publisher: B>, 2.5) # (1+4)/2
>>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(avg_rating=Avg('book__rating'))
>>> a, a.avg_rating
(<Publisher: A>, 4.5) # (5+4)/2
>>> b, b.avg_rating
(<Publisher: B>, 4.0) # 4/1 (book with rating 1 excluded)
Permintaan pertama meminta untuk rata-rata penilaian dari semua penerbit buku untuk penerbit yang mempunyai setidaknya satu buku ketika penilaian melebihi 3.0. Permintaan kedua meminta untuk rata-rata dari penilaian buku penerbit untuk hanya mereka penilaian melebihi 3.0.
Itu adalah sangat sulit memahami bahwa ORM akan menterjemahkan queryset rumit kedalam permintaan SQL ketika dalam keraguan, periksa SQL dengan str(queryset.query)
dan tulis jumlah besar percobaan.
order_by()
¶
Catatan dapat digunakan sebagai dasar untuk pengurutan. Ketika anda menentukan sebuah klausa order_by()
, pengumpulan anda sediakan dapat mengacu nama lain apapun ditentukan sebagai bagian dari sebuah klausa annotate()
dalam permintaan.
Sebagai contoh, untuk mengurutkan QuerySet
dari buku dengan jumlah dari pengarang yang memiliki bantuan pada buku, anda dapat menggunakan permintaan berikut:
>>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
values()
¶
Biasanya, catatan dibangkitkan pada dasar per-obyek - sebuah QuerySet
dicatat akan mengembalikan satu hasil untuk setiap obyek dalam QuerySet
asli. Bagaimanapun, ketika sebuah klausa values()
digunakan untuk membatasi kolom yang dikembalikan dalam kumpulan hasil, metode untuk menilai catatan adak sedikit berbeda. Sebagai gantinya mengembalikan sebuah hasil dicatat untuk setiap hasil dalam QuerySet
asli, hasil asli dikelompokkan menurut pada perpaduan unik dari bidang-bidang ditentukan dalam klausa values()
. Sebuah catatam kemudian disediakan untuk setiap kelompok unik; catatan dihitung terhadap semua anggota dari kelompok.
Untuk setiap contoh, pertimbangkan sebuah permintaan penulis yang berusaha menemukan rata-rata penilaian dari buku ditulis oleh setiap penulis.
>>> Author.objects.annotate(average_rating=Avg('book__rating'))
Ini akan mengembalikan satu hasil untuk setiap penulis di basisdata, diberikan keterangan dengan nilai buku rata-rata mereka.
Bagaimanapun, hasil akan sedikit berbeda jika anda menggunakan kalusa values()
:
>>> Author.objects.values('name').annotate(average_rating=Avg('book__rating'))
Dalam contoh ini, penulis akan dikelompokkan berdasarkan nama, jadi anda akan hanya mendapatkan sebuah hasil dicatata untuk setiap nama penulis unik. Ini berarti jika anda mempunyai dua penulis dengan nama sama, hasil mereka akan digabung kedalam hasil tunggal di keluaran dari permintaan; rata-rata akan dihitung sebagai rata-rata terhadap buku ditulis oleh kedua penulis.
Urutan dari klausa annotate()
dan values()
¶
Seperti klausa filter()
, urutan dimana klausa annotate()
dan values()
diberlakukan pada sebuah permintaan adalah signifikan. Jika klausa values()
mendahului annotated()
, catatan akan dihitung menggunakan pengelomppokan digambarkan oleh klausa values()
.
Bagaimanapun, jika klausa annotate()
mendahului klausa values()
, catatan akan dibangkitkan terhadap keseluruhan kumpulan permintaan. Dalam kasus ini, klausa values()
hanya membatasi bidang yang dibangkitkan pada keluaran.
Sebagai contoh, jika kami membalikkan urutan dari klausa values()
dan annotate()
dari contoh kami sebelumnya:
>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')
Ini akan sekarang menghasilkan hasil unik untuk setiap penulis; bagaimanapun, hanya nama penulis dan catatan average_rating
akan dikembalikan dalam data keluaran.
Anda harus juga catat bahwa average_rating
telah jelas disertakan dalam daftar dari nilai-nilai untuk dikembalikan. Ini dibutuhkan karena dari urutan dari klausa values()
dan annotate()
.
Jika klausa values()
mendahului klausa annotate()
, setiap catatan akan otomatis ditambahkan ke kumpulan hasil. Bagaimanapun, jika klausa values()
diberlakukan setelah klausa annotate()
, anda butuh jelas menyertakan kolom pengumpulan.
Interaksi dengan pengurutan awalan atau order_by()
¶
Bidang-bidang yang disebut di bagian order_by()
dari sebuah queryset (atau yang digunakan di pengurutan awalan pada sebuah model) digunakan ketika memilih data keluaran, bahkan jika mereka tidak sebaliknya ditentukan di panggilan values()
. Bidang tambahan ini digunakan untuk mengelompokkan hasil “like” bersama-sama dan mereka dapat membuat sebaliknya baris hasil mirip muncul untuk dipesahkan. Ini menunjukkan, khususnya, ketika menghitung hal-hal.
Berdasarkan cara contoh, seharusnya anda mempunyai model seperti ini:
from django.db import models
class Item(models.Model):
name = models.CharField(max_length=10)
data = models.IntegerField()
class Meta:
ordering = ["name"]
Bagian terpenfting disini adalah pengurutan awalan pada bidang name
. Jika anda ingin menghitung seberapa banyak kali setiap perbedaan nilai data
muncul, anda mungkin mencoba ini:
# Warning: not quite correct!
Item.objects.values("data").annotate(Count("id"))
...yang akan mengelompokkan obyek Item
berdasarkan nilai-nilai data
umum mereka dan menghitung nomro dari nilai-nilai id
dalam setiap kelompok, Kecuali itu tidak akan bekerja. Pengurutan awalan oleh name
akan juga bermain bagian dalam pengelompokan, jadi permintaan ini akan dikelompokkan berdasarkan perbedaan pasangan (data, name)
, yang tidak apa anda inginkan. Malahan, anda harus membangun queryset ini:
Item.objects.values("data").annotate(Count("id")).order_by()
...membersihkan pengurutan apapun di permintaan. Anda dapat juga mengurutkan berdasarkan, katakan, data
tanpa pengaruh berbahaya apapun, sejak itu sudah bermain sebuah peran dalam permintaan.
Perilaku ini adalah sama seperti dicatat dalam dokumentasi queryset untuk distinct()
dan aturan umum adalah sama: biasanya anda tidak ingin kolom tambahan bermain bagian dalam hasil, jadi bersihkan pengurutan, atau setidaknya pastikan itu terbatas hanya bidang-bidang tersebut anda juga pilih dalam sebuah panggilan values()
.
Catatan
Anda mungkin layak bertanya mengapa Django tidak memidnahkan kolom tidak ada hubungannya untuk anda. Alasan utama adalah ketetapan dengan distinct()`` dan tempat lain: Django tidak pernah memindahkan pengurutan yang anda telah tentukan (dan kami tidak dapat merubah perilaku metode lain tersebut, ketika itu akan melanggar kebijakan Keseimbangan API kami).
Pengumpulan catatan¶
Anda dapat juga membangkitkan sebuah pengumpulan pada hasil dari sebuah catatan. Ketika anda menentukan sebuah klausa aggregate()
, pengumpulan anda sediakan dapat mengacu setiap nama lain ditentukan sebagai bagian dari sebuah klausa annotated()
dalam permintaan.
Sebagai contoh, jika anda ingin menjumlahkan angka rata-rata dari penulis per buku anda pertama membubuhi catatan kumpulan buku dengan jumlah penulis, lalu kumpulkan jumlah penulis itu, mengacu bidang membubuhi catatan
>>> from django.db.models import Count, Avg
>>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Avg('num_authors'))
{'num_authors__avg': 1.66}