2009-12-05 10 views
8

Ich habe ein Modell namens Valor. Valor hat einen Roboter. Ich frage wie folgt:schnelle Suche nach dem letzten Element in einem Django QuerySet?

, um die letzte Valor der R-Roboter zu erhalten. Valor.objects.filter (roboter = r) .count() ist ungefähr 200000 und das Abrufen der letzten Elemente dauert ungefähr 4 Sekunden in meinem PC.

Wie kann ich es beschleunigen? Ich frage in die falsche Richtung?

+0

Haben Sie eine sehr reich verzierte Struktur von ForeignKey-, OneToOneField- oder ManyToManyField-Beziehungen? –

+0

fwiw, das ist langsam, weil Sie alles in der 'valor' Tabelle auswählen und eine Django Modellinstanz für jeden Eintrag instanziieren, wenn Sie sie in eine Liste konvertieren (über' .reverse') und nur das erste Element in der Liste. – Carson

Antwort

3

Es klingt wie Ihr Datensatz wird groß genug sein, dass Sie die Dinge ein wenig denormalize möchten. Hast du versucht, das letzte Valor-Objekt im Robot-Objekt zu verfolgen?

class Robot(models.Model): 
    # ... 
    last_valor = models.ForeignKey('Valor', null=True, blank=True) 

und verwenden Sie dann ein post_savesignal das Update zu machen.

from django.db.models.signals import post_save 

def record_last_valor(sender, **kwargs): 
    if kwargs.get('created', False): 
     instance = kwargs.get('instance') 
     instance.robot.last_valor = instance 

post_save.connect(record_last_valor, sender=Valor) 

Sie werden die Kosten für eine zusätzliche db Transaktion zahlen, wenn Sie die Valor Objekte, sondern die last_valor Lookup schnell wird lodernden erstellen. Spielen Sie damit und sehen Sie, ob sich der Kompromiss für Ihre App lohnt.

+1

Dies ist eine gute Lösung für das Design-Problem, aber es lässt offen die Frage, warum sein Design schlecht lief, selbst wenn passender Anfragen als gegeben sein ursprüngliches. Mit richtiger Indizierung, Bestellung und Limits sollte sein ursprüngliches normalisiertes Design auch "flammend schnell" gewesen sein. Ich hoffe, der OP aktualisiert die Frage mit seinen Ergebnissen. –

+0

Django bat mich, es zu ändern: def record_last_valor (Absender, ** kwargs): wenn erstellt: instance.robot.last_valor = Instanz –

+0

Ich stimme zu Joe, dies versucht nicht, die zugrunde liegenden DB-Leistungsprobleme zu beantworten. Es schien, als gäbe es schon eine Menge über das Überprüfen von Indizes, und Ihr Vorschlag über qs.query sah nach dem bestmöglichen Weg aus, den Ansatz der Prüfindizes zu untersuchen. – istruble

3

Nun, es gibt keine order_by-Klausel, also frage ich mich, was Sie mit 'last' meinen. Angenommen, Sie meinten "zuletzt hinzugefügt",

Valor.objects.filter(robot=r).order_by('-id')[0] 

könnte die Arbeit für Sie erledigen.

+0

Im Valor-Modell habe ich Meta: ordering = ('id',). Ist damit Ihre Anfrage die gleiche wie meine? –

+0

