【问题标题】:Django: ordering numerical value with order_byDjango:使用 order_by 排序数值
【发布时间】:2011-02-27 12:01:23
【问题描述】:

我的情况是,我必须通过用于存储街道地址的 CharField 输出相当大的对象列表。

我的问题是,显然数据是按 ASCII 码排序的,因为它是一个字符域,结果可预测......它对数字进行排序;

1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 20, 21....

现在显而易见的步骤是将 Charfield 更改为正确的字段类型(比方说是 IntegerField),但它无法工作,因为某些地址可能有公寓......比如“128A”。

我真的不知道如何正确订购..

【问题讨论】:

  • 很想知道您是否找到了解决方案。非常感谢。 N

标签: django sorting


【解决方案1】:

如果您确定字段中只有整数,您可以让数据库通过extra 方法将其转换为整数,然后按此排序:

MyModel.objects.extra(
    select={'myinteger': 'CAST(mycharfield AS INTEGER)'}
).order_by('myinteger')

【讨论】:

  • 并非所有地址都以数字开头。这种方法适用于“我的 char 字段中有数字”的特殊情况,但无法对混合数据进行排序。
  • extra 的使用非常有趣,我很少使用这种方法。但不幸的是,它似乎不适用于我的情况。
  • 如果您的 MYSQL 版本不支持,请使用 'SIGNED' 或 'UNSIGNED' 而不是 INTEGER。
  • 由于 django 已弃用 extra(),以下是使用 annotate() 执行相同操作的方法:MyModel.objects.annotate(myinteger=RawSQL('CAST(mycharfield AS UNSIGNED)', params=[])).order_by('myinteger')
【解决方案2】:

Django 正在尝试deprecate extra() 方法,但在 v1.10 中引入了Cast()。在 sqlite 中(至少),CAST 可以采用诸如10a 之类的值并将其转换为整数10,因此您可以这样做:

from django.db.models import IntegerField
from django.db.models.functions import Cast

MyModel.objects.annotate(
    my_integer_field=Cast('my_char_field', IntegerField())
).order_by('my_integer_field', 'my_char_field')

这将首先返回按街道编号排序的对象,然后按字母顺序,例如...14, 15a, 15b, 16, 16a, 17...

【讨论】:

  • Cast 不支持10a 等值。如果它收到一个,它会抛出DataError: invalid input syntax for integer。我通过删除值中不是数字的所有字符(PostgreSQL)找到了一种解决方法:from django.db.models.expressions import F, Value, Funcqueryset.annotate(my_integer_field=Cast( Func(F('my_char_field'), Value('[^\d]'), Value(''), Value('g'), function='regexp_replace'), IntegerField()) )
  • @Filly 你能给出引发错误的完整示例吗?
  • @practual 您的解决方案适用于 Sqlite3,但在 PostgreSQL 上会出现错误,如 Filly 的评论。
【解决方案3】:

如果您使用的是 PostgreSQL(不确定 MySQL),您可以安全地在字符/文本字段上使用以下代码并避免转换错误:

MyModel.objects.extra(
    select={'myinteger': "CAST(substring(charfield FROM '^[0-9]+') AS INTEGER)"}
).order_by('myinteger')

【讨论】:

  • 如果您必须按带有数字的字符串排序,这是最好的选择,模式为 '.[0-9]+'
  • MariaDB 的语法是:“CAST(REGEXP_SUBSTR(name, '^[0-9]+') AS INTEGER)”。谢谢!!
  • 很棒的答案,当我查找该字段的外键时,我该怎么做?
  • 这对我不起作用,我得到的错误是:ProgrammingError: syntax error at or near "[" LINE 1: ...ECT DISTINCT ('SELECT CAST(substring(value FROM '^[0-9]+') A...。我使用 PostgreSQL 11、Python 2.7 和 Django 1.11。我想可能是因为我有旧版本。
【解决方案4】:

很棒的提示!这个对我有用! :) 那是我的代码:

revisioned_objects = revisioned_objects.extra(select={'casted_object_id': 'CAST(object_id AS INTEGER)'}).extra(order_by = ['casted_object_id'])

