【问题标题】:Django - order_by() char and numericalDjango - order_by() 字符和数字
【发布时间】:2019-07-14 07:53:27
【问题描述】:

我必须对包含主机名的对象列表进行排序。

主机名采用以下格式: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

什么是面向未来的方法?

【问题讨论】:

  • 看起来这两个答案应该对您有所帮助:stackoverflow.com/questions/5967500/…stackoverflow.com/questions/4836710/…
  • @DanSwain 我认为 OP 想要使用 django orm
  • 你使用什么数据库?
  • @BearBrown PostgreSQL
  • 你看过db functions吗?我写下了这个想法,但我不知道它是否可行,但可能是一个起点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")

标签: django postgresql django-queryset


【解决方案1】:

我让它与一个额外的字段 normalized_hostname 一起工作,它也是独立于数据库的。在 Django Signals 的帮助下在模型中实现 pre_save()

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

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

示例:
主机名 -> 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

【讨论】:

    【解决方案2】:

    你可以使用f-expressions

    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'}]>
    

    关于数据库功能大家可以阅读Lregexp_match

    【讨论】:

    • 输出看起来不错,但function='regexp_matches' 来自哪里?
    • 这是postgres函数postgresql.org/docs/10/…
    • 在第二个视图中,顺序并不正确。它像 order_by(hostname) 一样订购,我尽量避免。它应该是 h1、h5、h10、h12、h12-abc、h1000、x10 - 不是 h1、h10、h1000、h12 等等 - 知道如何解决这个问题吗?
    • @FelixK 我修复了正则表达式,现在结果应该没问题。
    • 对于 PostgreSQL 这是有效的,所以我会接受这个作为答案。您将在下面找到我的替代数据库独立解决方案,该解决方案也有效。谢谢!
    猜你喜欢
    • 2011-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-20
    • 1970-01-01
    • 2017-11-24
    • 2017-06-09
    • 1970-01-01
    相关资源
    最近更新 更多