【问题标题】:Django query optimization for 3 related tables3个相关表的Django查询优化
【发布时间】:2020-03-29 02:43:44
【问题描述】:

我有 4 个模型:

class Run(models.Model):
    start_time = models.DateTimeField(db_index=True)
    end_time = models.DateTimeField()
    chamber = models.ForeignKey(Chamber, on_delete=models.CASCADE)
    recipe = models.ForeignKey(Recipe, default=None, blank=True, null=True, on_delete=models.CASCADE)

class RunProperty(models.Model):
    run = models.ForeignKey(Run, on_delete=models.CASCADE)
    property_name = models.CharField(max_length=50)
    property_value = models.CharField(max_length=500)

class RunValue(models.Model):
    run = models.ForeignKey(Run, on_delete=models.CASCADE)
    run_parameter = models.ForeignKey(RunParameter, on_delete=models.CASCADE)
    value = models.FloatField(default=0)

class RunParameter(models.Model):
    parameter = models.ForeignKey(Parameter, on_delete=models.CASCADE)
    chamber = models.ForeignKey(Chamber, on_delete=models.CASCADE)
    param_name_user_defined = models.BooleanField(default=True)

一个Run 可以有任意数量的RunProperty(通常是用户定义的属性,可以是自定义的)和一些预定义的RunValue(例如平均电压、最小电压、最大电压),它们是数值。

RunParameter 基本上只是一个包含参数名称(电压、电流、频率、温度、阻抗、振荡、可变性等)的容器。

当我构建一个前端表以显示每个 Run 及其所有“文件”RunPropertyRun 的来源)及其所有“电压”RunValue 时,我首先查询所有 Run 对象的数据库,然后对 Min/Max/Avg 执行另外 3 个查询,然后对 File 执行另一个查询,然后我在后端构建一个 dict 以传递到前端以构建表行:

runs = Run.objects.filter(chamber__in=chambers)
min_v_run_values = RunValue.objects.filter(run__in=runs, run_parameter__parameter__parameter_name__icontains="Minimum Voltage")
max_v_run_values = RunValue.objects.filter(run__in=runs, run_parameter__parameter__parameter_name__icontains="Maximum Voltage")
avg_v_run_values = RunValue.objects.filter(run__in=runs, run_parameter__parameter__parameter_name__icontains="Average Voltage")
run_files = RunProperty.objects.filter(run__in=runs, property_name="File")

对于在他们的数据库中有大约 10 到 30 个 Run 对象的客户来说,这不是一个大问题,但是我们有一个使用量很大的客户,他有 3500 个 Run 实例。不用说,它太慢了。我正在执行 5 次查询以获取所有需要的实例,然后我必须循环并将它们放在一个字典中。为一位客户执行此操作需要超过 45 秒(而对于大多数其他客户,大约需要 8 或 10 秒)。

有没有一种方法可以查询我的数据库中的所有Run 对象以及所有最小/最大/平均电压RunValue 和文件RunProperty 并返回,比如说,一个字典列表,一个对于每个 Run 以及其他对象?

我认为Q 查询可以在这里使用,但我不太确定如何使用它们,或者它们是否适用于这种情况?

我试过了(但没走多远):

runs = Run.objects.filter(chamber__in=chambers)
v_query = Q(run_parameter__parameter__parameter_name__icontains="Voltage")
run_values = RunValue.objects.filter(run__in=runs).filter(v_query)
run_files = RunProperty.objects.filter(run__in=runs, property_name="File")

这让我在 1 个查询中获得了所有 RunValue 相关对象,但每个查询仍然是 3 个。如果可能的话,我需要进一步优化。

我正在寻找类似的东西:

runs = Run.objects.filter(chamber__in=chambers)
        .annotate(Q(run__runvalue__run_parameter__parameter__parameter_name__icontains="Voltage")
                & Q(run__runproperty__property_name__icontains="File"))

我认为从广义上讲(甚至不是伪代码)我需要这样的查询:

"获取所有Runs,并且对于每个Run,获取与该Run 相关的所有RunValue 对象,这些对象包含["Average"、"Maximum"、"Minimum"] 以及所有包含“文件”的 RunRunProperty 对象。

我不知道这是否可能(听起来应该是),我不确定是否应该使用 Q 过滤、聚合或注释。从广义上讲,如果可能,我需要在一个查询中获取一个模型的所有实例,以及每个实例的所有外键

例子:

我有表 Run 有 2 个实例:

R1
R2

每个Run 实例都有一个关联的 RunProperty 实例“文件”(只是一个字符串):

R1_run.dat
R2_run.dat

每个Run 实例都有很多 RunValue 实例(我以 Voltage 为例,但有 26 个):

R1_max_v
R1_min_v
R1_avg_v

R2_max_v
R2_min_v
R2_avg_v

我需要查询数据库以使其返回(列表或字典,我都可以解决):

[{R1, R1_run.dat, R1_max_v, R1_min_v, R1_avg_v},
{R2, R2_run.dat, R2_max_v, R2_min_v, R2_avg_v}]

甚至是二维数组:

[[R1, R1_run.dat, R1_max_v, R1_min_v, R1_avg_v],
[R2, R2_run.dat, R2_max_v, R2_min_v, R2_avg_v]]

这可能吗?