【讨论】:

    【解决方案5】:

    您遇到的问题与按文件名排序时文件名的排序方式非常相似。在那里,您希望“2 Foo.mp3”出现在“12 Foo.mp3”之前。

    一种常见的方法是将数字“规范化”以扩展为固定位数,然后根据规范化形式进行排序。也就是说,为了排序,“2 Foo.mp3”可能会扩展为“0000000002 Foo.mp3”。

    Django 不会在这里直接帮助你。您可以添加一个字段来存储“规范化”地址,并拥有数据库order_by,或者您可以在处理列表之前在您的视图(或视图使用的助手)中对地址记录进行自定义排序记录到模板。

    【讨论】:

      【解决方案6】:

      在我的例子中,我有一个带有名称字段的 CharField,例如,它具有混合 (int+string) 值。 “a1”、“f65”、“P”、“55”等 ..

      通过使用 sql cast(用 postgres 和 mysql 测试)解决了这个问题, 首先,我尝试按转换后的整数值排序,然后按名称字段的原始值。

      parking_slots = ParkingSlot.objects.all().extra(
              select={'num_from_name': 'CAST(name AS INTEGER)'}
          ).order_by('num_from_name', 'name')
      

      这样,无论如何,正确的排序对我有用。

      【讨论】:

        【解决方案7】:

        我知道我迟到了,但由于它与问题密切相关,而且我很难找到这个:

        你要知道,你可以直接把Cast放在你模型的ordering选项中。

        from django.db import models
        from django.db.models.functions import Cast
        
        
        class Address(models.Model):
        
            street_number = models.CharField()
        
            class Meta:
                ordering = [
                    Cast("street_number", output_field=models.IntegerField()),
                ]
        

        来自关于ordering的文档:

        您也可以使用查询表达式。

        还有关于database functions的文档:

        函数也是表达式,因此它们可以与聚合函数等其他表达式一起使用和组合。

        【讨论】:

          【解决方案8】:

          如果您需要对由点分隔的多个数字组成的版本号进行排序(例如1.9.0, 1.10.0),这是一个仅限 postgres 的解决方案:

          class VersionRecordManager(models.Manager):
          
              def get_queryset(self):
                  return super().get_queryset().extra(
                      select={
                          'natural_version': "string_to_array(version, '.')::int[]",
                      },
                  )
          
              def available_versions(self):
                  return self.filter(available=True).order_by('-natural_version')
          
              def last_stable(self):
                  return self.available_versions().filter(stable=True).first()
          
          class VersionRecord(models.Model):
              objects = VersionRecordManager()
              version = models.CharField(max_length=64, db_index=True)
              available = models.BooleanField(default=False, db_index=True)
              stable = models.BooleanField(default=False, db_index=True)
          

          如果您想允许非数字字符(例如0.9.0 beta、2.0.0 stable):

          def get_queryset(self):
              return super().get_queryset().extra(
                  select={
                      'natural_version':
                          "string_to_array(                     "  
                          "   regexp_replace(                   "  # Remove everything except digits
                          "       version, '[^\d\.]+', '', 'g'  "  # and dots, then split string into
                          "   ), '.'                            "  # an array of integers.
                          ")::int[]                             "
                  }
              )
          

          【讨论】:

            【解决方案9】:

            我正在寻找一种方法来对 CharField 中的数字字符进行排序,我的搜索将我带到了这里。我的对象中的 name 字段是 CC 许可证,例如“CC BY-NC 4.0”。

            由于extra() 将被弃用,我可以这样做:

            MyObject.objects.all()
                .annotate(sorting_int=Cast(Func(F('name'), Value('\D'), Value(''), Value('g'), function='regexp_replace'), IntegerField()))
                .order_by('-sorting_int')
            

            因此,MyObject 和 name='CC BY-NC 4.0' 现在有了 sorting_int=40。

            【讨论】:

              【解决方案10】:

              此线程中的所有答案都对我不起作用,因为它们假设为数字文本。我找到了一个适用于部分案例的解决方案。考虑这个模型

              Class Block(models.Model):
                    title = models.CharField()
              

              假设我的字段有时包含前导字符和尾随数字字符如果我尝试正常订购

               >>> Block.objects.all().order_by('title')
              <QuerySet [<Block: 1>, <Block: 10>, <Block: 15>, <Block: 2>, <Block: N1>, <Block: N12>, <Block: N4>]>
              

              正如预期的那样,它按字母顺序是正确的,但对我们人类来说毫无意义。我为这个特定用例所做的技巧是将我找到的任何文本替换为数字 9999,然后将值转换为整数并按其排序。

              对于大多数具有前导字符的情况,这将获得所需的结果。见下文

              from django.db.models.expressions import RawSQL
              
              >>> Block.objects.all()\
              .annotate(my_faux_integer=RawSQL("CAST(regexp_replace(title, '[A-Z]+', '9999', 'g') AS INTEGER)", ''))\    
              .order_by('my_faux_integer', 'title')
                  
              <QuerySet [<Block: 1>, <Block: 2>, <Block: 10>, <Block: 15>, <Block: N1>, <Block: N4>, <Block: N12>]>
              

              【讨论】:

                猜你喜欢
                • 2014-06-23
                • 1970-01-01
                • 2014-11-15
                • 1970-01-01
                • 2012-05-06
                • 2019-08-06
                • 2012-06-29
                • 2021-12-02
                • 2022-09-28
                相关资源
                最近更新 更多