我想在前端下拉列表中显示不同的用户城市。为此,我进行了一个数据库查询,该查询从表city_name中获取不同的City,但仅获取存在用户的城市。

下面的内容适用于较小的User表,但是如果User表的大小为1000万,则花费很长时间。这些用户的不同城市仍为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'上的索引。我还阅读了一种解决方法here,其中涉及编写自定义SQL查询,该查询以某种方式利用索引。

我试图使用上面的查询找到不同的'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毫秒

但是现在我发现很难将其合并到我的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()

使用自定义查询将需要至少使用“__”作为分隔符来拆分字段名称并处理第一部分。但这对我来说似乎是一个坏习惯。

我该如何改善呢?

PS。只是显示了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上的索引,运行速度非常快。

10-06 13:06