【问题标题】:django - ordering queryset by a calculated fielddjango - 按计算字段排序查询集
【发布时间】:2010-12-11 18:35:51
【问题描述】:

我想要一个模型,其中包含可以应用排序的计算字段。例如,假设我有以下模型:

class Foo(models.Model):
    A = models.IntegerField(..)
    B = models.IntegerField(..)
    C = models.ForeignKey(..)

我想要一个 D 和一个 E 字段,通过以下公式计算:

  1. D = A - B
  2. E = A - X(其中 X 是模型 C 的相关记录的字段)

如果我不需要应用排序,那么实现这将是微不足道的;我只是将属性添加到模型类。但是,我需要按这些字段排序。

一个解决方案是将所有记录提取到内存中并在那里进行排序,我认为这是最后的手段(它会破坏关于分页的事情)。

有没有办法实现我正在尝试的目标?任何指导表示赞赏。

编辑: 非规范化是不行的。字段X的值变化非常频繁,大量的Foo记录与模型C的一条记录相关。X的更新需要E的数千次更新。

【问题讨论】:

标签: django


【解决方案1】:

如果您不介意一些逻辑重复,那么以下将起作用:

Foo.objects.extra(select={'d_field': 'A - B'}).extra(order_by=['d_field'])

【讨论】:

【解决方案2】:

请不要使用extra(),因为它会在未来被弃用。

从 Django 1.7 开始,您可以使用 annotate()order_by() 的组合来实现此目的

Foo.objects.annotate(ordering=F('A') - F('B')).order_by('ordering')

还有一项正在进行的工作是允许在整个 ORM 中使用表达式,因此以下内容应该可以在 Django 的未来版本中使用:

Foo.objects.order_by(F('A') - F('B'))

