Comment écrire efficacement une requête DISTINCT dans Django avec une table ayant des clés étrangères

StackOverflow https://stackoverflow.com//questions/20013116

Question

Je souhaite afficher des villes distinctes d'utilisateurs dans la liste déroulante frontale.Pour cela, je fais une requête db qui récupère des éléments distincts city_name du tableau City mais uniquement les villes où les utilisateurs sont présents.

Quelque chose comme ci-dessous fonctionne pour une petite taille de User table, mais cela prend beaucoup de temps si User table en taille 10 millions.Cependant, les villes distinctes de ces utilisateurs sont toujours d'environ 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')

Maintenant, j'essaie de rechercher des noms de villes distincts comme :

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

ce qui se traduit par ceci sur PostgreSQL :

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

Temps:9760,302 ms

Cela montrait clairement que PostgreSQL n'utilisait pas d'index sur 'user'.'city_id'.J'ai également lu des informations sur une solution de contournement ici ce qui impliquait l'écriture d'une requête SQL personnalisée qui utilise d'une manière ou d'une autre l'index.

J'ai essayé de trouver un « utilisateur ». « city_id » distinct en utilisant la requête ci-dessus, et cela s'est avéré assez rapide.

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;

Temps:79,056 ms

Mais maintenant, j'ai du mal à intégrer cela dans mon code Django.Je pense toujours que c'est une sorte de hack ajoutant une requête personnalisée dans le code pour cela.Mais un plus gros souci pour moi est que le nom de colonne peut être totalement dynamique, et je ne peux pas coder en dur ces noms de colonnes (par exemple.city_id, etc.) dans le code.

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

L’utilisation de la requête personnalisée nécessiterait au moins de diviser le nom du champ avec « __ » comme délimiteur et de traiter la première partie.Mais cela me semble être un mauvais hack.

Comment puis-je améliorer cela ?

PS.Le City User L'exemple est juste montré pour expliquer le scénario.La syntaxe n'est peut-être pas correcte.

Était-ce utile?

La solution

J'ai finalement trouvé cette solution de contournement.

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

Qui utilise l'index sur city_id dans user table, court assez vite.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top