Django 开发中有时候会遇到这样的需求:查询到不同模型(Model) 的查询集(QuerySet),需要将其合并成一个查询集,甚至还希望能够对合并后的查询集排序,以便在模板中循环展示。
sorted
>>> import itertools >>> qs1 = Post.objects.all() >>> qs2 = Material.objects.all() >>> qs = itertools.chain(qs1, sq2) >>> sorted(qs, key=lambda o: o.created_time, reverse=True)
union
QuerySet 的 union 方法
unionUNIONPostMaterialPostMaterialpub_date
frpm django.db import models
class Post(modes.Model):
title = models.CharField(max_length=255)
body = models.TextField()
pub_date = models.DateTimeField()
pinned = models.BooleanField(default=False)
class Meta:
ordering = ['-pinned', '-pub_date']
class Material(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
pub_date = models.DateTimeField()
class Meta:
ordering = ['-pub_date']
MaterialPostpinnedpinned
def get_index_entry_queryset():
post_qs = Post.objects.all().order_by().annotate(
type=Value('p', output_field=CharField(max_length=1)),
entry_pinned=F('pinned'))
post_qs = post_qs.values_list(
'title','pub_date','entry_pinned','type'
)
material_qs = Material.objects.all().order_by().annotate(
type=Value('m', output_field=CharField(max_length=1)),
entry_pinned=Value(False, BooleanField()))
material_qs = material_qs.values_list(
'title','pub_date','entry_pinned','type'
)
entry_qs = post_qs.union(material_qs)
entry_qs = entry_qs.order_by('-entry_pinned', '-pub_date')
return entry_qs
annotatetypeMaterialpinnedannotateentry_pinnedFalsePostpinnedannotatepinned
entry_qsquery
SELECT "blog_post"."title", "blog_post"."pub_date", 'p' AS "type", "blog_post"."pinned" AS "entry_pinned" FROM "blog_post" UNION SELECT "courses_material"."title", "courses_material"."pub_date", 'm' AS "type", False AS "entry_pinned" FROM "courses_material" ORDER BY (4) DESC, (2) DESC
数据库查询结果如下:
| title | pub_date | type | entry_pinned |
|---|---|---|---|
| Markdown 测试 | 2019-09-23 15:35:47.898271 | p | 1 |
| test | 2019-09-15 13:13:00 | p | 0 |
| 分类、归档和标签页 | 2019-09-07 01:41:00 | m | 0 |
| 页面侧边栏:使用自定义模板标签 | 2019-08-29 23:49:00 | m | 0 |
UNIONunionall=TrueUNION ALL
注意事项
UNIONunion
- select 的字段类型必须匹配(字段名可以不同,但排列顺序要一致)。例如 field1 和 field 2 都是整数类型,select field1 和 select field 可以进行 union 操作,当引用时,以第一个 QuerySet 中的字段名进行引用。
- 组合后的查询集,很多方法将不可用。
不过在实际使用过程中,发现还用很多的未提及的限制需要小心翼翼地处理。
例如看到示例中的这两句代码:
post_qs = Post.objects.all().order_by().annotate(
type=Value('p', output_field=CharField(max_length=1)),
entry_pinned=F('pinned'))
material_qs = Material.objects.all().order_by().annotate(
type=Value('m', output_field=CharField(max_length=1)),
entry_pinned=Value(False, BooleanField()))
order_by()Metaordering
django.db.utils.DatabaseError: ORDER BY not allowed in subqueries of compound statements.
annotatePostpinnedpinnedMaterial
有的同学可能想这样做:
post_qs = Post.objects.all().annotate(
type=Value('p', output_field=CharField(max_length=1)))
post_qs = post_qs.values_list(
'title','pub_date','pinned','type'
)
material_qs = Material.objects.all().annotate(
type=Value('m', output_field=CharField(max_length=1)),
pinned=Value(False, BooleanField()))
material_qs = material_qs.values_list(
'title','pub_date','pinned','type'
)
Postvalues_listvalues_list
SELECT "blog_post"."title", "blog_post"."pub_date", "blog_post"."pinned", 'p' AS "type" FROM "blog_post" UNION SELECT "courses_material"."title", "courses_material"."pub_date", 'm' AS "type", False AS "pinned" FROM "courses_material" ORDER BY (4) DESC, (2) DESC
pinnedannotateannotate
material_qs = Material.objects.all().annotate(
type=Value('m', output_field=CharField(max_length=1)),
pinned=Value(False, BooleanField()))
typepinnedUNIONtypepinned
annotate
post_qs = Post.objects.all().annotate(
type=Value('p', output_field=CharField(max_length=1)))
post_qs = post_qs.values_list(
'title','pub_date','pinned','type'
)
material_qs = Material.objects.all().annotate(
pinned=Value(False, BooleanField())).annotate(
type=Value('m', output_field=CharField(max_length=1)))
material_qs = material_qs.values_list(
'title','pub_date','pinned','type'
)
material_qsannotatepinnedtypepinned
ORDER BYnull_firstnulls_last
entry_qs = entry_qs.order_by(F('type').desc(), '-pub_date')
entry_qs = entry_qs.order_by('-pinned', F('pub_date').desc(nulls_last=True))
将得到如下错误:
django.db.utils.DatabaseError: ORDER BY term does not match any column in the result set.
然而对于实际的 SQL 语句,使用 as 设置的别名字段是可以进行排序的:
SELECT "blog_post"."title", "blog_post"."pub_date", "blog_post"."pinned", 'p' AS "type" FROM "blog_post" UNION SELECT "courses_material"."title", "courses_material"."pub_date", False AS "pinned", 'm' AS "type" FROM "courses_material" ORDER BY type DESC, pub_date DESC
直接执行这条查询语句可以得到正确的查询结果,不知道为何 django 会报错。
总结
unionUNION
annotate annotate
-- EOF --