كيفية كتابة استعلام DISTINCT بكفاءة في Django مع وجود جدول يحتوي على مفاتيح خارجية
-
21-12-2019 - |
سؤال
أريد إظهار المدن المميزة للمستخدمين في القائمة المنسدلة للواجهة الأمامية.لذلك، أقوم بإجراء استعلام قاعدة البيانات الذي يجلب مميزًا city_name
من الجدول City
ولكن فقط تلك المدن التي يتواجد فيها المستخدمون.
شيء مثل أدناه يعمل لحجم صغير User
الجدول، ولكن يستغرق وقتا طويلا جدا إذا User
طاولة بحجم 10 مليونلا تزال المدن المميزة لهؤلاء المستخدمين تصل إلى 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 مللي ثانية
أظهر ذلك بوضوح أن PostgreSQL لم يكن يستخدم الفهرس الموجود على "user".'city_id'.قرأت أيضًا عن الحل البديل هنا والتي تضمنت كتابة استعلام SQL مخصص يستخدم بطريقة أو بأخرى ملف Index.
لقد حاولت العثور على 'user'.'city_id' مميز باستخدام الاستعلام أعلاه، وتبين أن ذلك كان سريعًا جدًا.
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 مللي ثانية
ولكن الآن أجد صعوبة في دمج هذا في كود جانغو الخاص بي.ما زلت أعتقد أنه نوع من الاختراق بإضافة استعلام مخصص في الكود لهذا الغرض.لكن ما يثير القلق الأكبر بالنسبة لي هو أن اسم العمود يمكن أن يكون ديناميكيًا تمامًا، ولا يمكنني ترميز أسماء الأعمدة هذه (على سبيل المثال.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()
سيتطلب استخدام الاستعلام المخصص على الأقل تقسيم اسم الحقل باستخدام "__" كمحدد ومعالجة الجزء الأول.ولكن يبدو وكأنه اختراق سيء بالنسبة لي.
كيف يمكنني تحسين هذا؟
ملاحظة.ال City
User
يظهر المثال فقط لشرح السيناريو.قد لا يكون بناء الجملة صحيحًا.
المحلول
لقد وصلت أخيرًا إلى هذا الحل البديل.
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
الجدول، يعمل بسرعة كبيرة.