【发布时间】:2020-04-16 16:26:00
【问题描述】:
我是 Django 的新手,我有一些关于通过 QuerySet API 进行查询的问题。
例如,我有用户、他的订单及其状态
class User(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
is_active = models.BooleanField()
class OrderStatus(models.Model):
name = models.CharField(max_length=100)
class Order(models.Model):
number = models.CharField(max_length=10)
amount = models.DecimalField(max_digits=19, decimal_places=2)
user = models.ForeignKey(User, on_delete=models.PROTECT, related_name="orders")
order_status = models.ForeignKey(OrderStatus, on_delete=models.PROTECT)
creation_datetime = models.DateTimeField(auto_now_add=True)
# Some filtering field
filtering_field = models.IntegerField()
我将我所有的问题合并到这个查询中:
为每个用户获取一些额外数据的活跃用户:
- 按“filtering_field”过滤并按最小值和最大值聚合的订单的“金额”
- “filtering_field”过滤的第一个订单的“Number”和“Amount”
- 按“filtering_field”过滤、按“计数”汇总并按“订单状态”分组的订单计数。这种分组意味着来自查询 #1 和 #2 的数据可以重复,这没关系。
我可以在 T-SQL 中通过 3 个带有自己的分组、过滤、排序的独立子查询来进行此查询:
SELECT
u.id,
u.first_name,
u.last_name,
ts.min_amount,
ts.max_amount,
first_order.number as first_order_number,
first_order.amount as first_order_amount,
cnt.order_status_id,
cnt.cnt
FROM
[User] u
-- 1. 'Amount' of the Orders filtered by 'filtering_field' and aggregated by Min and Max
LEFT OUTER JOIN (
SELECT
[user_id],
MIN(amount) min_amount,
MAX(amount) max_amount
FROM
[Order]
WHERE
filtering_field = 1
GROUP BY
[user_id]
) ts ON u.id = ts.[user_id]
-- 2. 'Number' and 'Amount' of the first Order filtered by 'filtering_field'
OUTER APPLY (
SELECT TOP 1
o.number,
o.amount
FROM
[Order] o
WHERE
u.id = o.[user_id] AND
o.filtering_field = 2
ORDER BY
o.creation_datetime
) first_order
-- 3. Count of the Orders filtered by 'filtering_field', aggregated by Count and grouped by 'Order Status'.
LEFT OUTER JOIN (
SELECT
[user_id],
order_status_id,
COUNT(*) cnt
FROM
[Order]
WHERE
filtering_field = 3
GROUP BY
[user_id],
order_status_id
) cnt ON u.id = cnt.[user_id]
WHERE
u.is_active = 1
我如何通过 QuerySet API 做同样的事情?
查询 #1 我可以在 Annotate 中做 Min 和 Max。
data = User.objects.filter(
Q(is_active=True)
).values(
'id',
'first_name',
'last_name',
).annotate(
min_amount=Min(
'orders__amount',
filter=Q(orders__filtering_field=1)
),
max_amount=Max(
'orders__amount',
filter=Q(orders__filtering_field=1)
)
)
但是查询 #2 和 #3 呢?
我考虑过 Subquery(),但它只支持一个输出值。 我的意思是,如果你想从 1 个查询集中获取 5 个字段,sql server 会运行 5 个查询。我认为这对性能不利。
如何加入第一个订单一次以使用其字段以及如何使用 Count() 按子模型的过滤行进行分组?
我想使用 .prefetch_related() 代替 T-SQL 中的子查询,如下所示:
Prefetch(
'orders',
queryset=Order.objects.filter(filtering_field=1)..., #staff with .values(), annotate(Min(), Max()) and etc.
to_attr='pf_query_1'
)
然后在 User.objects...values()...annotate() 中使用 'pf_query_1' 之类的 'orders__pf_query_1__amount'。
但我不能在 Prefetch 中使用 .values() 以及 'pf_query_1' 作为模型字段。
那么,通过 QuerySet API 进行这一查询的最佳实践是什么? 我想查看整个 QuerySet API 查询,就像 T-SQL 查询一样
【问题讨论】: