Django-Filter basierend auf der Textlänge

Antworten:

-17

Es wäre viel besser und schneller, wenn Sie nur eine Spalte hinzufügen, die die Länge des Textes vorberechnet (auswendig lernt).

z.B

class MyModel(models.Model):
    text = models.TextField()
    text_len = models.PositiveIntegerField()

     def save(self, *args, **kwargs):
         self.text_len = len(self.text)
         return super(MyModel, self).save(*args, **kwargs)

MyModel.objects.filter(text_len__gt = 10)     # Here text_len is pre-calculated by us on `save`
Rantanplan
quelle
Liegt es daran, dass das Textfeld nicht indiziert ist und die Textlänge jedes Mal berechnet wird, wenn die Abfrage die Datenbank erreicht? Die von lain vorgeschlagene Lösung macht dasselbe, nicht wahr (obwohl diese Lösung bei mir nicht funktioniert).
Ashish
@ashish 1) Ja, es ist vorberechnet. 2) Kein Liegender macht nicht dasselbe.
Rantanplan
1) Wenn also die Länge vorberechnet ist, warum muss ich dann eine andere Spalte haben? 2) Die Lösung von lain prüft nicht für jeden Ausdruck, ob das Auftreten von Zeichen größer als n ist?
Ashish
1
@ashish Ich habe einen Kommentar in die letzte Zeile des obigen Codes eingefügt. Wir fügen dem Modell eine Spalte hinzu, um die Länge des zu speichern text. Dies wird jedes Mal aktualisiert, wenn der Text geändert wird. Wenn wir also das Modell abfragen, können wir nach der Textlänge filtern, die WIR für unsere saveMethode vorberechnet haben .
Rantanplan
203

Für Django> = 1.8 können Sie die Length-Funktion verwenden , die @ Pratyush CHAR_LENGTH()unter der Haube für MySQL oder LENGTH()für einige andere Datenbanken ist:

from django.db.models.functions import Length
qs = MyModel.objects.annotate(text_len=Length('text_field_name')).filter(
    text_len__gt=10)
Kochfelder
quelle
1
Angenommen, ich möchte das Abfrageset nicht filtern, sondern gebe mich stattdessen mit den Objekten text_len__gt=10an erster Stelle zurück ( order_by). Irgendein Hinweis?
Vabada
3
@dabad, können Sie die Verwendung text_len Annotation in der gleichen Art und Weise Sie eine andere Datenbank verwenden würden Feld , so dass es in funktioniert order_byoder Sumoder was auch immer. So sortieren Sie die Ergebnisse in abnehmender Reihenfolge der Textlänge und geben die Längenwerte zurück : MyModel.objects.annotate(text_len=Length('text_field_name')).order_by('-text_len').values_list('text_len', flat=True).
Kochfelder
1
@guettli Ein Problem mit der akzeptierten Antwort ist, dass das Originalplakat das letzte Mal im September 2015 bei SO gesehen wurde und Ihr bewundernswerter Altruismus die einzige Möglichkeit war :-) Ich musste diese Antwort bearbeiten, bevor ich abstimmen konnte. Ich habe eine ähnliche Antwort für Django> = 1.9 hinzugefügt , für die keine Anmerkungen erforderlich sind, sondern eine globale Registrierung von LengthTransform.
Hynekcer
1
Dies ist in den Dokumenten sehr schwer zu finden, da es nicht mit anderen Aggregationen wie gruppiert ist Sum. Es ist auch in vielen Fällen äußerst wichtig. Ich hatte einen Fall, in dem ich die maximale Datengröße überprüfen musste, die eine Abfrage möglicherweise zurückgibt. Der Server verfügt nicht über genügend Arbeitsspeicher, und eine Variante davon funktionierte einwandfrei.
AlanSE
59

Ein anderer Weg ist:

MyModel.objects.extra(where=["CHAR_LENGTH(text) > 300"])

Dies kann verwendet werden, wenn die Textlänge ebenfalls mehr als 255 Zeichen beträgt.

Pratyush
quelle
4
Wenn Sie SQLite haben, ist es LENGTH(..).
Andrei-Niculae Petre
40

Eine gute Lösung für Django> = 1.9 ist möglich, indem die eingebaute Funktion Lengthals Transformation für die CharFieldSuche registriert wird .

