Django yalnızca yinelenen alan değerlerine sahip satırları seçer


99

django'da aşağıdaki gibi tanımlanan bir modelimiz olduğunu varsayalım:

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

Ad alanı benzersiz değildir ve bu nedenle yinelenen değerlere sahip olabilir. Aşağıdaki görevi gerçekleştirmem gerekiyor: Modelden , alanın en az bir yinelenen değerine sahip tüm satırları seçin name.

Düz SQL kullanarak nasıl yapılacağını biliyorum (en iyi çözüm olmayabilir):

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

Öyleyse, bunu django ORM kullanarak seçmek mümkün mü? Veya daha iyi SQL çözümü?

Yanıtlar:


199

Deneyin:

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

Bu, Django ile olabildiğince yakın. Sorun şu ki, bu ValuesQuerySetyalnızca nameve ile a döndürür count. Ancak, daha sonra QuerySetbunu başka bir sorguya geri besleyerek normal oluşturmak için kullanabilirsiniz :

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

5
Muhtemelen demek istedin Literal.objects.values('name').annotate(name_count=Count('name')).filter(name_count__gt=1)?
ejderha

Orijinal sorgu verirCannot resolve keyword 'id_count' into field
ejderha

2
Güncellenen cevap için teşekkürler, sanırım bu çözüme bağlı kalacağım, hatta bunu kullanarak listeyi anlamadan bile yapabilirsinizvalues_list('name', flat=True)
dragoon

1
Django'nun daha önce bu konuda bir hatası vardı (son sürümlerde düzeltilmiş olabilir), burada Countek açıklama için kaydedilecek bir alan adı belirtmezseniz , varsayılan olarak [field]__count. Bununla birlikte, bu çift alt çizgi sözdizimi aynı zamanda Django'nun bir birleştirme yapmak istediğinizi nasıl yorumladığıdır. Yani, esasen bunu filtrelemeye çalıştığınızda, Django countaçıkça var olmayan bir birleştirme yapmaya çalıştığınızı düşünüyor . Düzeltme, açıklama sonucunuz için bir ad belirtmek, yani bunun yerine annotate(mycount=Count('id'))filtre uygulamaktır mycount.
Chris Pratt

1
Not eklemek values('name')için çağrınızdan sonra başka bir çağrı eklerseniz , liste anlayışını kaldırabilir ve bunun Literal.objects.filter(name__in=dupes)tek bir sorguda yürütülmesine izin vereceğini söyleyebilirsiniz .
Piper Merriam

45

Bu bir düzenleme olarak reddedildi. Yani burada daha iyi bir cevap olarak

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

Bu ValuesQuerySet, yinelenen adların tümü ile bir döndürür . Ancak, daha sonra QuerySetbunu başka bir sorguya geri besleyerek normal oluşturmak için kullanabilirsiniz . Django ORM, bunları tek bir sorguda birleştirecek kadar akıllıdır:

Literal.objects.filter(name__in=dups)

.values('name')Açıklama aramasından sonra gelen ekstra arama biraz garip görünüyor. Bu olmadan alt sorgu başarısız olur. Ekstra değerler, ORM'yi yalnızca alt sorgu için ad sütununu seçmesi için kandırır.


Güzel numara, maalesef bu yalnızca tek bir değer kullanıldığında işe yarayacaktır (örneğin, hem 'isim' hem de 'telefon' kullanıldığında, son kısım çalışmayacaktır).
guival

1
Ne .order_by()için?
stefanfoulis

4
@stefanfoulis Mevcut siparişleri kaldırır. Bir model seti sıralamanız varsa, bu SQL GROUP BYcümlesinin bir parçası haline gelir ve bu, işleri bozar. Bunu Alt Sorgu ile oynarken öğrendiniz (burada çok benzer gruplama yaptığınız .values())
Oli

10

toplama kullanmayı deneyin

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

Tamam, bu doğru isim listesini veriyor, ancak kimlikleri ve diğer alanları aynı anda seçmek mümkün mü?
ejderha

@dragoon - hayır ama Chris Pratt cevabındaki alternatifi ele aldı.
JamesO

5

PostgreSQL kullanıyorsanız, şöyle bir şey yapabilirsiniz:

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

Bu oldukça basit bir SQL sorgusu ile sonuçlanır:

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

0

Nesneleri değil, yalnızca isimleri listelemek istiyorsanız, aşağıdaki sorguyu kullanabilirsiniz

repeated_names = Literal.objects.values('name').annotate(Count('id')).order_by().filter(id__count__gt=1).values_list('name', flat='true')
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.