with what tutorial para framework español develop applications django django-models django-queryset

what - tutorial de django



django comenta y cuenta: cómo filtrar los que se incluirán en la cuenta (3)

Dado un queryset, agrego el recuento de objetos relacionados (Modelo A) con lo siguiente:

qs = User.objets.all() qs.annotate(modela__count=models.Count(''modela''))

Sin embargo, ¿hay una manera de contar el Modelo A que solo cumpla con un criterio? Por ejemplo, cuente el ModelA donde deleted_at es nulo?

He intentado dos soluciones que no funcionan correctamente.

1) Como sugirió @knbk, use el filtro antes de anotar.

qs = User.objects.all().filter(modela__deleted_at__isnull=True).annotate(modela__count=models.Count(''modela'', distinct=True))

Aquí está la versión simplificada de la consulta generada por django:

SELECT COUNT(DISTINCT "modela"."id") AS "modela__count", "users".* FROM "users" LEFT OUTER JOIN "modela" ON ( "users"."id" = "modela"."user_id" ) WHERE "modela"."deleted_at" IS NULL GROUP BY "users"."id"

El problema viene de la cláusula WHERE. De hecho, hay un IZQUIERDA ÚNICA, pero las últimas condiciones DÓNDE la obligaron a ser una ÚNICA ÚNICA. Necesito poner las condiciones en la cláusula JOIN para que funcione según lo previsto.

Entonces, en lugar de

LEFT OUTER JOIN "modela" ON ( "users"."id" = "modela"."user_id" ) WHERE "modela"."deleted_at" IS NULL

Necesito lo siguiente que funciona cuando lo ejecuto directamente en SQL simple.

LEFT OUTER JOIN "modela" ON ( "users"."id" = "modela"."user_id" ) AND "modela"."deleted_at" IS NULL

¿Cómo puedo cambiar el queryset para obtener esto sin hacer una consulta en bruto?

2) Como otros sugirieron, podría usar una agregación condicional.

Intenté lo siguiente:

qs = User.objects.all().annotate(modela__count=models.Count(Case(When(modela__deleted_at__isnull=True, then=1))))

que se convierte en la siguiente consulta SQL:

SELECT COUNT(CASE WHEN "modela"."deleted_at" IS NULL THEN 1 ELSE NULL END) AS "modela__count", "users".* FROM "users" LEFT OUTER JOIN "modela" ON ( "users"."id" = "modela"."user_id" ) GROUP BY "users"."id"

Al hacer eso, recibo a todos los usuarios (por lo tanto, la IZQUIERDA A LA IZQUIERDA funciona correctamente) pero obtengo "1" (en lugar de 0) para modela__count para todos los usuarios que no tienen ningún ModelA. ¿Por qué obtengo 1 y no 0 si no hay nada que contar? ¿Cómo se puede cambiar eso?


En Django 1.8 creo que esto se puede lograr con la agregación condicional . Sin embargo para versiones anteriores lo haría con .extra

ModelA.objects.extra(select={ ''account_count'': ''SELECT COUNT(*) FROM account WHERE modela.account_id = account.id AND account.some_prop IS NOT NULL'' })


En una modela LEFT JOIN , todos los campos de modela podrían ser NULL debido a la ausencia de la fila correspondiente. Asi que

modela.deleted_at IS NULL

... no solo es cierto para las filas coincidentes, sino también para aquellos users que no modela filas de modela correspondiente.

Creo que el SQL correcto debería ser:

SELECT COUNT( CASE WHEN `modela`.`user_id` IS NOT NULL -- Make sure modela rows exist AND `modela`.`deleted_at` IS NULL THEN 1 ELSE NULL END ) AS `modela__count`, `users`.* FROM `users` LEFT OUTER JOIN `modela` ON ( `users`.`id` = `modela`.`user_id` ) GROUP BY `users`.`id`

En Django 1.8 esto debería ser:

from django.db import models qs = User.objects.all().annotate( modela_count=models.Count( models.Case( models.When( modela__user_id__isnull=False, modela__deleted_at__isnull=True, then=1, ) ) ) )

Aviso :

@YAmikep descubrió que un error en Django 1.8.0 hace que el SQL generado tenga un INNER JOIN lugar de un LEFT JOIN , por lo que perderá filas sin la correspondiente relación de clave externa. Utilice la versión Django 1.8.2 o superior para solucionarlo.


Simplemente puede filtrar antes de anotar:

from django.db.models import Q, Count qs = ModelA.objects.filter(account__prop1__isnull=False).annotate(account_count=Count(''account''))