agregar () vs anotar () en Django

11 minutos de lectura

Avatar de usuario de Alexander Artemenko
Alejandro Artemenko

de Django QuerySet tiene dos métodos, annotate y aggregate. La documentación dice que:

A diferencia de added(), annotate() no es una cláusula terminal. El resultado de la cláusula annotate() es un QuerySet.
https://docs.djangoproject.com/en/4.1/topics/db/aggregation/#generando-agregados-para-cada-elemento-en-un-conjunto-de-consulta

¿Hay alguna otra diferencia entre ellos? Si no, entonces ¿por qué aggregate ¿existir?

  • ¿Puede agregar el enlace a esta declaración en los documentos?

    –Lev Slinsen

    20/04/2022 a las 21:15

  • enlace agregado a documentos

    –Harry Moreno

    10 ene a las 20:43

Me centraría en las consultas de ejemplo en lugar de su cita de la documentación. Aggregate calcula los valores de completo conjunto de consultas Annotate calcula los valores de resumen para Cada artículo en el conjunto de consultas.

Agregación

>>> Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 34.35}

Devuelve un diccionario que contiene el precio promedio de todo libros en el conjunto de consulta.

Anotación

>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2
>>> q[1].num_authors
1

q es el conjunto de consulta de libros, pero cada libro se ha anotado con el número de autores.

  • ¿Estoy en lo correcto? .annotate() en un qs solo no golpea el db, pero llamando q[0].num_authors ¿hace? Asumo aggregate siempre debe golpear el db ya que es una cláusula terminal?

    – alias51

    30 de julio de 2020 a las 23:05


  • @alias51 que está realmente relacionado con la pregunta original, por lo que no creo que los comentarios sobre una pregunta de hace ocho años sean el mejor lugar para preguntar. Si desea verificar cuándo se ejecutan las consultas, puede verificar connection.queries. Pista: compruebe si es el book = q[0] o ` book.num_authors` que provoca la consulta.

    – Alasdair

    31 de julio de 2020 a las 12:27


Avatar de usuario de Vinay Kumar
Vinay Kumar

Agregar
Agregue valores de resultados de generación (resumen) en un QuerySet completo. La operación agregada sobre el conjunto de filas para obtener un valor único del conjunto de filas (por ejemplo, la suma de todos los precios en el conjunto de filas). El agregado se aplica en todo el QuerySet y genera valores de resultado (resumen) en todo un QuerySet.

En modelo:

class Books(models.Model):
    name = models.CharField(max_length=100)
    pages = models.IntegerField()
    price = models.DecimalField(max_digits=5, decimal_places=3)

En cáscara:

>>> Books.objects.all().aggregate(Avg('price'))
# Above code will give the Average of the price Column 
>>> {'price__avg': 34.35}

Anotar
Anotar genera un resumen independiente para cada objeto en un QuerySet. (Podemos decir que itera cada objeto en un QuerySet y aplica la operación)

En modelo:

