Frage

Ich möchte im Front-End-Dropdown verschiedene Benutzerstädte anzeigen.Dazu erstelle ich eine Datenbankabfrage, die eindeutige Ergebnisse abruft city_name vom Tisch City aber nur die Städte, in denen Benutzer anwesend sind.

Etwas wie unten funktioniert für eine kleine Größe von User Tisch, dauert aber sehr lange, wenn User Tabelle in der Größe 10 Millionen.Die Zahl der einzelnen Städte dieser Nutzer beträgt jedoch immer noch etwa 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')

Jetzt versuche ich, nach bestimmten Städtenamen zu suchen:

City.objects.filter().values_list('city__city_name').distinct()

was auf PostgreSQL Folgendes bedeutet:

SELECT DISTINCT "city"."city_name" 
FROM "user" 
LEFT OUTER JOIN "city" 
                ON ("user"."city_id" = "city"."city_code");

Zeit:9760,302 ms

Das zeigte deutlich, dass PostgreSQL den Index für „user“.‘city_id‘ nicht nutzte.Ich habe auch von einer Workaround-Lösung gelesen Hier Dazu gehörte das Schreiben einer benutzerdefinierten SQL-Abfrage, die irgendwie den Index nutzt.

Ich habe versucht, mit der obigen Abfrage eine eindeutige „Benutzer-ID“ zu finden, und das ging tatsächlich ziemlich schnell.

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;

Zeit:79,056 ms

Aber jetzt fällt es mir schwer, dies in meinen Django-Code zu integrieren.Ich denke immer noch, dass es eine Art Hack ist, dem Code dafür eine benutzerdefinierte Abfrage hinzuzufügen.Eine größere Sorge für mich ist jedoch, dass der Spaltenname völlig dynamisch sein kann und ich diese Spaltennamen nicht fest codieren kann (z. B.city_id usw.) im Code.

#original_fields could be a list from input, like ['area_code__district_code__name']
dataset_klass.objects.filter().values_list(*original_fields).distinct()

Bei Verwendung der benutzerdefinierten Abfrage müsste zumindest der Feldname mit „__“ als Trennzeichen aufgeteilt und der erste Teil verarbeitet werden.Aber für mich sieht es nach einem schlechten Hack aus.

Wie kann ich das verbessern?

PS.Der City User Das Beispiel wird nur gezeigt, um das Szenario zu erläutern.Die Syntax ist möglicherweise nicht korrekt.

War es hilfreich?

Lösung

Endlich habe ich diese Workaround-Lösung gefunden.

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

Was den Index nutzt city_id In user Tabelle, läuft ziemlich schnell.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top