2012-01-24 23 views
65

supongamos que tenemos un modelo de Django definido de la siguiente manera:Django seleccionar sólo las filas con valores de campo duplicados

class Literal: 
    name = models.CharField(...) 
    ... 

campo Nombre no es único, y por lo tanto pueden tener valores duplicados. Necesito realizar la siguiente tarea: Seleccione todas las filas del modelo que tienen al menos un valor duplicado del campo name.

sé cómo hacerlo utilizando SQL normal (no puede ser la mejor solución):

select * from literal where name IN (
    select name from literal group by name having count((name)) > 1 
); 

lo tanto, es posible seleccionar esta usando ORM de Django? O mejor solución de SQL?

Respuesta

129

Probar:

from django.db.models import Count 
Literal.objects.values('name') 
       .annotate(Count('id')) 
       .order_by() 
       .filter(id__count__gt=1) 

Esto es lo más cerca que se puede obtener con Django. El problema es que esto devolverá un ValuesQuerySet con solo name y count. Sin embargo, a continuación, puede utilizar este para construir un habitual QuerySet alimentándolo de nuevo en otra consulta:

dupes = Literal.objects.values('name') 
         .annotate(Count('id')) 
         .order_by() 
         .filter(id__count__gt=1) 
Literal.objects.filter(name__in=[item['name'] for item in dupes]) 
+4

Probablemente ha querido decir '' Literal.objects.values ​​('name'). annotate (name_count = Count ('nombre')). Filter (name_count__gt = 1) ''? – dragoon

+1

'name' puede no ser único, pero estoy bastante seguro de que' id' es. –

+0

La consulta original da '' No se puede resolver la palabra clave 'id_count' en el campo'' – dragoon

9

tratar de usar aggregation

Literal.objects.values('name').annotate(name_count=Count('name')).exclude(name_count=1) 
+0

Ok, eso da la lista de nombres corrent, pero ¿es posible seleccionar identificadores y otros campos al mismo tiempo? – dragoon

+0

@dragoon - no, pero Chris Pratt ha cubierto la alternativa en su respuesta. – JamesO

26

Esto fue rechazado como una edición. Así que aquí es como mejor respuesta

dups = (
    Literal.objects.values('name') 
    .annotate(count=Count('id')) 
    .values('name') 
    .order_by() 
    .filter(count__gt=1) 
) 

Esto devolverá un ValuesQuerySet con todos los nombres duplicados. Sin embargo, puede usar esto para construir un QuerySet regular alimentándolo de nuevo a otra consulta. El ORM de Django es lo suficientemente inteligente como para combinarlos en una sola consulta:

Literal.objects.filter(name__in=dups) 

La llamada extra para .values ​​('nombre') después de la llamada annotate parece un poco extraño. Sin esto, la subconsulta falla. Los valores adicionales engañan al orm para que solo seleccione la columna de nombre para la subconsulta.

+0

Buen truco, desafortunadamente esto solo funcionará si solo se usa un valor (por ejemplo, si se usaron tanto 'nombre' como 'teléfono', la última parte no funcionaría). – guival

+1

¿Para qué es el '.order_by()'? – stefanfoulis

+2

@stefanfoulis Borra cualquier pedido existente. Si tiene un orden de conjunto de modelos, esto se convierte en parte de la cláusula SQL 'GROUP BY', y eso rompe las cosas. Lo descubrí al jugar con Subquery (en el que haces un agrupamiento muy similar a través de '.values ​​()') – Oli

0

Si desea la lista de resultados únicos nombres pero no los objetos, puede utilizar la siguiente consulta

repeated_names = Literal.objects.values('name').annotate(Count('id')).order_by().filter(id__count__gt=1).values_list('name', flat='true') 
0

En caso de usar PostgreSQL, puede hacer algo como esto:

from django.contrib.postgres.aggregates import ArrayAgg 
from django.db.models import Func, Value 

duplicate_ids = (Literal.objects.values('name') 
       .annotate(ids=ArrayAgg('id')) 
       .annotate(c=Func('ids', Value(1), function='array_length')) 
       .filter(c__gt=1) 
       .annotate(ids=Func('ids', function='unnest')) 
       .values_list('ids', flat=True)) 

Se resultados en esta consulta SQL bastante simple:

SELECT unnest(ARRAY_AGG("app_literal"."id")) AS "ids" 
FROM "app_literal" 
GROUP BY "app_literal"."name" 
HAVING array_length(ARRAY_AGG("app_literal"."id"), 1) > 1 
Cuestiones relacionadas