Django chỉ chọn các hàng có giá trị trường trùng lặp


96

giả sử chúng ta có một mô hình trong django được định nghĩa như sau:

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

Trường tên không phải là duy nhất và do đó có thể có các giá trị trùng lặp. Tôi cần hoàn thành nhiệm vụ sau: Chọn tất cả các hàng từ mô hình có ít nhất một giá trị trùng lặp của nametrường.

Tôi biết cách thực hiện bằng cách sử dụng SQL thuần túy (có thể không phải là giải pháp tốt nhất):

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

Vì vậy, có thể chọn điều này bằng cách sử dụng django ORM không? Hay giải pháp SQL tốt hơn?

Câu trả lời:


192

Thử:

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

Điều này gần như bạn có thể đạt được với Django. Vấn đề là điều này sẽ trả về a ValuesQuerySetonly nameand count. Tuy nhiên, sau đó bạn có thể sử dụng điều này để tạo một quy tắc QuerySetbằng cách đưa nó trở lại vào một truy vấn khác:

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
Có lẽ bạn có nghĩa là Literal.objects.values('name').annotate(name_count=Count('name')).filter(name_count__gt=1)?
dragoon

Original truy vấn choCannot resolve keyword 'id_count' into field
dragoon

2
Cảm ơn cho câu trả lời được cập nhật, tôi nghĩ tôi sẽ gắn bó với giải pháp này, bạn thậm chí có thể làm điều đó mà không cần danh sách hiểu bằng cách sử dụngvalues_list('name', flat=True)
dragoon

1
Django trước đây đã có một lỗi về điều này (có thể đã được sửa trong các phiên bản gần đây) trong đó nếu bạn không chỉ định tên trường cho Countchú thích để lưu dưới dạng, nó sẽ mặc định thành [field]__count. Tuy nhiên, cú pháp gạch dưới kép đó cũng là cách Django diễn giải bạn muốn thực hiện một phép nối. Vì vậy, về cơ bản khi bạn cố gắng lọc điều đó, Django nghĩ rằng bạn đang cố gắng thực hiện một liên kết countmà rõ ràng là không tồn tại. Cách khắc phục là chỉ định tên cho kết quả chú thích của bạn, tức là annotate(mycount=Count('id'))và sau đó lọc tiếp mycount.
Chris Pratt

1
nếu bạn thêm một lệnh gọi khác vào values('name')sau lệnh gọi chú thích, bạn có thể xóa phần hiểu danh sách và nói Literal.objects.filter(name__in=dupes)điều này sẽ cho phép tất cả điều này được thực thi trong một truy vấn duy nhất.
Piper Merriam

42

Điều này đã bị từ chối dưới dạng một bản chỉnh sửa. Vì vậy, đây là một câu trả lời tốt hơn

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

Điều này sẽ trả về a ValuesQuerySetvới tất cả các tên trùng lặp. Tuy nhiên, sau đó bạn có thể sử dụng nó để xây dựng một thông thường QuerySetbằng cách đưa nó trở lại vào một truy vấn khác. ORM django đủ thông minh để kết hợp chúng thành một truy vấn duy nhất:

Literal.objects.filter(name__in=dups)

Cuộc gọi bổ sung tới .values('name')sau cuộc gọi chú thích trông hơi lạ. Nếu không có điều này, truy vấn con không thành công. Các giá trị phụ đánh lừa ORM chỉ chọn cột tên cho truy vấn con.


Mẹo hay, tiếc là điều này sẽ chỉ hoạt động nếu chỉ một giá trị được sử dụng (ví dụ: nếu cả 'tên' và 'điện thoại' được sử dụng, phần cuối cùng sẽ không hoạt động).
guival

1
Để làm gì .order_by()?
stefanfoulis

4
@stefanfoulis Nó xóa mọi thứ tự hiện có. Nếu bạn có một thứ tự theo tập mô hình, điều này sẽ trở thành một phần của GROUP BYmệnh đề SQL , và điều đó sẽ phá vỡ mọi thứ. Tìm thấy điều đó khi chơi với Truy vấn con (trong đó bạn thực hiện nhóm rất giống nhau qua .values())
Oli

10

thử sử dụng tổng hợp

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

Ok, điều đó cung cấp danh sách tên chính xác, nhưng có thể chọn id và các trường khác cùng một lúc không?
dragoon

@dragoon - không nhưng Chris Pratt đã đưa ra phương án thay thế trong câu trả lời của mình.
JamesO

5

Trong trường hợp bạn sử dụng PostgreSQL, bạn có thể làm như sau:

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

Nó dẫn đến truy vấn SQL khá đơn giản này:

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

Nếu bạn chỉ muốn kết quả danh sách tên chứ không phải các đối tượng, bạn có thể sử dụng truy vấn sau

repeated_names = Literal.objects.values('name').annotate(Count('id')).order_by().filter(id__count__gt=1).values_list('name', flat='true')
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.