【问题讨论】:

  • 使用annotate、select_related和prefetch_related。
  • 我一直在阅读更多关于此的内容,我相信这是我必须做的,查询 Run 并注释其他 2 个表。我只是不知道该怎么做。
  • 你想要平均电压,但你在模型中提到过。
  • 哦,平均值已经计算好了。它直接存储在自己的表中。它是从一个完全不同的表中计算出来的,并且需要经常访问,所以我们决定创建RunValue 表来存储每个Run 的 Avg/Max/Min,以免每次都通过 annotate 函数计算这些值。
  • 你能显示这个模型“RunParameter”吗

标签: python django postgresql django-queryset


【解决方案1】:

从数据库的角度来看,您只需使用带有几个连接的单个查询即可获取所需的所有数据:

-- This assumes that there is a primary key Run.id and 
-- foreign keys RunValue.run_id and RunProperty.run_id.
-- IDs or names of min/max/avg run parameters, as well as 
-- chamber ids are replaced with *_PARAMETER and CHAMBER_IDS 
-- for brevity.
SELECT Run.*, 
       RVmin.value AS min_value, 
       RVmax.value AS max_value,
       RVavg.value AS avg_value,
       RP.value AS file_value
FROM Run 
JOIN RunValue RVmin ON Run.id = RVmin.run_id
JOIN RunValue RVmax ON Run.id = RVmax.run_id
JOIN RunValue RVavg ON Run.id = RVavg.run_id
JOIN RunProperty RP ON Run.id = RP.run_id
WHERE
  RVmin.run_parameter = MIN_PARAMETER AND
  RVmax.run_parameter = MAX_PARAMETER AND
  RVavg.run_parameter = AVG_PARAMETER AND
  RP.property_name = 'File' AND
  Run.chamber IN (CHAMBER_IDS);

Django 构建此类连接的方式必须类似于 Run.runvalue_set.filter(run_parameter__contains 'Maximum Voltage') 请参阅“向后跟踪关系”:https://docs.djangoproject.com/en/2.2/topics/db/queries/#following-relationships-backward

【讨论】:

    【解决方案2】:

    您可以使用annotateMinMaxAvg 在查询中获取此信息。

    针对您的问题。你可以这样做。

    在 ForeignKey 字段中添加相关名称。

    class RunProperty(models.Model):
        run = models.ForeignKey(Run, on_delete=models.CASCADE, related_name="run_prop_name")
    
    class RunValue(models.Model):
        run = models.ForeignKey(Run, on_delete=models.CASCADE, related_name="run_value_name")
        run_parameter = models.ForeignKey(RunParameter, on_delete=models.CASCADE)
        value = models.FloatField(default=0)
    

    views.py

    from django.db.models import Avg, Max, Min
    
    filt = 'run_value_name__value'
    query = Run.objects.annotate(run_avg = Avg(filt), run_max = Max(filt))
    

    你可以得到所有的值:

      for i in query:
         print(i.run_avg, i.run_max, i.run_min )
    

    ------------编辑------------

    请检查我在 RunValue 模型中添加了“related_name”。

    让我们假设您在 Run 模型中有两个值。

    1) 运行_1

    2) 运行_2

    在模型 RunValue 中,6 个条目。

    run = 1, run_parameter = "Avg_value", value = 50

    run = 1, run_parameter = "Min_value", value = 25

    run = 1, run_parameter = "Max_value", value = 75

    run = 2, run_parameter = "Avg_value", value = 28

    run = 2, run_parameter = "Max_value", value = 40

    run = 2, run_parameter = "Min_value", value = 16

    你想要这样的字典:

    {'run_1': {'Avg_value': 50, 'Min_value': 25, 'Max_value': 75}, 'run_2': {...}}
    

    这样做记得阅读select_relatedprefetch_related 以获取文档。

        rt = Rub.objects.all().prefetch_related('run_value_name')
        s = {} # output dictionary
        for i in rt:
            s[i.run] = {} # run dictionary
            for j in i.run_value_name.all():
    
                s[i.run].update({j.run_parameter: j.value}) # update run dictionary
    
        print(s)
    

    ---------加法----------

    检查此代码命中的数据库数量。

    from django.db import connection, reset_queries
    print(len(connection.queries))
    reset_queries()
    

    【讨论】:

    • 抱歉,我应该解释一下,Avg/Max/Min 已经计算出来,并作为RunValue 的实例存储为ForeignKeyRun
    • @Mormoran 的 Avg/Max/Min 值是静态的还是会在创建新实例时发生变化?
    • 对于Run,它们是静态的。这些值是在不同的视图上计算的,这种情况只发生一次,在 Run 完成之后(Run 只是我们收集数据的一段时间)。有很多不同的参数我们需要知道平均/最大值/最小值,我只是以“电压”为例,但有 26 个不同的参数。这就是为什么我们决定计算一次并将它们存储在 ForeignKey 中。
    • 我认为从广义上讲(甚至不是伪代码)我需要这样的查询:“获取所有运行,并且对于每次运行,获取与该运行相关的所有 RunValue 对象,其中包含 ["Avg ", "Max", "Min"] 以及该运行的所有包含 "File" 的 RunProperty 对象我不知道它是否可能(听起来应该是),我不确定是否应该使用Q 过滤、聚合或注释。从广义上讲,我需要在一个查询中获取一个模型的所有实例,以及每个实例的所有外键,如果可能的话。
    • @Mormoran,这可能是您想要的,但我很困惑哪个模型“文件”来自(您尚未定义 FileField),哪个模型用于 Avg/Max/Min。并且值已经计算好了,使用prefetch_related和普通查询即可获取全部。
    猜你喜欢
    • 2016-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-05
    • 1970-01-01
    • 2018-04-03
    • 1970-01-01
    相关资源
    最近更新 更多