Registrieren Sie die Transformation einmal im Projekt. (Der beste Ort ist wahrscheinlich models.py.)

from django.db.models import CharField
from django.db.models.functions import Length

CharField.register_lookup(Length, 'length')

Verwendung :

result = MyModel.objects.filter(text__length__gt=10)

Siehe genau das gleiche Beispiel in den Dokumenten für Länge als Transformation .


Es funktioniert korrekt für alle Backends, kompiliert von LENGTH()für die meisten Backends und von CHAR_LENGTH()für MySQL. Es wird dann automatisch für alle Unterklassen von CharField registriert, z. B. für EmailField. Die TextFieldmüssen einzeln registriert werden. Es ist sicher, den Namen "Länge" zu registrieren, da ein Transformationsname niemals durch einen gleichnamigen Feldnamen oder einen verwandten Feldnamen schattiert oder schattiert werden kann.

Der einzige Nachteil könnte das Lesbarkeitsrätsel sein: Woher kommt die "Länge"? (Die Suche ist global, kann jedoch glücklicherweise wiederholt in mehreren Modulen sicher registriert werden, wenn dies für die Lesbarkeit nützlich ist, ohne dass ein möglicher Overhead zur Laufzeit der Abfrage möglich ist.)

Eine andere ähnlich wertvolle Lösung sind die oben genannten Kochfelder , die kürzer sind, wenn eine Registrierung zählt und wenn eine ähnliche Abfrage nicht wiederholt verwendet wird.

Hynekcer
quelle
@guettli Unerwartet, dass Sie zuerst und eine Minute vor Beginn der Prämie eine Lösung geschrieben haben? Ich habe es auch in einer seltsamen Reihenfolge gemacht: Ich habe Details für die Lösung aus der Django-Quelle gefunden und dann festgestellt, dass alles in den Dokumenten darüber steht, dass Sie die Lösung zuerst gekannt haben.
Hynekcer
Ich habe ein Kopfgeld gestartet, da die akzeptierte Frage, die leider immer noch die oberste ist, veraltet ist. Ich hatte gehofft, dass die Antwort mit der Längenfunktion (> = Django 1.8) mehr positive Stimmen bekommt. AFAIK das ist passiert, aber leider ist die veraltete Antwort immer noch oben.
Guettli
29

Mit dem Regex-Filter können Sie nach Text einer bestimmten Länge suchen:

MyModel.objects.filter(text__regex = r'.{10}.*')

Vorsichtsmaßnahme: Für MySQL beträgt der maximale Längenwert 255. Andernfalls wird eine Ausnahme ausgelöst:

DatabaseError: (1139, "Got error 'invalid repetition count(s)' from regexp")
Iain Shelvington
quelle
3
Wie die Dokumentation sagt:Using raw strings (e.g., r'foo' instead of 'foo') for passing in the regular expression syntax is recommended.
Sergey Goliney
Ich erhalte diese Ausnahme, nachdem der Code OperationalError ausgeführt wurde: (1139, "Fehler 'ungültige Wiederholungsanzahl (en)' von regulärem Ausdruck erhalten") und dies liegt an geschweiften Klammern.
Ashish
Tatsächlich ist die oben angegebene Ausnahme im Grunde eine MySQL-Ausnahme.
Ashish
Dies funktioniert gut für jede Zahl unter 256. MySQL hat eine maximale Wiederholungszahl von 256.
Emil Stenström
2
@ Emil-Stenstrom eigentlich ist es 255
Glarrain
-6

Ich würde das Problem auf Ihrem App-Server lösen und Ihre Datenbank nicht besteuern. Sie können dies tun durch:

models_less_than_ten = []
mymodel = MyModel.objects.all()
for m in mymodel:
    if len(m.text) > 10:
          models_less_than_ten.append(m)
Atma
quelle
2
Dies lässt sich für viele Zeilen in MyModel nicht gut skalieren. Wenn Sie 100.000 Zeilen hätten, wäre es für die Datenbank weniger anstrengend, eine Strlen zu erstellen und keine Zeile zu senden, als Tonnen von Daten zum Herausfiltern an den App-Server zu senden. Es ist fast immer besser, die Arbeit an der Datenbank zu erledigen, und wenn es zu langsam oder zu anstrengend ist, kann die Abfrage optimiert werden.
Nevelis