Ich habe Ihre Lösung versucht und es ist langsamer als meins :( –

+0

Haben Sie einen geeigneten Index auf Ihre Valor-Tabelle für robot_id? –

0

Gibt es eine Limit-Klausel in Django? Auf diese Weise können Sie die db haben, einfach einen einzelnen Datensatz zurückgeben.

mysql

select * from table where x = y limit 1 

SQL Server

select top 1 * from table where x = y 

Orakel

select * from table where x = y and rownum = 1 

Ich weiß, dies nicht in django übersetzt wird, aber kann jemand kommen und reinigen diese nach oben.

+0

begrenzen und umzukehren, meinen Sie. Richtig? –

+0

In Django Sie begrenzen mit Pythons Slicing sintax Valor.objects.filter (roboter = r) .reverse() [0: 1] .get() –

+0

Ich habe gerade diese letzte Abfrage ausprobiert und ist so langsam wie meine ursprüngliche. –

7

Wenn keiner der früheren Vorschläge funktioniert, würde ich vorschlagen, Django aus der Gleichung zu nehmen und diese rohe SQL gegen Ihre Datenbank laufen zu lassen. Ich rate um Ihre Tabellennamen, also müssen Sie möglicherweise entsprechend anpassen:

Ist das langsam? Wenn ja, lassen Sie Ihr RDBMS (MySQL?) Den Abfrageplan für Sie erklären. Dies wird Ihnen sagen, ob es vollständige Tabellen-Scans macht, die Sie offensichtlich nicht mit einer so großen Tabelle wollen. Sie können auch Ihre Frage bearbeiten und das Schema für die Tabelle valor für uns einfügen.

Auch können Sie die SQL sehen, dass Django ist, indem Sie diese zu erzeugen (die Abfrage-Set von Peter Rowell versehen mit):

qs = Valor.objects.filter(robot=r).order_by('-id')[0] 
print qs.query 

Stellen Sie sicher, dass SQL auf die ‚raw‘ Abfrage ähnlich ist ich gepostet über. Sie können auch Ihren RDBMS diesen Abfrageplan für Sie erläutern lassen.

+1

qs.query (oder .query.as \ _sql()) ist definitiv der Weg für jemanden, der versucht, dieses Problem von der DB-Seite zu finden.Personen, die sich fragen, wie Slicing/Sql Limit, Order_by() und Filter() sind auf einem niedrigen Niveau gehandhabt sollte mit diesem ein wenig spielen. – istruble

7

Die optimale MySQL-Syntax für dieses Problem wäre etwas entlang der Linien der folgenden sein:

Valor.objects.filter(robot=r).order_by('-id')[:1][0] 

Beachten Sie, wie diese Lösung auf Djangos slicing Methode verwendet:

SELECT * FROM table WHERE x=y ORDER BY z DESC LIMIT 1 

Das django Äquivalent sein würde Begrenzen Sie die Abfragegruppe vor Kompilieren der Liste der Objekte.

1

Ganz schnell sollte auch sein:

qs = Valor.objects.filter(robot=r) # <-- it doesn't hit the database 
count = qs.count()     # <-- first hit the database, compute a count 
last_item = qs[ count-1 ]   # <-- second hit the database, get specified rownum 

also in der Praxis führen Sie nur 2 von SQL-Abfragen;)

0

der richtige Weg, dies zu tun, ist die verwenden integrierte in QuerySet Verfahren neueste() und füttern Sie die Spalte (Feldname), nach der sortiert werden soll. Der Nachteil ist, dass es nur nach einer einzelnen db-Spalte sortieren kann.

Die aktuelle Implementierung sieht so aus und ist im selben Sinne wie @ Aarons Vorschlag optimiert.

def latest(self, field_name=None): 
    """ 
    Returns the latest object, according to the model's 'get_latest_by' 
    option or optional given field_name. 
    """ 
    latest_by = field_name or self.model._meta.get_latest_by 
    assert bool(latest_by), "latest() requires either a field_name parameter or 'get_latest_by' in the model" 
    assert self.query.can_filter(), \ 
      "Cannot change a query once a slice has been taken." 
    obj = self._clone() 
    obj.query.set_limits(high=1) 
    obj.query.clear_ordering() 
    obj.query.add_ordering('-%s' % latest_by) 
    return obj.get() 
0
Model_Name.objects.first() 

// Für erste bekommen Element

Model_name.objects.last() 

// Für letzte get()

in meinem Fall letzte Arbeit ist nicht, weil es nur eine Zeile in der Datenbank ist auch für u voll helfen kann :)

Verwandte Themen