使用 prefetch_related 优化 Django 查询集多对多 for 循环
Optimize Django Queryset Many to Many for loop with prefetch_related
我看到一个函数可以在 4.5 秒内执行 64 个查询,所以我必须进行优化以获得更好的性能。
问题是 prefetch_related 没有像我预期的那样工作,所以我在 for 循环中的每次迭代都有一个新的查询集。
这是模型。
class Carrera(models.Model):
'''Carreras de cada sede'''
codigo = models.CharField(max_length=5, unique=True)
nombre = models.CharField(max_length=300)
def __str__(self):
return f"{self.codigo} - {self.nombre}"
class EstandarProducto(models.Model):
''''''
costo_unitario_uf = models.DecimalField(max_digits=20, decimal_places=5, default=0)
cantidad = models.IntegerField(default=0)
total_uf = models.DecimalField(max_digits=20, decimal_places=5, default=0)
recinto = models.ForeignKey(Recinto, related_name='estandares_producto', on_delete=models.CASCADE)
producto = models.ForeignKey(
Producto, related_name='estandares_producto', on_delete=models.PROTECT)
proveedor = models.ForeignKey(
Proveedor, related_name='estandares_producto', on_delete=models.CASCADE, blank=True, null=True)
carreras = models.ManyToManyField(Carrera, related_name="estandares_productos", blank=True)
这是我的观点:
class getEstandarPorRecinto(APIView):
@query_debugger
def get(self, request, format=None):
id_recinto = request.GET.get('recinto')
id_sede = request.GET.get('sede')
estandares = EstandarProducto.objects.select_related('recinto','producto','proveedor').prefetch_related(Prefetch('carreras')).filter(recinto=id_recinto)
inventario = InventarioProducto.objects.select_related('inventario').filter(inventario__sede=id_sede)
num_salas = SedeRecinto.objects.select_related('sede').filter(sede=id_sede, recinto=id_recinto)[0].numero_salas
productos_en_inventario = inventario.values()
lista_enviar = []
for estandarProducto in estandares:
carreras = []
for carrera in estandarProducto.carreras.values():
print("Codigo: " ,carrera.get("codigo"))
carreras.append(carrera.get("codigo"))
cantidad_inventario = 0
año_compra = ""
id_inventario_producto = ""
print("Carreras: ",carreras)
for producto in productos_en_inventario:
if producto.get("id") == estandarProducto.producto.id:
cantidad_inventario = producto.get("cantidad")
año_compra = producto.get("ultimo_año_reposicion")
id_inventario_producto = producto.get("id")
estandar_json = {
"id_producto": estandarProducto.producto.id,
"id_estandar_producto": estandarProducto.id,
"codigo": estandarProducto.producto.codigo,
"carreras": carreras,
"categoria": estandarProducto.producto.categoria,
"nombre": estandarProducto.producto.nombre,
"descripcion": estandarProducto.producto.descripcion,
"cantidad_esperada": estandarProducto.cantidad,
"proveedor_id": estandarProducto.proveedor.id,
"proveedor": estandarProducto.proveedor.nombre,
"frecuencia_reposicion": estandarProducto.producto.frecuencia_reposicion,
"inventario_producto_id": id_inventario_producto,
"costo_un_uf": estandarProducto.costo_unitario_uf,
"total_uf": estandarProducto.total_uf,
"tipo_presupuesto": estandarProducto.producto.tipo_presupuesto,
"inventario": cantidad_inventario,
"ultimo_año_compra": año_compra,
"numero_salas": num_salas,
"a_comprar": estandarProducto.cantidad*num_salas
}
lista_enviar.append(estandar_json)
data = lista_enviar
return Response(data, status.HTTP_200_OK)
问题在线:
estandares = EstandarProducto.objects.select_related('recinto','producto','proveedor').prefetch_related(Prefetch('carreras')).filter(recinto=id_recinto)
和
for carrera in estandarProducto.carreras.values():
print("Codigo: " ,carrera.get("codigo"))
carreras.append(carrera.get("codigo"))
如何在单个查询中获取所有 carreras.codigo,然后追加到列表中 carreras[]?
您可以使用 values_list
并设置 flat
True 以提供列表输出
例如:
carreras = estandares.values_list("carreras__codigo", flat=True)
如 docs
中所述:
any subsequent chained methods which imply a different database query will ignore previously cached results, and retrieve data using a fresh database query.
这意味着 values()
调用实质上使您的预取不可用,并且会导致您的 for 循环命中数据库以满足 values()
调用。在那种情况下,只需使用 all()
并从实例本身获取 codigo
,如下所示:
for estandarProducto in estandares:
carreras = []
for carrera in estandarProducto.carreras.all():
carreras.append(carrera.codigo)
但似乎您只需要 codigo
本身,所以@pouria farhadi 的回答会很有效。
我看到一个函数可以在 4.5 秒内执行 64 个查询,所以我必须进行优化以获得更好的性能。
问题是 prefetch_related 没有像我预期的那样工作,所以我在 for 循环中的每次迭代都有一个新的查询集。
这是模型。
class Carrera(models.Model):
'''Carreras de cada sede'''
codigo = models.CharField(max_length=5, unique=True)
nombre = models.CharField(max_length=300)
def __str__(self):
return f"{self.codigo} - {self.nombre}"
class EstandarProducto(models.Model):
''''''
costo_unitario_uf = models.DecimalField(max_digits=20, decimal_places=5, default=0)
cantidad = models.IntegerField(default=0)
total_uf = models.DecimalField(max_digits=20, decimal_places=5, default=0)
recinto = models.ForeignKey(Recinto, related_name='estandares_producto', on_delete=models.CASCADE)
producto = models.ForeignKey(
Producto, related_name='estandares_producto', on_delete=models.PROTECT)
proveedor = models.ForeignKey(
Proveedor, related_name='estandares_producto', on_delete=models.CASCADE, blank=True, null=True)
carreras = models.ManyToManyField(Carrera, related_name="estandares_productos", blank=True)
这是我的观点:
class getEstandarPorRecinto(APIView):
@query_debugger
def get(self, request, format=None):
id_recinto = request.GET.get('recinto')
id_sede = request.GET.get('sede')
estandares = EstandarProducto.objects.select_related('recinto','producto','proveedor').prefetch_related(Prefetch('carreras')).filter(recinto=id_recinto)
inventario = InventarioProducto.objects.select_related('inventario').filter(inventario__sede=id_sede)
num_salas = SedeRecinto.objects.select_related('sede').filter(sede=id_sede, recinto=id_recinto)[0].numero_salas
productos_en_inventario = inventario.values()
lista_enviar = []
for estandarProducto in estandares:
carreras = []
for carrera in estandarProducto.carreras.values():
print("Codigo: " ,carrera.get("codigo"))
carreras.append(carrera.get("codigo"))
cantidad_inventario = 0
año_compra = ""
id_inventario_producto = ""
print("Carreras: ",carreras)
for producto in productos_en_inventario:
if producto.get("id") == estandarProducto.producto.id:
cantidad_inventario = producto.get("cantidad")
año_compra = producto.get("ultimo_año_reposicion")
id_inventario_producto = producto.get("id")
estandar_json = {
"id_producto": estandarProducto.producto.id,
"id_estandar_producto": estandarProducto.id,
"codigo": estandarProducto.producto.codigo,
"carreras": carreras,
"categoria": estandarProducto.producto.categoria,
"nombre": estandarProducto.producto.nombre,
"descripcion": estandarProducto.producto.descripcion,
"cantidad_esperada": estandarProducto.cantidad,
"proveedor_id": estandarProducto.proveedor.id,
"proveedor": estandarProducto.proveedor.nombre,
"frecuencia_reposicion": estandarProducto.producto.frecuencia_reposicion,
"inventario_producto_id": id_inventario_producto,
"costo_un_uf": estandarProducto.costo_unitario_uf,
"total_uf": estandarProducto.total_uf,
"tipo_presupuesto": estandarProducto.producto.tipo_presupuesto,
"inventario": cantidad_inventario,
"ultimo_año_compra": año_compra,
"numero_salas": num_salas,
"a_comprar": estandarProducto.cantidad*num_salas
}
lista_enviar.append(estandar_json)
data = lista_enviar
return Response(data, status.HTTP_200_OK)
问题在线:
estandares = EstandarProducto.objects.select_related('recinto','producto','proveedor').prefetch_related(Prefetch('carreras')).filter(recinto=id_recinto)
和
for carrera in estandarProducto.carreras.values():
print("Codigo: " ,carrera.get("codigo"))
carreras.append(carrera.get("codigo"))
如何在单个查询中获取所有 carreras.codigo,然后追加到列表中 carreras[]?
您可以使用 values_list
并设置 flat
True 以提供列表输出
例如:
carreras = estandares.values_list("carreras__codigo", flat=True)
如 docs
中所述:
any subsequent chained methods which imply a different database query will ignore previously cached results, and retrieve data using a fresh database query.
这意味着 values()
调用实质上使您的预取不可用,并且会导致您的 for 循环命中数据库以满足 values()
调用。在那种情况下,只需使用 all()
并从实例本身获取 codigo
,如下所示:
for estandarProducto in estandares:
carreras = []
for carrera in estandarProducto.carreras.all():
carreras.append(carrera.codigo)
但似乎您只需要 codigo
本身,所以@pouria farhadi 的回答会很有效。