2010-06-03 19 views
8

Jestem w sytuacji, w której muszę wypisać dość dużą listę obiektów przez CharField służący do przechowywania adresów ulicznych.Django: zamawianie wartości liczbowej z order_by

Moim problemem jest to, że dane są oczywiście uporządkowane według kodów ASCII, ponieważ jest to Charfield, z przewidywalnymi wynikami ... sortuje liczby takie jak to;

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

Teraz oczywistym krokiem byłoby zmienić Charfield odpowiedni typ pola (IntegerField powiedzmy), to jednak nie może pracować, ponieważ niektóre adres może mieć apartamentów .. jak „128a”.

ja naprawdę nie wiem, jak mogę zamówić to poprawnie ..

+0

Ciekawy wiedzieć, czy znaleźli rozwiązanie tego. Wielkie dzięki. N –

Odpowiedz

15

Jeżeli jesteś pewien, że są tylko liczby całkowite w zakresie, można uzyskać bazę do oddania go jako liczbę całkowitą metodą extra i zamówienie to:

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

Nie wszystkie adresy zaczynają się od numeru. To podejście działa na specjalnym przypadku "Mam numery w polu char", ale nie posortuje mieszanych danych. –

+0

Bardzo ciekawe wykorzystanie dodatkowych, rzadko gram z tą metodą. Ale niestety nie działa to w mojej sytuacji. –

+2

Używaj opcji 'SIGNED' lub 'UNSIGNED' zamiast INTEGER, jeśli twoja wersja MYSQL go nie obsługuje. – Adriaan

2

Problem jesteś przeciw jest bardzo podobny do tego, jak się nazwy plików zamawiane przy sortowaniu według nazwy pliku. Tam chcesz "2 Foo.mp3" pojawić się przed "12 Foo.mp3".

Typowym podejściem jest "normalizacja" liczb w celu rozszerzenia do ustalonej liczby cyfr, a następnie sortowanie w oparciu o znormalizowany formularz. Oznacza to, że w celu sortowania "2 Foo.mp3" może rozwinąć się do "0000000002 Foo.mp3".

Django nie pomoże tutaj bezpośrednio. Możesz dodać pole do przechowywania "znormalizowanego" adresu i mieć bazę danych order_by, lub możesz zrobić sortowanie niestandardowe w widoku (lub w pomocniku, którego używa twój widok) w rekordach adresów przed przekazaniem listy rekordów do szablonu.

3

wielkim wskazówka! Mi to pasuje! :) To mój kod:

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

Jeśli używasz PostgreSQL (nie wiem o MySQL) można bezpiecznie użyć poniższy kod na Char polach/tekst i uniknąć błędów Obsada:

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

To doskonała propozycja! –

+0

To jest najlepsze, jeśli musisz sortować według ciągów, które mają numery, ze wzorcem ". [0-9] + ' – Ajoy

+0

Składnia dla MariaDB będzie następująca:" CAST (REGEXP_SUBSTR (name,'^[0-9] + ') AS INTEGER) ". Dzięki!! –

7

Django próbuje deprecate metody extra(), ale wprowadził Cast() w v1.10. W SQLite (co najmniej), CAST może przyjmować wartość taką jak 10a i rzuci go do całkowitej 10, więc można zrobić:

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

który powróci obiektów posortowane według liczby ulicy najpierw liczebnie, a następnie alfabetycznie np ...14, 15a, 15b, 16, 16a, 17...

+1

'Cast' nie obsługuje wartości takich jak' 10a'. Jeśli otrzyma jeden, zgłasza 'DataError: niepoprawna składnia wejściowa dla liczby całkowitej'. Znalazłem obejście poprzez usunięcie wszystkich znaków w wartości, które nie są numery (PostgreSQL): 'From django.db.models.expressions importować F, wartość, Func' ' queryset.annotate (my_integer_field = Obsada ( func (F ('my_char_field') wartość ('[^ \ d]'), wartość (''), wartość ('g'), funkcja = 'regexp_replace') IntegerField()) ) ' – Filly

+1

@ Czy możesz podać pełny przykład, który spowodował błąd? – practual

1

W przypadku trzeba sortować numery wersji składa się z wielu cyfr oddzielonych kropką (np 1.9.0, 1.10.0), tutaj jest postgres-jedynie rozwiązanie:

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) 

W przypadku, gdy chcesz, aby umożliwić nienumeryczny znaków (np 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[]        " 
     } 
    ) 
Powiązane problemy