2013-02-27 12 views
27

Vorrei ordinare un modello utilizzando l'opzione "NULLS LAST" di Postgresql. Come potrebbe essere fatto?Django: aggiunta di "NULLS LAST" per interrogare

ho provato qualcosa di simile

MyModel.objects.all().extra(order_by=('-price', 'NULLS LAST'))

ma ho

"Impossibile risolvere parola chiave 'NULLS ULTIMO' nel campo"

risposta

15

cosa più vicina che ho trovato lo sta facendo su due passi. In primo luogo l'ordinazione sul campo popolata e poi sui valori nulli:

Via this Gist (a sua volta tramite questi django logs):

all_projects = Project.objects.select_related().filter(
    company=company).order_by('-date_due') 

q = all_projects.extra(select={'date_due_null': 'date_due is null'}) 
q = q.extra(order_by=['date_due_null']) 
print q.query 

Buona fortuna.

+0

Grazie. Non è davvero la soluzione perfetta, ma un po 'funziona – GabiMe

+2

Prego. Non penso che ci sia una soluzione perfetta per questo, o almeno uno che è direttamente contemplato da Django, dato [questo gruppo di google post] (https://groups.google.com/d/msg/django-users/sxbpqmkRyCU/ qUYjX3niCOEJ) di Malcolm Treddinick. – Mariano

20

Se si desidera che venga eseguito in modo trasparente e su tutte le colonne, è possibile ridefinire la generazione SQL. Per fare ciò, è necessario che il proprio Manager restituisca il proprio QuerySet personalizzato per restituire la Query personalizzata per utilizzare il compilatore personalizzato. Il mio codice per che assomiglia a quella (Django 1.5):

from django.db import models, connections 

class NullsLastQuery(models.sql.query.Query): 
    """ 
    Query that uses custom compiler, 
    to utilize PostgreSQL feature of setting position of NULL records 
    """ 
    def get_compiler(self, using=None, connection=None): 
     if using is None and connection is None: 
      raise ValueError("Need either using or connection") 
     if using: 
      connection = connections[using] 

     # defining that class elsewhere results in import errors 
     from django.db.models.sql.compiler import SQLCompiler 
     class NullsLastSQLCompiler(SQLCompiler): 
      def get_ordering(self): 
       result, group_by = super(NullsLastSQLCompiler, self 
        ).get_ordering() 
       if self.connection.vendor == 'postgresql' and result: 
        result = [line + " NULLS LAST" for line in result] 
       return result, group_by 

     return NullsLastSQLCompiler(self, connection, using) 

class NullsLastQuerySet(models.query.QuerySet): 
    def __init__(self, model=None, query=None, using=None): 
     super(NullsLastQuerySet, self).__init__(model, query, using) 
     self.query = query or NullsLastQuery(self.model) 

class NullsLastManager(models.Manager): 
    def get_query_set(self): 
     return NullsLastQuerySet(self.model, using=self._db) 

class YourModel(models.Model): 
    objects = NullsLastManager() 
+5

Questo è leggermente malvagio, ma brillante. –

9

Per Django 1.9 (e possibilmente 1.8) è possibile utilizzare questo:

from django.db import connections, models 
from django.db.models.sql.compiler import SQLCompiler 


class NullsLastSQLCompiler(SQLCompiler): 
    def get_order_by(self): 
     result = super().get_order_by() 
     if result and self.connection.vendor == 'postgresql': 
      return [(expr, (sql + ' NULLS LAST', params, is_ref)) 
        for (expr, (sql, params, is_ref)) in result] 
     return result 


class NullsLastQuery(models.sql.query.Query): 
    """Use a custom compiler to inject 'NULLS LAST' (for PostgreSQL).""" 

    def get_compiler(self, using=None, connection=None): 
     if using is None and connection is None: 
      raise ValueError("Need either using or connection") 
     if using: 
      connection = connections[using] 
     return NullsLastSQLCompiler(self, connection, using) 


class NullsLastQuerySet(models.QuerySet): 
    def __init__(self, model=None, query=None, using=None, hints=None): 
     super().__init__(model, query, using, hints) 
     self.query = query or NullsLastQuery(self.model) 

E poi al modello (s):

objects = NullsLastQuerySet.as_manager() 

Questo è basato sulla risposta di Tim in https://stackoverflow.com/a/17077587/15690.

È stato riaperto il ticket per aggiungere supporto a questo servizio su Django: .

+0

mi hai salvato la giornata. – levi

+0

Hai qualche idea su come includere anche le funzionalità per posizionare le stringhe vuote alla fine? – Duncan

+1

@Duncan non senza essere pagato al momento, mi dispiace ..;) – blueyed

7

Questo probabilmente non era disponibile quando la domanda è stata posta, ma dal momento che Django 1.8 Penso che questa sia la soluzione migliore:

from django.db.models.functions import Coalesce, Value 
MyModel.objects.all().annotate(price_null= 
    Coalesce('price', Value(-100000000)).order_by('-price_null') 

Coalesce seleziona il primo valore non nullo, in modo da creare un valore price_null a l'ordine in base al quale è solo il prezzo ma con null sostituito da -100000000 (o +?).

+0

In realtà è un grande hack poiché non richiede la creazione di NULLS ULTIMI indici e cose del genere per velocizzare le query – valignatev

+1

@valentjedi Non questo richiede un indice di espressione con 'Coalesce ('price', Value (-100000000)'? Sarebbe come creare un indice NULLS LAST. Qui http://dba.stackexchange.com/a/99009 qualcuno afferma lo stesso di Sto dicendo – jperelli

+0

@jperelli hai ragione, ma nel mio caso questo hack è stato più veloce dell'aggiunta di NULLS LAST, quindi mi ha soddisfatto. – valignatev