如何在Django中实现短标签字段的自然排序查询集?
解决Django中Short Label的自然排序问题
我来帮你搞定这个自然排序的问题!首先咱们得先搞清楚你原来代码里的问题出在哪:你用的正则表达式^(\D+)|(\w+)会把整个short_label字符串都匹配替换成空,导致short_label_numeric最终变成NULL,所以排序的时候实际上是按short_label的字符串顺序来的,这就是为什么会出现1、10、11...这种不符合预期的结果。
接下来咱们重新设计排序逻辑,完全贴合你的需求:纯数字按数值排序、同数字带后缀的排在纯数字后面、S开头的标签按数字自然排序且整体排在非S标签之后。
解决方案代码
from django.db import models from django.db.models import Func, F, Value, Cast, Case, When import settings class MySiteManager(models.Manager): def get_queryset(self): qws = super().get_queryset().filter( some_id=settings.BASE_SOME_ID ).annotate( # 提取前缀:比如'S',无前缀则为空字符串 label_prefix=Func( F('short_label'), Value('^(\\D+)'), function='substring', template="%(function)s(%(expressions)s from %(param1)s)" ), # 提取数字部分并转为数值类型 label_number=Cast( Func( F('short_label'), Value('(\\d+)'), function='substring', template="%(function)s(%(expressions)s from %(param1)s)" ), models.BigIntegerField(), output_field=models.BigIntegerField() ), # 提取后缀:去掉前缀和数字后的剩余部分,无后缀则为空 label_suffix=Func( F('short_label'), Value('^\\D*\\d+'), Value(''), function='regexp_replace' ) ).order_by( # 先区分非S和S标签:非S在前,S在后 Case( When(label_prefix='S', then=Value(1)), default=Value(0), output_field=models.IntegerField() ), # 按数字数值排序 'label_number', # 同数字下,纯数字(空后缀)在前,带后缀的在后 'label_suffix' ) # 测试输出验证排序结果 for q in qws: print(q.short_label) return qws
代码逻辑解释
字段提取:
label_prefix:用substring提取开头的非数字字符(比如'S'),没有前缀的标签会得到空字符串。label_number:提取标签中的连续数字并转为BigInteger类型,这样就能实现数值排序而不是字符串排序。label_suffix:用regexp_replace去掉前缀和数字部分,剩下的就是后缀内容,纯数字标签的后缀为空。
排序规则:
- 第一步:通过
Case-When把非S标签(前缀为空)排在前面,S标签排在后面。 - 第二步:按
label_number的数值大小排序,解决纯数字和S开头标签的自然排序问题(比如1、2、10而不是1、10、2)。 - 第三步:同数字下,空后缀的纯数字标签排在前面,带后缀的(比如3c)排在后面。
- 第一步:通过
数据库兼容性说明
如果你的项目用的是MySQL而不是PostgreSQL,需要把substring函数换成REGEXP_SUBSTR,调整后的annotate部分如下:
.annotate( label_prefix=Func( F('short_label'), Value('^\\D+'), function='REGEXP_SUBSTR' ), label_number=Cast( Func( F('short_label'), Value('\\d+'), function='REGEXP_SUBSTR' ), models.BigIntegerField() ), label_suffix=Func( F('short_label'), Value('^\\D*\\d+'), Value(''), function='regexp_replace' ) )
这样调整后,你就能得到完全符合预期的排序结果了:纯数字按数值从小到大,同数字带后缀的紧跟其后,S开头的标签按数字自然排序且整体排在最后。
内容的提问来源于stack exchange,提问作者Successful_12
相关产品推荐
相关产品推荐