【讨论】:

    【解决方案3】:

    我会看看 Queryset 上的 extra 方法并指定 order_by 参数。

    【讨论】:

    • 除非 D 和 E 在数据库中具体化,否则我看不出额外的 (order_by) 会有什么帮助。
    • @celopes sql 的 order by 适用于动态计算字段,因此此处适用额外的方法。
    • 请不要依赖extra(),因为它应该被弃用。
    【解决方案4】:

    作为Simon says,您现在可以在查询中使用表达式,这些值将在数据库中计算。以下是您使用新排序技术提出的查询:

    Foo.objects.order_by(F('a') - F('b'))
    Foo.objects.order_by(F('a') - F('bar__x'))
    

    这是一个使用这些表达式的完整可运行示例:

    # Tested with Django 1.9.2
    import logging
    import sys
    
    import django
    from django.apps import apps
    from django.apps.config import AppConfig
    from django.conf import settings
    from django.db import connections, models, DEFAULT_DB_ALIAS
    from django.db.models import F
    from django.db.models.base import ModelBase
    from django.db.models.functions import Concat, Value
    
    from mock import patch, PropertyMock, MagicMock
    
    NAME = 'udjango'
    
    
    def main():
    
        setup()
    
        class Bar(models.Model):
            x = models.IntegerField()
    
        class Foo(models.Model):
            a = models.IntegerField()
            b = models.IntegerField()
            bar = models.ForeignKey(Bar)
    
        syncdb(Bar)
        syncdb(Foo)
    
        bar1 = Bar.objects.create(x=1)
        bar5 = Bar.objects.create(x=5)
        Foo.objects.create(a=10, b=3, bar=bar1)
        Foo.objects.create(a=13, b=3, bar=bar5)
        Foo.objects.create(a=15, b=9, bar=bar1)
    
        print(Foo.objects.annotate(ordering=F('a') - F('b'))
              .order_by('ordering').values_list('a', 'b', 'bar__x', 'ordering'))
        # >>> [(15, 9, 1, 6), (10, 3, 1, 7), (13, 3, 5, 10)]
    
        print(Foo.objects.annotate(ordering=F('a') - F('bar__x'))
              .order_by('ordering').values_list('a', 'b', 'bar__x', 'ordering'))
        # >>> [(13, 3, 5, 8), (10, 3, 1, 9), (15, 9, 1, 14)]
    
        print(Foo.objects.order_by(F('a') - F('b')).values_list('a', 'b', 'bar__x'))
        # >>> [(15, 9, 1), (10, 3, 1), (13, 3, 5)]
    
        print(Foo.objects.order_by(F('a') - F('bar__x')).values_list('a', 'b', 'bar__x'))
        # >>> [(13, 3, 5), (10, 3, 1), (15, 9, 1)]
    
        logging.info('Done.')
    
    
    def setup():
        db_file = NAME + '.db'
        with open(db_file, 'w'):
            pass  # wipe the database
        settings.configure(
            DEBUG=True,
            DATABASES={
                DEFAULT_DB_ALIAS: {
                    'ENGINE': 'django.db.backends.sqlite3',
                    'NAME': db_file}},
            LOGGING={'version': 1,
                     'disable_existing_loggers': False,
                     'formatters': {
                        'debug': {
                            'format': '%(asctime)s[%(levelname)s]'
                                      '%(name)s.%(funcName)s(): %(message)s',
                            'datefmt': '%Y-%m-%d %H:%M:%S'}},
                     'handlers': {
                        'console': {
                            'level': 'DEBUG',
                            'class': 'logging.StreamHandler',
                            'formatter': 'debug'}},
                     'root': {
                        'handlers': ['console'],
                        'level': 'INFO'},
                     'loggers': {
                        "django.db": {"level": "DEBUG"}}})
        app_config = AppConfig(NAME, sys.modules['__main__'])
        apps.populate([app_config])
        django.setup()
        original_new_func = ModelBase.__new__
    
        # noinspection PyDecorator
        @staticmethod
        def patched_new(cls, name, bases, attrs):
            if 'Meta' not in attrs:
                class Meta:
                    app_label = NAME
                attrs['Meta'] = Meta
            return original_new_func(cls, name, bases, attrs)
        ModelBase.__new__ = patched_new
    
    
    def syncdb(model):
        """ Standard syncdb expects models to be in reliable locations.
    
        Based on https://github.com/django/django/blob/1.9.3
        /django/core/management/commands/migrate.py#L285
        """
        connection = connections[DEFAULT_DB_ALIAS]
        with connection.schema_editor() as editor:
            editor.create_model(model)
    
    main()
    

    【讨论】:

      【解决方案5】:

      我目前还没有运行 Django 安装,但我认为您要问的是如何进行自定义保存,以便自动生成 D 和 E。我不知道您的 ForeignKey 在 unicode 上的返回值是多少,所以我假设它不是字符串并将“valueName”分配为您要使用的整数的令牌值。

      无论如何,它应该是这样的:

      class Foo(models.Model):
          A = models.IntegerField(..)
          B = models.IntegerField(..)
          C = models.ForeignKey(..)
          D = models.IntegerField(..)
          E = models.IntegerField(..)
          def save(self):
              self.D = self.A - self.B
              self.E = self.A - self.C.valueName
              super(Foo, self).save()
      

      最后一行 (super()) 之前的任何内容都将是 PRE 保存,之后的任何内容都是 POST。这确实是最重要的一点。

      【讨论】:

      • 如果shanyu不介意在数据库中具体化计算字段,这也可以。
      • 请看我的编辑,非规范化不是一个选项。感谢您的帮助。
      【解决方案6】:

      我发现 save 方法中没有 *args 和 **kwargs 会返回错误。正如 celopes 所说,如果您不介意在数据库中实现计算域,这只是一种解决方案。

      class Foo(models.Model):
          A = models.IntegerField(..)
          B = models.IntegerField(..)
          C = models.ForeignKey(..)
          D = models.IntegerField(..)
          E = models.IntegerField(..)
      
          def save(self, *args, **kwargs):
              self.D = self.A - self.B
              self.E = self.A - self.C.X
              super(Foo, self).save(*args, **kwargs)
      
          class Meta:
              ordering = ["E", "D"]
      

      【讨论】:

        猜你喜欢
        • 2020-08-16
        • 1970-01-01
        • 2015-08-31
        • 2012-12-20
        • 2012-01-26
        • 1970-01-01
        • 2015-12-20
        • 1970-01-01
        • 2012-07-30
        相关资源
        最近更新 更多