Django - order_by() 字符和数字

4

我需要对一个包含主机名的对象列表进行排序。

这些主机名的格式有:h1、h5、h10、h12、h12-abc、h1000、x10

如果我使用 order_by('hostname') 进行排序,它将按照以下顺序排序:

h1, h10, h1000, h12, h12-abc, h5, x10

我该如何实现这样的排序:

h1, h5, h10, h12, h12-abc, h1000, x10

主机名总是以一个字符开头,然后是1-4个数字和部分扩展名,例如'-abc'。
我猜我必须使用Substr()来提取数字并对数字进行排序,这样'10'就不会在'5'之前列出。
通过搜索,我找到了一些旧的例子,其中包含extra(),但Django文档说它将来会被弃用,并且“将此方法用作最后的手段” https://docs.djangoproject.com/en/2.1/ref/models/querysets/#extra 有什么未来可靠的方法可以做到这一点?

看起来这两个回答应该对你有帮助:https://dev59.com/SG025IYBdhLWcg3wf2OE和https://dev59.com/aG445IYBdhLWcg3we6V9 - Dan Swain
1
@DanSwain 我认为原帖作者想要使用Django ORM。 - Brown Bear
你使用哪个数据库? - Brown Bear
@BearBrown PostgreSQL - fuser60596
1
你看过数据库函数了吗?我记录了这个想法,但不知道它是否有效,但可能是一个起点。Hosts.objects.annotate( letter=Substr("hostname", 0, 1), extension=StrIndex(F("hostname"), Value("-")), ).annotate( numerical=Cast(Substr("hostname", 1, Coalesce("extension", Value(None))), IntegerField()), ).order_by("extensions", "numerical") - mfrackowiak
2个回答

5

你可以使用f表达式

from django.db.models import F, Value, TextField, IntegerField
from django.contrib.postgres.fields import ArrayField
from django.db.models.expressions import Func

sql = ordModel.objects.annotate(
        num=Cast(
            Func(
                F('hostname'),
                Value("\d+"),
                function='regexp_matches',
            ),
            output_field=ArrayField(IntegerField())
        ),
        char=Func(
            F('hostname'),
            Value("\D+"),
            function='regexp_matches',
            output_field=ArrayField(TextField())
        )
    ).order_by('char', 'num', ).values('hostname')

我的结果与相同数值的列表相同:

<QuerySet [
{'hostname': 'h1'},
{'hostname': 'h5'},
{'hostname': 'h10'},
{'hostname': 'h12'},
{'hostname': 'h12-abc'},
{'hostname': 'h1000'},
{'hostname': 'x10'}]>

关于数据库函数,您可以阅读 regexp_match

1
输出看起来很棒,但function='regexp_matches'是从哪里来的? - fuser60596
2
这是PostgreSQL函数 https://www.postgresql.org/docs/10/functions-matching.html#FUNCTIONS-POSIX-REGEXP - Brown Bear
在第二个视图中,顺序并不正确。它的排序方式是order_by(hostname),而我想避免这种情况。应该是h1、h5、h10、h12、h12-abc、h1000、x10的顺序,而不是h1、h10、h1000、h12等等。你有什么解决办法吗? - fuser60596
@FelixK 我修正了正则表达式,现在结果应该没问题了。 - Brown Bear
对于PostgreSQL,这个方法可行,所以我会接受它作为答案。下面是我的另一种数据库独立的解决方案,也能够工作。谢谢! - fuser60596

2
我使用了一个额外的字段normalized_hostname,使其独立于数据库,并通过Django信号的pre_save()方法在模型中实现。

https://docs.djangoproject.com/en/2.1/ref/signals/#pre-save

以下代码将主机名转换为一种格式,然后可以与 order_by('normalized_hostname') 一起使用。

示例:
主机名 -> 格式化后的主机名

h1 -> h0001 
h5 -> h0005, 
h10 -> h0010 
h12 -> h0012
h12-abc -> h0012-abc 
h1000 -> h1000 
x10 -> x0010

models.py

from django.db.models.signals import pre_save
import re

class MyModel(models.Model):
  the solution is also database independent  hostname = models.CharField(max_length=64)
    normalized_hostname = models.CharField(max_length=64)



def create_normalize_hostname(instance):
    normalize = re.sub("\D", "", instance.hostname).zfill(4)
    normalized_hostname = re.sub("(\d{1,4})", normalize, instance.hostname)
    return normalized_hostname

def receiver(sender, instance, *args, **kwargs)
    instance.normalized_hostname = create_normalize_hostname(instance)

pre_save.connect(receiver, sender=ModelName)

现在的顺序将会是这样:
h1, h5, h10, h12, h12-abc, h1000, x10

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接