class Video(models.Model):
    name = models.CharField(max_length=52, verbose_name="Name")
    video = models.FileField(upload_to=document_path, verbose_name="Upload 
               video")
    created_by = models.ForeignKey(User, verbose_name="Created by", 
                       related_name="create_%(class)s")
    user_likes = models.ManyToManyField(UserProfile, null=True, 
                  blank=True, help_text="User can like once", 
                         verbose_name="Like by")

En vista:

videos = Video.objects.values('id', 'name','video').annotate(Count('user_likes',distinct=True)

A la vista contará los likes de cada vídeo

  • por qué distinct=True se requiere en el último ejemplo?

    – Yuri Leonov

    8 de abril de 2020 a las 13:34

  • @YuriyLeonov distinto=Verdadero utilizado para que la operación se realice en un valor distinto. No está relacionado con la pregunta actual. Lo siento por eso, en realidad lo he usado en mi código.

    – Vinay Kumar

    8 de abril de 2020 a las 13:48

Esa es la principal diferencia, pero los agregados también funcionan a mayor escala que las anotaciones. Las anotaciones están inherentemente relacionadas con elementos individuales en un conjunto de consultas. Si ejecuta un Count anotación en algo así como un campo de muchos a muchos, obtendrá un recuento separado para cada miembro del conjunto de consultas (como un atributo agregado). Sin embargo, si tuviera que hacer lo mismo con una agregación, intentaría contar cada relación en cada miembro del conjunto de consultas, incluso duplicados, y lo devuelve como un solo valor.

  • ¿Estoy en lo correcto? .annotate() en un qs solo no golpea el db, sino que llama al resultado de una anotación como q[0].num_authors ¿hace? Asumo aggregate siempre debe golpear el db ya que es una cláusula terminal?

    – alias51

    30 de julio de 2020 a las 23:07

Kai - Avatar de usuario de Kazuya Ito
Kai-Kazuya Ito

  • agregar() can calcula todos los valores de la columna de un modelo. *Se devuelve un diccionario.

  • anotar() puede calcular todas las claves externas de la columna de un modelo secundario por clave externa.

*Promedio(), Contar(), máx(), Min(), Suma() y así sucesivamente se puede utilizar con aggregate() y annotate().

por ejemplo, hay Category y Product modelos a continuación:

# "models.py"

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=20)

class Product(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    name = models.CharField(max_length=50)
    price = models.DecimalField(decimal_places=2, max_digits=5)

Y aquí están Category y Product administradores a continuación:

# "admin.py"

from django.contrib import admin
from .models import Category, Product 

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('id', 'name')
    ordering = ('id',)

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ('id', 'category_id', 'category', 'name', 'price')
    ordering = ('id',)

Y, hay 2 categorías a continuación:

ingrese la descripción de la imagen aquí

Y, hay 5 productos a continuación:

ingrese la descripción de la imagen aquí

Y ahí está test ver a continuación:

# "views.py"

from .models import Category, Product
from django.http import HttpResponse
from django.db.models import Avg
from django.db.models import Count
from django.db.models import Max
from django.db.models import Min
from django.db.models import Sum

def test(request):
    return HttpResponse("Test")

Primero, explico sobre aggregate().

agregar():

Ahora, corro test vista que tiene las columnas id, category y price en Promedio(), Contar(), máx(), Min() y Suma() en aggregate() Como se muestra abajo:

# "views.py"

# ...

def test(request):

    print(Product.objects.aggregate(Avg('id')))
    print(Product.objects.aggregate(Count('id')))
    print(Product.objects.aggregate(Max('id')))
    print(Product.objects.aggregate(Min('id')))
    print(Product.objects.aggregate(Sum('id')))
    print()
    print(Product.objects.aggregate(Avg('category')))
    print(Product.objects.aggregate(Count('category')))
    print(Product.objects.aggregate(Max('category')))
    print(Product.objects.aggregate(Min('category')))
    print(Product.objects.aggregate(Sum('category')))
    print()
    print(Product.objects.aggregate(Avg('price')))
    print(Product.objects.aggregate(Count('price')))
    print(Product.objects.aggregate(Max('price')))
    print(Product.objects.aggregate(Min('price')))
    print(Product.objects.aggregate(Sum('price')))
    
    return HttpResponse("Test")

Luego, estos diccionarios a continuación se muestran en la consola:

{'id__avg': 3.0}
{'id__count': 5}
{'id__max': 5}
{'id__min': 1}
{'id__sum': 15}

{'category__avg': 1.4}
{'category__count': 5}
{'category__max': 2}
{'category__min': 1}
{'category__sum': 7}

{'price__avg': Decimal('30.0000000000000000')}
{'price__count': 5}
{'price__max': Decimal('50.00')}
{'price__min': Decimal('10.00')}
{'price__sum': Decimal('150.00')}

Y, aggregate() puede aceptar varios tipos de columnas y funciones en cualquier orden, los mismos tipos de columnas y funciones y ninguna columna ni función, como se muestra a continuación. * Las múltiples columnas y funciones del mismo tipo se convierten en una y ninguna columna y función obtiene un diccionario vacío:

# "views.py"

# ...

def test(request):
    # Multiple kinds of columns and functions in any order
    print(
        Product.objects.aggregate(
            Max('price'), Max('category'), Sum('id'), Min('id')
        )
    )

    # The multiple same kind of columns and functions
    print(
        Product.objects.aggregate(
            Sum('price'), Sum('price'), Sum('price')
        )
    )

    # No columns and functions
    print(Product.objects.aggregate())
        
    return HttpResponse("Test")

Luego, estos diccionarios a continuación se muestran en la consola:

{'price__max': Decimal('50.00'), 'category__max': 2, 'id__sum': 15, 'id__min': 1}
{'price__sum': Decimal('150.00')}
{}

Y, Max() y Min() a continuación puede aceptar tipos no numéricos:

# "views.py"

# ...

def test(request):

    print(Product.objects.aggregate(Count('name')))
    print(Product.objects.aggregate(Max('name')))
    print(Product.objects.aggregate(Min('name')))
        
    return HttpResponse("Test")

Luego, estos diccionarios a continuación se muestran en la consola:

{'name__count': 5}
{'name__max': 'Tea'}
{'name__min': 'Apple'}

Pero, Avg() y Sum() a continuación no puede aceptar tipos no numéricos:

# "views.py"

# ...

def test(request):

    print(Product.objects.aggregate(Avg('name')))
    print(Product.objects.aggregate(Sum('name')))
        
    return HttpResponse("Test")

Por lo tanto, se producen los siguientes errores:

django.db.utils.ProgrammingError: la función avg (carácter variable) no existe

django.db.utils.ProgrammingError: la función sum (caracter variable) no existe

Y puede cambiar los nombres de clave predeterminados como se muestra a continuación:

# "views.py"

# ...

def test(request):

    print(Product.objects.aggregate(priceAve=Avg('price')))
    print(Product.objects.aggregate(priceCount=Count('price')))
    print(Product.objects.aggregate(priceMax=Max('price')))
    print(Product.objects.aggregate(priceMin=Min('price')))
    print(Product.objects.aggregate(priceSum=Sum('price')))
        
    return HttpResponse("Test")

Luego, los nombres de clave predeterminados se cambian como se muestra a continuación:

{'priceAve': Decimal('30.0000000000000000')}
{'priceCount': 5}
{'priceMax': Decimal('50.00')}
{'priceMin': Decimal('10.00')}
{'priceSum': Decimal('150.00')}

A continuación, explico sobre annotate().

anotar():

Ahora, corro test vista que tiene las columnas product__id, product__category y product__price en Avg(), Count(), Max(), Min() y Sum() en annotate() Como se muestra abajo. * Tienes que poner __avg, __count, __max, __min y __sum a product__id, product__category y product__price para Avg(), Count(), Max(), Min() y Sum() respectivamente:

# "views.py"

# ...

def test(request):

    qs = Category.objects.annotate(
        Avg('product__id'), 
        Count('product__id'), 
        Max('product__id'), 
        Min('product__id'), 
        Sum('product__id')
    ).order_by('pk')

    for obj in qs:
        print(
            obj.id, 
            obj.name, 
            obj.product__id__avg, 
            obj.product__id__count, 
            obj.product__id__max, 
            obj.product__id__min, 
            obj.product__id__sum
        )

    print()

    qs = Category.objects.annotate(
        Avg('product__category'), 
        Count('product__category'), 
        Max('product__category'), 
        Min('product__category'), 
        Sum('product__category')
    ).order_by('pk')

    for obj in qs:
        print(
            obj.id, 
            obj.name, 
            obj.product__category__avg, 
            obj.product__category__count, 
            obj.product__category__max, 
            obj.product__category__min, 
            obj.product__category__sum
        )

    print()

    qs = Category.objects.annotate(
        Avg('product__price'), 
        Count('product__price'), 
        Max('product__price'), 
        Min('product__price'), 
        Sum('product__price')
    ).order_by('pk')

    for obj in qs:
        print(
            obj.id, 
            obj.name, 
            obj.product__price__avg, 
            obj.product__price__count, 
            obj.product__price__max, 
            obj.product__price__min, 
            obj.product__price__sum
        )
    
    return HttpResponse("Test")

Luego, estos a continuación se emiten en la consola:

1 Food 2.0 3 3 1 6
2 Drink 4.5 2 5 4 9

1 Food 1.0 3 1 1 3
2 Drink 2.0 2 2 2 4

1 Food 20.0000000000000000 3 30.00 10.00 60.00
2 Drink 45.0000000000000000 2 50.00 40.00 90.00

Y, la consulta sin order_by(‘pk’) a continuación hace que el orden sea descendiente:

# "views.py"

# ...

def test(request):

    qs = Category.objects.annotate(
        Avg('product__price'), 
        Count('product__price'), 
        Max('product__price'), 
        Min('product__price'), 
        Sum('product__price')
    ) # Without ".order_by('pk')"

    for obj in qs:
        print(
            obj.id, 
            obj.name, 
            obj.product__price__avg, 
            obj.product__price__count, 
            obj.product__price__max, 
            obj.product__price__min, 
            obj.product__price__sum
        )
    
    return HttpResponse("Test")

Entonces, el orden es descendiente como se muestra a continuación:

2 Drink 4.5 2 5 4 9
1 Food 2.0 3 3 1 6

Y, vacío annotate() abajo tiene id y name atributos:

# "views.py"

# ...

def test(request): 
                         # Empty "annotate()"
    qs = Category.objects.annotate().order_by('pk')

    for obj in qs:
        print(obj.id, obj.name)
        
    return HttpResponse("Test")

Luego, estos a continuación se emiten en la consola:

1 Food
2 Drink

Pero, vacío annotate() abajo no tiene __avg, __count, __max, __min y __sum atributos como se muestra a continuación:

# "views.py"

# ...

def test(request): 
                         # Empty "annotate()"
    qs = Category.objects.annotate().order_by('pk')
    
    for obj in qs:
        print(
            obj.product__price__avg,
            obj.product__price__count,
            obj.product__price__max,
            obj.product__price__min,
            obj.product__price__sum,
        )
        
    return HttpResponse("Test")

Por lo tanto, se producen los siguientes errores:

AttributeError: el objeto ‘Categoría’ no tiene el atributo ‘product__price__avg’

AttributeError: el objeto ‘Categoría’ no tiene el atributo ‘product__price__count’

AttributeError: el objeto ‘Categoría’ no tiene el atributo ‘product__price__max’

AttributeError: el objeto ‘Categoría’ no tiene el atributo ‘product__price__min’

AttributeError: el objeto ‘Categoría’ no tiene el atributo ‘product__price__sum’

Y, Max() y Min() a continuación puede aceptar tipos no numéricos:

# "views.py"

# ...

def test(request):

    qs = Category.objects.annotate(
        Count('product__name'), 
        Max('product__name'), 
        Min('product__name'), 
    ).order_by('pk')

    for obj in qs:
        print( 
            obj.product__name__count, 
            obj.product__name__max, 
            obj.product__name__min, 
        )

    return HttpResponse("Test")

Luego, estos a continuación se emiten en la consola:

3 Orange Apple
2 Tea Milk

Pero, Avg() y Sum() a continuación no puede aceptar tipos no numéricos:

# "views.py"

# ...

def test(request):

    qs = Category.objects.annotate(
        Avg('product__name'),
        Sum('product__name')
    ).order_by('pk')

    for obj in qs:
        print( 
            obj.product__name__avg, 
            obj.product__name__sum
        )
        
    return HttpResponse("Test")

Por lo tanto, se producen los siguientes errores:

django.db.utils.ProgrammingError: la función avg (carácter variable) no existe

django.db.utils.ProgrammingError: la función sum (caracter variable) no existe

Y puede cambiar los nombres de atributos predeterminados como se muestra a continuación:

# "views.py"

# ...

def test(request):

    qs = Category.objects.annotate(
        productPriceAvg=Avg('product__price'), 
        productPriceCount=Count('product__price'), 
        productPriceMax=Max('product__price'), 
        productPriceMin=Min('product__price'), 
        productPriceSum=Sum('product__price')
    ).order_by('pk')

    for obj in qs:
        print(
            obj.id, 
            obj.name, 
            obj.productPriceAvg, 
            obj.productPriceCount, 
            obj.productPriceMax, 
            obj.productPriceMin, 
            obj.productPriceSum
        )
    
    return HttpResponse("Test")

Luego, estos a continuación se emiten en la consola:

1 Food 20.0000000000000000 3 30.00 10.00 60.00
2 Drink 45.0000000000000000 2 50.00 40.00 90.00

¿Ha sido útil esta solución?