2010-06-03 16 views
8

Estoy en una situación en la que debo producir una lista bastante grande de objetos mediante un CharField usado para almacenar direcciones de calles.Django: ordenando el valor numérico con order_by

Mi problema es que obviamente los datos están ordenados por códigos ASCII ya que es un Charfield, con los resultados predecibles ... ordena los números como este;

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

Ahora el paso obvio sería cambiar la Charfield el tipo de campo adecuado (IntegerField digamos), sin embargo, no puede trabajar desde alguna dirección podría tener apartamentos .. como "128A".

Realmente no sé cómo puedo pedir este adecuadamente ..

+0

curiosidad por saber si has encontrado una solución a esto. Muchas gracias. N –

Respuesta

15

Si está seguro de que sólo hay números enteros en el campo, se puede obtener la base de datos para su emisión como un entero a través del método extra, y el orden que:

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

No todas las direcciones comienzan con un número. Este enfoque funciona para el caso especial de "Tengo números en mi campo", pero no podrá ordenar los datos mezclados. –

+0

Uso muy interesante de extra, rara vez juego con ese método ... Pero desafortunadamente no parece funcionar en mi situación. –

+2

Use 'SIGNED' o 'UNSIGNED' en lugar de INTEGER si su versión MYSQL no lo admite. – Adriaan

2

El problema que está en contra es bastante similar a cómo obtener los nombres de archivo ordenaron al ordenar por nombre de archivo. Allí, quiere que aparezca "2 Foo.mp3" antes de "12 Foo.mp3".

Un enfoque común es "normalizar" los números para expandirlos a un número fijo de dígitos, y luego ordenarlos según la forma normalizada. Es decir, para fines de clasificación, "2 Foo.mp3" podría expandirse a "0000000002 Foo.mp3".

Django no lo ayudará directamente aquí. Puede agregar un campo para almacenar la dirección "normalizada" y tener la base de datos order_by que, o puede hacer una ordenación personalizada en su vista (o en un ayudante que usa su vista) en registros de direcciones antes de entregar la lista de registros a una plantilla.

3

gran consejo! ¡Esto funciona para mi! :) Esa es mi código:

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

Si está utilizando PostgreSQL (no estoy seguro sobre MySQL) se puede utilizar con seguridad siguiente código en campos char/texto y evitar errores elenco:

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

¡Esta es una excelente sugerencia! –

+0

Este es el mejor si tiene que ordenar por cadenas que tienen números, con un patrón '. [0-9] +' – Ajoy

+0

La sintaxis para MariaDB sería: "CAST (REGEXP_SUBSTR (name, '^ [0-9] + ') COMO INTEGER) ". ¡¡Gracias!! –

7

Django está intentando deprecate el método extra(), pero ha introducido Cast() en v1.10. En sqlite (por lo menos), CAST puede tener un valor como 10a y se lo echó al entero 10, por lo que puede hacer:

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

que devolverá los objetos ordenados por el número de la calle primera numéricamente, luego alfabéticamente , p.ej ...14, 15a, 15b, 16, 16a, 17...

+1

'Cast' no admite valores como' 10a'. Si recibe uno, arroja 'DataError: sintaxis de entrada inválida para entero '. He encontrado una solución mediante la eliminación de todos los caracteres en el valor que no son números (PostgreSQL): ' de django.db.models.expressions importar F, el valor, Func' ' queryset.annotate (my_integer_field = moldeada ( Func (F ('my_char_field'), Valor ('[^ \ d]'), el valor (''), el valor ('g'), función = 'REGEXP_REPLACE'), IntegerField()) ) ' – Filly

+1

@Filly ¿Puedes dar el ejemplo completo que arrojó el error? – practual

1

En caso de que necesite para ordenar los números de versión que consta de varios números separados por un punto (por ejemplo 1.9.0, 1.10.0), aquí es una solución de postgres solamente:

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) 

En caso de que quiera permitir que no numérico personajes (por ejemplo 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[]        " 
     } 
    ) 
Cuestiones relacionadas