2010-06-03 1 views
8

Sono in una situazione in cui devo generare un elenco piuttosto grande di oggetti da un CharField utilizzato per memorizzare gli indirizzi.Django: ordinamento del valore numerico con order_by

Il mio problema è che ovviamente i dati sono ordinati in base ai codici ASCII dato che si tratta di un campo di battaglia, con risultati prevedibili .. ordina i numeri come questo;

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

ora il passo ovvia sarebbe quella di cambiare il Charfield il tipo di campo corretto (IntegerField diciamo), tuttavia non può funzionare in quanto qualche indirizzo potrebbe avere appartamenti .. come "128A".

io davvero non so come posso ordinare questo correttamente ..

+0

Curioso di sapere se hai trovato una soluzione a questo. Grazie molto. N –

risposta

15

Se sei sicuro ci sono solo numeri interi in campo, si potrebbe ottenere il database per lanciarla come un numero intero tramite il metodo extra, e l'ordine da quel:

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

Non tutti gli indirizzi iniziano con un numero. Questo approccio funziona per il caso speciale di "I have numbers in my char field", ma non riuscirà a ordinare i dati misti. –

+0

Uso molto interessante di extra, gioco raramente con quel metodo .. Ma sfortunatamente non sembra funzionare nella mia situazione. –

+2

Utilizzare 'SIGNED' o 'UNSIGNED' invece di INTEGER se la versione di MySQL non lo supporta. – Adriaan

2

Il problema si sta di fronte è molto simile al modo in cui i nomi dei file vengono ordinati durante l'ordinamento per nome del file. Lì, vuoi che "2 Foo.mp3" appaia prima di "12 Foo.mp3".

Un approccio comune è quello di "normalizzare" i numeri per l'espansione su un numero fisso di cifre e quindi l'ordinamento basato sulla forma normalizzata. Cioè, ai fini dell'ordinamento, "2 Foo.mp3" potrebbe espandersi a "0000000002 Foo.mp3".

Django non ti aiuterà direttamente qui. È possibile aggiungere un campo per memorizzare l'indirizzo "normalizzato" e disporre del database order_by oppure è possibile eseguire un ordinamento personalizzato nella visualizzazione (o in un helper utilizzato dalla vista) sui record degli indirizzi prima di passare all'elenco dei record a un modello.

3

Grande punta! Per me funziona! :) Questo è il mio codice:

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

Se stai usando PostgreSQL (non sono sicuro su MySQL) si può tranquillamente utilizzare seguente codice a char campi/testo e evitare errori del cast:

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

Questo è un ottimo suggerimento! –

+0

Questo è il migliore se devi ordinare per stringhe che hanno numeri, con un modello '. [0-9] +' – Ajoy

+0

La sintassi per MariaDB sarebbe: "CAST (REGEXP_SUBSTR (nome, '^ [0-9] + ') AS INTEGER) ". Grazie!! –

7

Django sta provando a deprecate il metodo extra(), ma ha introdotto Cast() nella v1.10. In sqlite (almeno), CAST può assumere un valore come 10a e si gettò al numero intero 10, in modo da poter fare:

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

che tornerà in ordine a seconda del numero civico prima numerico, poi in ordine alfabetico , per esempio ...14, 15a, 15b, 16, 16a, 17...

+1

'Cast' non supporta valori come' 10a'. Se ne riceve uno, genera 'DataError: sintassi di input non valida per intero '. Ho trovato una soluzione, eliminando tutti i caratteri del valore che non è un numero (PostgreSQL): 'da django.db.models.expressions importare F, di valore, Func' ' queryset.annotate (my_integer_field = Cast ( func (F ('my_char_field'), valore ('[^ \ d]'), valore (''), valore ('g'), funzione = 'REGEXP_REPLACE'), IntegerField()) ) ' – Filly

+1

@Filly puoi dare l'esempio completo che ha generato l'errore? – practual

1

In caso di necessità di ordinare i numeri di versione composta da più numeri separati da un punto (ad esempio 1.9.0, 1.10.0), ecco una soluzione postgres soli:

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) 

Nel caso in cui si desidera consentire non numerico caratteri (ad esempio 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[]        " 
     } 
    )