【问题标题】:How to efficiently write DISTINCT query in Django with table having foreign keys如何在 Django 中使用具有外键的表有效地编写 DISTINCT 查询
【发布时间】:2013-11-29 13:21:39
【问题描述】:

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

类似下面的内容适用于 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()

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

我该如何改进呢?

附言。 City User 示例仅用于解释该场景。语法可能不正确。

【问题讨论】:

    标签: sql django django-models django-postgresql


    【解决方案1】:

    我终于找到了这个解决方法。

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

    它利用user 表中city_id 上的索引,运行速度非常快。

    【讨论】:

      猜你喜欢
      • 2018-01-22
      • 1970-01-01
      • 1970-01-01
      • 2018-03-04
      • 2016-01-17
      • 1970-01-01
      • 1970-01-01
      • 2011-12-28
      • 1970-01-01
      相关资源
      最近更新 更多