외래 키가있는 테이블을 사용하여 Django에서 뚜렷한 쿼리를 효율적으로 쓰는 방법
-
21-12-2019 - |
문제
프런트 엔드 드롭 다운에서 사용자의 별개의 도시를 표시하고 싶습니다. 이를 위해 DB city_name
테이블에서 Distinct City
를 가져 오는 DB 쿼리를 만듭니다. 그러나 사용자가있는 도시만을 사용하십시오.
아래의 작은 크기의 User
테이블의 크기가 작지만 User
테이블의 크기가 천만에있는 경우 매우 오랜 시간이 걸립니다. 이 사용자들의 뚜렷한 도시는 여전히 ~ 100입니다.
class City(models.Model):
city_code = models.IntegerField(unique=True)
city_name = models.CharField(max_length=256)
class User(models.Model):
city = models.ForeignKey('City', to_field='city_code')
.
이제는
와 같은 별개의 도시 이름을 검색하려고합니다.City.objects.filter().values_list('city__city_name').distinct()
.
PostgreSQL 에서이 트랜스 패스를 해제합니다.
SELECT DISTINCT "city"."city_name"
FROM "user"
LEFT OUTER JOIN "city"
ON ("user"."city_id" = "city"."city_code");
.
시간 : 9760.302 ms
PostgreSQL이 '사용자'에서 인덱스를 사용하지 않았 음을 명확하게 보여주었습니다. 'City_id'. 또한 해결 방법 글쓰기와 관련된 어떻게 든 색인을 활용하는 사용자 정의 SQL 쿼리입니다.나는 뚜렷한 '사용자'를 찾으려고 노력했다. 위의 쿼리를 사용하고 실제로 꽤 빨리 밝혀졌습니다.
WITH
RECURSIVE t(n) AS
(SELECT min(city_id)
FROM user
UNION
SELECT
(SELECT city_id
FROM user
WHERE city_id > n order by city_id limit 1)
FROM t
WHERE n is not null)
SELECT n
FROM t;
.
시간 : 79.056 ms
그러나 이제는 Django 코드에 이것을 통합하기가 어려워지고 있습니다. 나는 이것을 위해 코드에 맞춤 쿼리를 추가하는 일종의 해킹이라고 생각합니다. 그러나 나에게 더 큰 관심사는 열 이름이 완전히 역동적 일 수 있으며 코드 에서이 열 이름 (예 : City_ID 등)을 하드 코드 할 수 없다는 것입니다.
#original_fields could be a list from input, like ['area_code__district_code__name']
dataset_klass.objects.filter().values_list(*original_fields).distinct()
.
사용자 지정 쿼리를 사용하면 atleast가 구분 기호로 '__'을 사용하여 필드 이름을 분리하고 첫 번째 부분을 처리해야합니다. 그러나 그것은 나에게 나쁜 해킹처럼 보입니다.
어떻게 개선 할 수 있습니까?
ps. City
예제는 시나리오를 설명하는 것으로 표시됩니다. 구문이 올바르지 않을 수 있습니다.
해결책
마침내이 해결 방법 해결책에 도달했습니다.
from django.db import connection, transaction
original_field = 'city__city_name'
dataset_name = 'user'
dataset_klass = eval(camelize(dataset_name))
split_arr = original_field.split("__",1)
"""If a foreign key relation is present
"""
if len(split_arr) > 1:
parent_field = dataset_klass._meta.get_field_by_name(split_arr[0])[0]
cursor = connection.cursor()
"""This query will run fast only if parent_field is indexed (city_id)
"""
cursor.execute('WITH RECURSIVE t(n) AS ( select min({0}) from {1} '
'union select (select {0} from {1} where {0} > n'
' order by {0} limit 1) from t where n is not null) '
'select n from t;'.format(parent_field.get_attname_column()[1], dataset_name))
"""Create a list of all distinct city_id's"""
distinct_values = [single[0] for single in cursor.fetchall()]
"""create a dict of foreign key field to the above list"""
"""to get the actual city_name's using _meta information"""
filter_dict = {parent_field.rel.field_name+'__in':distinct_values}
values = parent_field.rel.to.objects.filter(**filter_dict).values_list(split_arr[1])
else:
values = dataset_klass.objects.filter().values_list(original_field).distinct()
.
city_id
테이블의 user
의 인덱스를 사용하는
은 꽤 빨리 실행됩니다.