2012-01-24 19 views
65

supponiamo di avere un modello di Django definiti come segue:Django selezionare solo le righe con valori di campo duplicati

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

campo del nome non è unico, e quindi possono avere valori duplicati. Devo eseguire la seguente attività: Selezionare tutte le righe dal modello che hanno almeno un valore duplicato del campo name.

so come farlo utilizzando SQL pianura (potrebbe non essere la soluzione migliore):

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

Quindi, è possibile selezionare questo utilizzando Django ORM? O meglio una soluzione SQL?

risposta

129

Prova:

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

Questo è il più vicino si può ottenere con Django. Il problema è che questo restituirà un ValuesQuerySet con solo name e count. Tuttavia, è possibile quindi utilizzare questo per costruire un regolare QuerySet alimentando nuovamente in un'altra query:

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

Probabilmente si è voluto dire '' Literal.objects.values ​​('name'). annotate (name_count = Count ('name')). Filtrare (name_count__gt = 1) ''? – dragoon

+1

'name' potrebbe non essere univoco, ma sono piuttosto sicuro che' id' sia. –

+0

La query originale dà '' Impossibile risolvere la parola chiave 'id_count' nel campo'' – dragoon

9

provare a utilizzare aggregation

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

Ok, questo fornisce l'elenco corretto dei nomi, ma è possibile selezionare gli ID e gli altri campi contemporaneamente? – dragoon

+0

@dragoon - No, ma Chris Pratt ha coperto l'alternativa nella sua risposta. – JamesO

26

Questa è stata respinta come una modifica. Così qui è come un meglio risposta

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

Ciò restituirà un ValuesQuerySet con tutti i nomi duplicati. Tuttavia, è possibile utilizzarlo per creare un QuerySet normale inserendolo nuovamente in un'altra query. L'ORM Django è abbastanza intelligente per combinare questi in una singola query:

Literal.objects.filter(name__in=dups) 

La chiamata in più per .values ​​('name') dopo la chiamata di annotazione sembra un po 'strano. Senza questo, la subquery fallisce. I valori extra fanno in modo che orm selezioni solo la colonna del nome per la sottoquery.

+0

Bel trucco, sfortunatamente questo funzionerà solo se viene usato un solo valore (ad esempio se sia 'nome' che 'telefono' dove usato, l'ultima parte non funzionerebbe). – guival

+1

A cosa serve '.order_by()'? – stefanfoulis

+2

@stefanfoulis Elimina tutti gli ordini esistenti. Se si dispone di un ordinamento modello-set, questo diventa parte della clausola SQL 'GROUP BY', e ciò rompe le cose. Scoprilo quando giochi con Subquery (in cui fai un raggruppamento molto simile tramite '.values ​​()') – Oli

0

Se si desidera risultati solo i nomi, ma non gli oggetti, è possibile utilizzare la seguente query

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

Nel caso in cui si utilizza PostgreSQL, si può fare qualcosa di simile:

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

E ' risultati in questa query SQL piuttosto semplice:

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 
Problemi correlati