我有以下型号:
class Entry(models.Model):
name = models.Charfield(max_length=255)
client = models.Charfield(max_length=255)
client
是客户的名称,可能包含facebook
,google
等值。
是否可以对查询集进行排序,以便结果交替显示client
的值?
我的期望是这样的:
Entry.objects.order_by('alternate client') -->
| client | name |
| google | robert |
| facebook | linda |
| google | kate |
| facebook | jack |
| google | nina |
| facebook | pierre |
如果有帮助,我正在使用django2.x和postgres。
编辑:
一些额外的信息/要求。
created
DateField。如果可能的结果也应按日期排序答案 0 :(得分:12)
由于你使用Postgres,你可以使用它的Window Functions,它在一组与当前行有某种关系的表行中执行计算。另一个好的信息依赖于你使用支持窗口函数(Django docs)的Django2.x,它允许向OVER
添加Querysets
子句。
您可以使用单个ORM查询解决您的用例,例如:
from django.db.models.expressions import Window
from django.db.models.functions import RowNumber
from django.db.models import F
results = Entry.objects.annotate(row_number=Window(
expression=RowNumber(),
partition_by=[F('client')],
order_by=F('created').desc())
).order_by('row_number', 'client')
for result in results:
print('Id: {} - client: {} - row_number {}'.format(result.id, result.client, result.row_number))
输出:
Id: 12 - client: facebook - row_number 1
Id: 13 - client: google - row_number 1
Id: 11 - client: facebook - row_number 2
Id: 8 - client: google - row_number 2
Id: 10 - client: facebook - row_number 3
Id: 5 - client: google - row_number 3
Id: 9 - client: facebook - row_number 4
Id: 3 - client: google - row_number 4
Id: 7 - client: facebook - row_number 5
Id: 2 - client: google - row_number 5
Id: 6 - client: facebook - row_number 6
Id: 1 - client: google - row_number 6
Id: 4 - client: facebook - row_number 7
原始SQL看起来像:
SELECT
"orm_entry"."id",
"orm_entry"."name",
"orm_entry"."client",
"orm_entry"."created",
ROW_NUMBER() OVER (PARTITION BY "orm_entry"."client" ORDER BY "orm_entry"."created" DESC) AS "row_number"
FROM "orm_entry"
ORDER BY "row_number" ASC, "orm_entry"."client" ASC
窗口函数被声明为聚合函数,后跟一个OVER子句,该子句准确指示行的分组方式。应用窗口函数的行组称为“分区”。
您可以注意到我们按'client'字段对行进行了分组,您可以得出结论,在我们的示例中,我们将有两个分区。第一个分区将包含所有“ facebook”条目,第二个分区将包含所有'google'条目。在其基本形式中,分区与普通聚合函数组没有区别:只是一组被某些条件视为“相等”的行,并且该函数将应用于所有这些行以返回单个结果。
在您的示例中,我们可以使用row_number
窗口函数,它只返回从1开始的分区中当前行的索引。这有助于我在order_by('row_number', 'client')
中建立交替输出。
其他信息:
如果您想要达到这样的订单:
'facebook','facebook', 'google','google','facebook','facebook','google','google'
或
'facebook','facebook','facebook','google','google','google','facebook', 'facebook','facebook'
您需要对上一个查询进行一次与数学相关的小修改,例如:
GROUP_SIZE = 2
results = Entry.objects.annotate(row_number=Window(
expression=RowNumber(),
partition_by=[F('client')],
order_by=F('created').desc())
).annotate(row_number=(F('row_number') - 1)/GROUP_SIZE + 1).order_by('row_number', 'client')
for result in results:
print('Id: {} - client: {} - row_number {}'.format(result.id, result.client, result.row_number))
输出:
Id: 12 - client: facebook - row_number 1
Id: 11 - client: facebook - row_number 1
Id: 8 - client: google - row_number 1
Id: 13 - client: google - row_number 1
Id: 10 - client: facebook - row_number 2
Id: 9 - client: facebook - row_number 2
Id: 3 - client: google - row_number 2
Id: 5 - client: google - row_number 2
Id: 7 - client: facebook - row_number 3
Id: 6 - client: facebook - row_number 3
Id: 1 - client: google - row_number 3
Id: 2 - client: google - row_number 3
Id: 4 - client: facebook - row_number 4
您可以注意到GROUP_SIZE
常量定义了每个交替组中的项目数。
<强> P.S。强>
感谢您提出这个问题,因为它帮助我更好地理解了窗口函数。
快乐编码:)
答案 1 :(得分:3)
这不是最高效的方式,但有效:
from itertools import zip_longest
from django.db.models import Case, When
grouped_pks = []
for client in Entry.objects.values_list('client', flat=True).distinct():
grouped_pks.append(
Entry.objects.filter(client=client).values_list('pk', flat=True)
)
alternated_pks = [
x for x in
filter(
None,
sum(zip_longest(*grouped_pks), ())
)
]
alternated_pks_order = Case(
*[
When(pk=pk, then=position)
for position, pk in enumerate(alternated_pks)
]
)
entries = Entry.objects.filter(pk__in=alternated_pks).order_by(alternated_pks_order)
for entry in entries:
print('id: {} - client: {}'.format(entry.id, entry.client))
预期产出:
id: 8901 - client: google
id: 8890 - client: facebook
id: 8884 - client: google
id: 8894 - client: facebook
id: 8748 - client: google
id: 8891 - client: facebook
id: 8906 - client: google
id: 8909 - client: facebook
id: 8888 - client: google
id: 8895 - client: facebook
id: 8919 - client: google
id: 8910 - client: facebook
id: 8878 - client: google
id: 8896 - client: facebook
id: 8916 - client: google
id: 8902 - client: facebook
id: 8917 - client: google
id: 8885 - client: facebook
id: 8918 - client: google
id: 8903 - client: facebook
id: 8920 - client: google
id: 8886 - client: facebook
id: 8904 - client: facebook
id: 8905 - client: facebook
id: 8887 - client: facebook
id: 8911 - client: facebook
id: 8897 - client: facebook
id: 8912 - client: facebook
id: 8898 - client: facebook
id: 8899 - client: facebook
id: 8914 - client: facebook
id: 8900 - client: facebook
id: 8915 - client: facebook
这是python3代码,但是如果你想将它与python 2一起使用,请将zip_longest
函数更改为izip_longest
。
这段代码很不错,因为我们仍然使用Queryset,因此所有其他排序,排序,管理器,分页和其他内容仍然有效。
答案 2 :(得分:0)
使用numpy模块
可能会有以下工作import numpy
queryset = Entry.objects.all()
''' catch the number of distinct Entry considering the client fields '''
queryset_client_entries = Entry.objects.distinct('client')
new_list = list(numpy.resize(queryset_client_entries, queryset.count()))
''' An alternative list of clients depending on the length of queryset '''