Dan*_*nra 2 django django-queryset
从django中的数据库中以某种自定义顺序获取多个对象的优雅方法是什么?
例如,假设您有一些产品,每个产品都有其名称,并且您希望获取其中三个产品以在某个固定的自定义顺序中显示在您网站页面的一行中.假设您要显示的产品名称按顺序排列:["Milk", "Chocolate", "Juice"]
一个人可以做到
unordered_products = Product.objects.filter(name__in=["Milk", "Chocolate", "Juice"])
products = [
unordered_products.filter(name="Milk")[0],
unordered_products.filter(name="Chocolate")[0],
unordered_products.filter(name="Juice")[0],
]
Run Code Online (Sandbox Code Playgroud)
并且可以改进post-fetch订购部分以使用name-indexed字典:
ordered_product_names = ["Milk", "Chocolate", "Juice"]
products_by_name = dict((x.name, x) for x in unordered_products)
products = [products_by_name[name] for name in ordered_product_names]
Run Code Online (Sandbox Code Playgroud)
但是有更优雅的方式吗?例如,以某种方式将所需的订单传达给DB层,或者返回按名称分组的产品(聚合似乎与我想要的类似,但我想要实际的对象,而不是关于它们的统计数据).
您可以通过自定义订单订购产品,只需一次ORM查询(仅执行一个SQL查询):
ordered_products = Product.objects.filter(
name__in=['Milk', 'Chocolate', 'Juice']
).annotate(
order=Case(
When(name='Milk', then=Value(0)),
When(name='Chocolate', then=Value(1)),
When(name='Juice', then=Value(2)),
output_field=IntegerField(),
)
).order_by('order')
Run Code Online (Sandbox Code Playgroud)
谈到"优雅的方式"(和最佳实践),我认为extra方法(由@Satendra提出)是绝对要避免的.
官方Django文档报告了额外的:
警告
每当你使用extra()时都应该非常小心.每次使用它时,都应该使用params来转义用户可以控制的任何参数,以防止SQL注入攻击.请阅读有关SQL注入保护的更多信息.
如果你想处理更多项目只有一个查询,你可以改变我的第一个查询,并使用@Shubhanshu在他的回答中建议的Django ORM灵活性:
products = ['Milk', 'Chocolate', 'Juice']
ordered_products = Product.objects.filter(
name__in=products
).order_by(Case(
*[When(name=n, then=i) for i, n in enumerate(products)],
output_field=IntegerField(),
))
Run Code Online (Sandbox Code Playgroud)
此命令的输出将类似于:
<QuerySet [<Product: Milk >, <Product: Chocolate>, <Product: Juice>]>
Run Code Online (Sandbox Code Playgroud)
ORM生成的SQL将如下所示:
SELECT "id", "name"
FROM "products"
WHERE "name" IN ('Milk', 'Chocolate', 'Juice')
ORDER BY CASE
WHEN "name" = 'Milk' THEN 0
WHEN "name" = 'Chocolate' THEN 1
WHEN "name" = 'Juice' THEN 2
ELSE NULL
END ASC
Run Code Online (Sandbox Code Playgroud)