2012-01-24 19 views
65

załóżmy, że mamy model w Django zdefiniowane następująco:Django tylko wybrane wiersze z zduplikowanych wartości pól

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

Nazwa pola nie jest wyjątkowy, a zatem mogą mieć zduplikowane wartości. Muszę wykonać następujące zadanie: Zaznacz wszystkie wiersze z modelu, które mają co najmniej jedną zduplikowaną wartość pola name.

wiem jak to zrobić przy użyciu zwykłego języka SQL (może nie być najlepszym rozwiązaniem):

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

Tak, to jest możliwe, aby wybrać tę Django ORM? Lub lepsze rozwiązanie SQL?

Odpowiedz

129

Spróbuj:

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

To jest tak blisko jak można dostać się z Django. Problem polega na tym, że zwróci to ValuesQuerySet tylko z name i count. Jednakże, można następnie wykorzystać do skonstruowania regularne QuerySet przez karmienie go z powrotem do innego zapytania:

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

Prawdopodobnie miałeś na myśli '' Literal.objects.values ​​('name'). adnotate (name_count = Count ("name")). Filter (name_count__gt = 1) ''? – dragoon

+1

'name' może nie być unikatowe, ale jestem prawie pewien, że' id' jest. –

+0

Oryginalne zapytanie daje "Nie można rozwiązać słowa kluczowego" id_count "w polu" ' – dragoon

9

spróbuj aggregation

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

OK, która podaje aktualną listę nazw, ale czy możliwe jest wybranie identyfikatorów ids i innych pól w tym samym czasie? – dragoon

+0

@dragoon - nie, ale Chris Pratt omówił alternatywę w swojej odpowiedzi. – JamesO

26

ten został odrzucony jako edit. Więc to jest tutaj jako lepszego odpowiedź

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

będzie to powrót do ValuesQuerySet ze wszystkich zduplikowanych nazw. Można jednak użyć tego do skonstruowania zwykłego zestawu kwerendy, przesyłając go z powrotem do innego zapytania. Django ORM jest wystarczająco inteligentny, aby połączyć je w jednym zapytaniu:

Literal.objects.filter(name__in=dups) 

Dodatkowy wywołanie .values ​​(„nazwa”) po wywołaniu opisywanie wygląda trochę dziwnie. Bez tego podzapytanie kończy się niepowodzeniem. Dodatkowe wartości powodują, że użytkownik wybiera tylko kolumnę nazwy podzapytania.

+0

Dobra sztuczka, niestety to zadziała tylko wtedy, gdy zostanie użyta tylko jedna wartość (np. Jeśli zarówno nazwa, jak i telefon będą używane, ostatnia część nie zadziała). – guival

+1

Do czego służy funkcja '.order_by()'? – stefanfoulis

+2

@stefanfoulis Usuwa wszelkie istniejące porządki. Jeśli masz uporządkowanie zestawów modelowych, staje się to częścią klauzuli SQL GROUP BY i łamie rzeczy. Odkryłem to podczas grania z podkwerendą (w której robisz bardzo podobne grupowanie za pomocą '.values ​​()') – Oli

0

Jeśli chcesz prowadzić listę tylko nazwy, ale nie obiektów, można użyć następującego zapytania

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

W przypadku używasz PostgreSQL, można zrobić coś takiego:

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

To wyniki w tym dość proste zapytanie SQL:

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 
Powiązane problemy