2009-10-30 18 views
20

Chcę mieć model z polami obliczanymi, na które mogę zastosować sortowanie. Na przykład, powiedzmy, że mam następujący model:django - sortowanie zapytań według pola obliczeniowego

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

chcę mieć D i pole E, które są obliczane według następujących wzorów:

  1. D = A - B
  2. E = A - X (gdzie X jest polem odpowiedniego rekordu modelu C)

Wdrożenie tego byłoby trywialne, gdybym nie musiał stosować sortowania; Chciałbym tylko dodać właściwości do klasy modelu. Muszę jednak zamówić przez te pola.

Rozwiązaniem jest pobieranie wszystkich zapisów do pamięci i sortowanie tam, co uważam za ostateczne rozwiązanie (złamie to, co dotyczy stronicowania).

Czy istnieje sposób osiągnięcia tego, co próbuję? Wszelkie wskazówki są mile widziane.

EDYTOWANIE: Denormalizacja jest bezużyteczna. Wartość pola X zmienia się bardzo często, a wiele rekordów Foo jest powiązanych z jednym rekordem modelu C. Aktualizacja X będzie wymagać tysięcy aktualizacji E.

+1

Wystarczy podkreślić, że podobne pytanie zostało zadane: http : //stackoverflow.com/questions/930865/how-to-sort-by-a-computed-value-in-django – cethegeek

Odpowiedz

15

Chciałbym rzucić okiem na metodę extra w kwerendie i określ parametr order_by order_by.

+0

O ile D i E nie zmaterializowały się w bazie danych, nie widzę, w jaki sposób dodatkowy (order_by) pomoże. – cethegeek

+0

Kolejność @celopes sql działa z polami obliczonymi w locie, dlatego odpowiednia metoda jest tutaj odpowiednia. – shanyu

+0

Nie należy polegać na 'extra()', ponieważ jest to przestarzałe. –

1

Obecnie nie mam uruchomionej instalacji Django, ale myślę, że to, co chcesz zrobić, to zrobić niestandardowy zapis, taki, że D i E są generowane automatycznie. Nie wiem, jaki jest twój zwrot ForeignKey na unicode, więc zakładam, że to nie jest ciąg znaków i przypisanie "valueName" jako tokenu tokena dla liczby całkowitej, której chcesz użyć.

W każdym razie, należy przejść trochę tak:

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() 

Wszystko przed ostatnim wierszu tego (super()) będzie PRE uratować, wszystko po to POST. To naprawdę najważniejszy punkt.

+1

Jest to OK, jeśli Shanyu nie ma nic przeciwko zmaterializowaniu obliczonych pól w bazie danych. – cethegeek

+1

Proszę zobaczyć moją edycję, denormalizacja nie jest opcją. Dzięki za pomoc. – shanyu

27

Jeśli nie przeszkadza trochę logiki duplicaton, następnie dodaje zadziała:

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

jeśli potrzebujesz przekazać parametry, powinieneś użyć polecenia OrderedDict: https://docs.djangoproject.com/en/1.8/ref/models/querysets/#extra – fjsj

+0

Proszę nie polegać na 'extra()', ponieważ jest to to ma być przestarzałe. –

+1

'Foo.objects.extra (select = {'d_field': 'A - B'}, order_by = ['d_field'])' jeden 'extra''' wystarczy – WeizhongTu

0

Uważam, że bez args * oraz ** kwargs w sposobie ratowania, zwraca błąd. I jak stwierdził celopes, jest to tylko rozwiązanie, jeśli nie masz nic przeciwko zmaterializowaniu obliczonego pola w bazie danych.

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"] 
13

Proszę nie używać extra(), ponieważ ma być przestarzałe w przyszłości.

Od Django 1.7 można użyć kombinacji annotate() i order_by() aby osiągnąć ten

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

Jest również ungoing pracy w celu umożliwienia wyrażenia mają być stosowane na całym ORM więc następujące powinien działać w przyszłych wersjach Django:

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

Jako Simon says można teraz używać wyrażeń w zapytaniach, a te wartości będą obliczane w bazie danych. Oto pytania Pytałeś o z nową techniką Sortowanie:

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

Oto pełna runnable przykład, że gra z tych wyrażeń:

# 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() 
Powiązane problemy