Django中按指定顺序获取QuerySet并补全不存在项为None的方法
问题描述
现有Django模型及测试数据如下:
class Book(models.Model): name = models.CharField(max_length=10) Book.objects.bulk_create([ Book(name='A'), Book(name='B'), Book(name='C'), ]) book_names = ['C', 'D', 'A', 'B'] # 期望函数输出 # >>> [Book(name='C'), None, Book(name='A'), Book(name='B')]
需求:实现一个函数,传入模型特定字段的取值列表后,返回与列表顺序完全一致的模型对象,字段值不存在时返回None,要求仅通过单次Django ORM查询实现,避免循环查询数据库。
当前已实现按顺序获取存在的对象,但无法补全不存在项为None:
preserved = Case(*[When(name=name, then=pos) for pos, name in enumerate(book_names)]) Book.objects.filter(name__in=book_names).order_by(preserved) # >>> [Book(name='C'), Book(name='A'), Book(name='B')]
解决方案
推荐实现方式(简洁高效,兼容所有数据库)
数据库本身不会返回不存在的记录,因此可以通过单次ORM查询获取所有存在的对象,再通过Python字典映射补全None,Python层面的循环不会增加数据库负担:
from django.db.models import Case, When, Value # 生成排序条件,保证查询结果按目标列表顺序返回 preserved = Case(*[When(name=name, then=Value(pos)) for pos, name in enumerate(book_names)]) # 单次ORM查询获取所有存在的Book对象,并转为{name: book}的字典映射 book_map = {book.name: book for book in Book.objects.filter(name__in=book_names).order_by(preserved)} # 按原列表顺序生成结果,不存在的项返回None result = [book_map.get(name) for name in book_names]
执行后result即为期望输出:[Book(name='C'), None, Book(name='A'), Book(name='B')]
PostgreSQL专属实现(纯ORM逻辑,依赖特定函数)
如果使用PostgreSQL数据库,可以借助unnest函数生成包含所有目标字段值的虚拟行,再通过左连接关联模型表,实现纯ORM层面的结果输出:
from django.db.models import OuterRef, Value, Case, When from django.contrib.postgres.functions import Unnest # 生成包含所有目标name的虚拟子查询 names_subquery = Book.objects.annotate( name=Unnest(Value(book_names)) ).values('name').annotate( pos=Case(*[When(name=name, then=Value(pos)) for pos, name in enumerate(book_names)]) ) # 左连接获取所有对象(包括不存在的虚拟行)并排序 combined_books = Book.objects.filter(name__in=book_names).annotate( pos=Case(*[When(name=name, then=Value(pos)) for pos, name in enumerate(book_names)]) ).union(names_subquery, all=True).order_by('pos') # 将无ID的虚拟行转为None result = [book if hasattr(book, 'id') and book.id else None for book in combined_books]
这种方式完全依赖ORM,但仅兼容PostgreSQL,实现复杂度较高,一般推荐第一种方法。
内容的提问来源于stack exchange,提问作者Yeongbin Jo
相关产品推荐
相关产品推荐

