Wie kombiniere ich mehrere Abfragesätze in Django?

Lesezeit: 9 Minuten

Benutzeravatar von espenhogbakk
espenhogbakk

Ich versuche, die Suche für eine von mir erstellte Django-Site zu erstellen, und bei dieser Suche suche ich in drei verschiedenen Modellen. Und um eine Paginierung in der Suchergebnisliste zu erhalten, möchte ich eine generische object_list-Ansicht verwenden, um die Ergebnisse anzuzeigen. Aber dazu muss ich drei Abfragesätze zu einem zusammenführen.

Wie kann ich das machen? Ich habe das versucht:

result_list = []
page_list = Page.objects.filter(
    Q(title__icontains=cleaned_search_term) |
    Q(body__icontains=cleaned_search_term))
article_list = Article.objects.filter(
    Q(title__icontains=cleaned_search_term) |
    Q(body__icontains=cleaned_search_term) |
    Q(tags__icontains=cleaned_search_term))
post_list = Post.objects.filter(
    Q(title__icontains=cleaned_search_term) |
    Q(body__icontains=cleaned_search_term) |
    Q(tags__icontains=cleaned_search_term))

for x in page_list:
    result_list.append(x)
for x in article_list:
    result_list.append(x)
for x in post_list:
    result_list.append(x)

return object_list(
    request,
    queryset=result_list,
    template_object_name="result",
    paginate_by=10,
    extra_context={
        'search_term': search_term},
    template_name="search/result_list.html")

Aber das funktioniert nicht. Ich erhalte eine Fehlermeldung, wenn ich versuche, diese Liste in der generischen Ansicht zu verwenden. In der Liste fehlt das Klonattribut.

Wie kann ich die drei Listen zusammenführen, page_list, article_list Und post_list?

  • Sieht so aus, als hätte t_rybik eine umfassende Lösung bei erstellt djangosnippets.org/snippets/1933

    – akaihola

    21. März 2010 um 18:17 Uhr

  • Für die Suche ist es besser, dedizierte Lösungen wie zu verwenden Heuhaufen – es ist sehr flexibel.

    – Aufpasser

    9. April 2010 um 8:52 Uhr

  • Django-Benutzer 1.11 und abv, siehe diese Antwort – stackoverflow.com/a/42186970/6003362

    – Sahil Agarwal

    22. Februar 2018 um 9:24 Uhr


  • Notiz: Die Frage beschränkt sich auf den sehr seltenen Fall, dass Sie nach dem Zusammenführen von 3 verschiedenen Modellen keine Modelle erneut aus der Auflistung extrahieren müssen, um Daten zu Typen zu unterscheiden. In den meisten Fällen – wenn eine Unterscheidung erwartet wird – wird es eine falsche Schnittstelle geben. Für die gleichen Modelle: siehe Antworten zu union.

    – Sławomir Lenart

    15. Juli 2019 um 17:18 Uhr


Das Verketten der Abfragesätze zu einer Liste ist der einfachste Ansatz. Wenn die Datenbank sowieso für alle Abfragesätze getroffen wird (zB weil das Ergebnis sortiert werden muss), entstehen dadurch keine weiteren Kosten.

from itertools import chain
result_list = list(chain(page_list, article_list, post_list))

Verwenden itertools.chain ist schneller als jede Liste zu durchlaufen und Elemente einzeln anzuhängen, da itertools ist in C implementiert. Es verbraucht auch weniger Speicher als das Konvertieren jedes Abfragesatzes in eine Liste vor dem Verketten.

Jetzt ist es möglich, die resultierende Liste zB nach Datum zu sortieren (wie im Kommentar von hasen j zu einer anderen Antwort gefordert). Der sorted() Funktion akzeptiert bequem einen Generator und gibt eine Liste zurück:

result_list = sorted(
    chain(page_list, article_list, post_list),
    key=lambda instance: instance.date_created)

Wenn Sie Python 2.4 oder höher verwenden, können Sie verwenden attrgetter statt Lambda. Ich erinnere mich, dass ich darüber gelesen habe, dass es schneller sei, aber ich habe keinen merklichen Geschwindigkeitsunterschied für eine Liste mit einer Million Elementen festgestellt.

from operator import attrgetter
result_list = sorted(
    chain(page_list, article_list, post_list),
    key=attrgetter('date_created'))

  • Wenn Sie Abfragesätze aus derselben Tabelle zusammenführen, um eine ODER-Abfrage durchzuführen, und doppelte Zeilen haben, können Sie sie mit der Funktion groupby entfernen: from itertools import groupby unique_results = [rows.next() for (key, rows) in groupby(result_list, key=lambda obj: obj.id)]

    – Josh Russo

    18. September 2011 um 22:21 Uhr


  • Ok, also etwas zur Groupby-Funktion in diesem Zusammenhang. Mit der Q-Funktion sollten Sie in der Lage sein, jede gewünschte ODER-Abfrage durchzuführen: https://docs.djangoproject.com/en/1.3/topics/db/queries/#complex-lookups-with-q-objects

    – Josh Russo

    18. September 2011 um 22:41 Uhr


  • @apelliciari Chain verbraucht deutlich weniger Speicher als list.extend, da es nicht beide Listen vollständig in den Speicher laden muss.

    – Dan Gayle

    15. April 2015 um 17:17 Uhr

  • @AWrightIV Hier ist die neue Version dieses Links: docs.djangoproject.com/en/1.8/topics/db/queries/…

    – Josh Russo

    12. September 2015 um 16:27 Uhr

  • Versuchen Sie diesen Ansatz, aber haben 'list' object has no attribute 'complex_filter'

    – grillazz

    5. Oktober 2016 um 10:30 Uhr

Versuche dies:

matches = pages | articles | posts

Es behält alle Funktionen der Abfragesätze bei, was nett ist, wenn Sie möchten order_by o.ä.

Bitte beachten Sie: Dies funktioniert nicht bei Abfragesätzen aus zwei verschiedenen Modellen.

  • Funktioniert jedoch nicht bei segmentierten Abfragesätzen. Oder übersehe ich etwas?

    – etw

    20. April 2014 um 22:30 Uhr

  • Früher habe ich die Abfragesätze mit “|” verknüpft. aber nicht immer funktioniert gut. Es ist besser, “Q” zu verwenden: docs.djangoproject.com/en/dev/topics/db/queries/…

    – Ignacio Pérez

    12. September 2014 um 8:59 Uhr

  • Hier | ist der Set-Union-Operator, nicht bitweises OR.

    – e100

    26. März 2015 um 18:53 Uhr


  • @ e100 nein, es ist nicht der set union-Operator. django überlädt den bitweisen OR-Operator: github.com/django/django/blob/master/django/db/models/…

    – shangxiao

    24. August 2016 um 15:31 Uhr

  • Beachten Sie, dass diese Lösung funktioniert nicht Ordnung bewahren, also ein Satz {x,y,x} und ein Satz {a,b,c} kann enden {a,b,c,x,y,z} unabhängig davon, ob Sie verwenden s1 | s2 oder s2 | s1 und das macht | in vielen Fällen etwas nutzlos.

    – Mike „Pomax“ Kamermans

    25. Oktober 2017 um 1:07 Uhr


Benutzeravatar von Udi
Udi

Related, zum Mischen von Abfragesätzen aus demselben Modell oder für ähnliche Felder aus einigen Modellen, beginnend mit Django 1.11 A QuerySet.union() Methode ist auch vorhanden:

union()

union(*other_qs, all=False)

Neu in Django 1.11. Verwendet den UNION-Operator von SQL, um die Ergebnisse von zwei oder mehr QuerySets zu kombinieren. Zum Beispiel:

>>> qs1.union(qs2, qs3)

Der UNION-Operator wählt standardmäßig nur eindeutige Werte aus. Um doppelte Werte zuzulassen, verwenden Sie das Argument all=True.

union(), Intersection() und difference() geben Modellinstanzen des Typs des ersten QuerySet zurück, selbst wenn die Argumente QuerySets anderer Modelle sind. Das Übergeben verschiedener Modelle funktioniert, solange die SELECT-Liste in allen QuerySets gleich ist (zumindest die Typen, die Namen spielen keine Rolle, solange die Typen in der gleichen Reihenfolge sind).

Außerdem sind im resultierenden QuerySet nur LIMIT, OFFSET und ORDER BY (dh Slicing und order_by()) zulässig. Weiter, Datenbanken beschränken, welche Operationen in den kombinierten Abfragen zulässig sind. Beispielsweise lassen die meisten Datenbanken LIMIT oder OFFSET in den kombinierten Abfragen nicht zu.

  • Dies ist eine bessere Lösung für mein Problem, das eindeutige Werte haben muss.

    – Brennende Kristalle

    7. September 2017 um 13:03 Uhr

  • Woher importiert ihr Union? Muss es aus einem der X Abfragesätze stammen?

    – Jack

    19. November 2019 um 16:04 Uhr

  • Ja, es ist eine Methode von queryset.

    – Udi

    19. November 2019 um 16:13 Uhr

  • Ich denke, es entfernt Suchfilter

    – Pierre Cordier

    28. November 2019 um 23:03 Uhr

  • Denken Sie an Sie wird nicht können Zu filter() dieses Abfrageset nach der Verwendung nicht mehr union(). filter() wird einfach lautlos scheitern. Zumindest in Django 2.2

    – Qzurück

    3. Dezember 2019 um 17:07 Uhr


Benutzeravatar von akaihola
akaihola

Du kannst den … benutzen QuerySetChain Klasse unten. Wenn es mit dem Paginator von Django verwendet wird, sollte es nur die Datenbank mit treffen COUNT(*) Abfragen für alle Abfragesätze und SELECT() Abfragen nur für die Abfragesätze, deren Datensätze auf der aktuellen Seite angezeigt werden.

Beachten Sie, dass Sie angeben müssen template_name= bei Verwendung von a QuerySetChain mit generischen Ansichten, auch wenn die verketteten Abfragesätze alle dasselbe Modell verwenden.

from itertools import islice, chain

class QuerySetChain(object):
    """
    Chains multiple subquerysets (possibly of different models) and behaves as
    one queryset.  Supports minimal methods needed for use with
    django.core.paginator.
    """

    def __init__(self, *subquerysets):
        self.querysets = subquerysets

    def count(self):
        """
        Performs a .count() for all subquerysets and returns the number of
        records as an integer.
        """
        return sum(qs.count() for qs in self.querysets)

    def _clone(self):
        "Returns a clone of this queryset chain"
        return self.__class__(*self.querysets)

    def _all(self):
        "Iterates records in all subquerysets"
        return chain(*self.querysets)

    def __getitem__(self, ndx):
        """
        Retrieves an item or slice from the chained set of results from all
        subquerysets.
        """
        if type(ndx) is slice:
            return list(islice(self._all(), ndx.start, ndx.stop, ndx.step or 1))
        else:
            return islice(self._all(), ndx, ndx+1).next()

In Ihrem Beispiel wäre die Verwendung:

pages = Page.objects.filter(Q(title__icontains=cleaned_search_term) |
                            Q(body__icontains=cleaned_search_term))
articles = Article.objects.filter(Q(title__icontains=cleaned_search_term) |
                                  Q(body__icontains=cleaned_search_term) |
                                  Q(tags__icontains=cleaned_search_term))
posts = Post.objects.filter(Q(title__icontains=cleaned_search_term) |
                            Q(body__icontains=cleaned_search_term) | 
                            Q(tags__icontains=cleaned_search_term))
matches = QuerySetChain(pages, articles, posts)

Dann benutze matches mit dem paginator wie du es benutzt hast result_list in deinem beispiel.

Der itertools -Modul wurde in Python 2.3 eingeführt, daher sollte es in allen Python-Versionen verfügbar sein, auf denen Django läuft.

Falls Sie viele Abfragesätze verketten möchten, versuchen Sie Folgendes:

from itertools import chain
result = list(chain(*docs))

wobei: docs eine Liste von Abfragesätzen ist

Benutzeravatar von Carl Meyer
Karl Meier

Der große Nachteil Ihres derzeitigen Ansatzes ist seine Ineffizienz bei großen Suchergebnisseiten, da Sie jedes Mal die gesamte Ergebnismenge aus der Datenbank abrufen müssen, obwohl Sie nur eine Ergebnisseite anzeigen möchten.

Um nur die tatsächlich benötigten Objekte aus der Datenbank herunterzuziehen, müssen Sie die Paginierung für ein QuerySet verwenden, nicht für eine Liste. Wenn Sie dies tun, schneidet Django das QuerySet tatsächlich, bevor die Abfrage ausgeführt wird, sodass die SQL-Abfrage OFFSET und LIMIT verwendet, um nur die Datensätze abzurufen, die Sie tatsächlich anzeigen. Aber Sie können dies nicht tun, es sei denn, Sie können Ihre Suche irgendwie in eine einzige Abfrage packen.

Angesichts der Tatsache, dass alle drei Ihrer Modelle Titel- und Textfelder haben, warum nicht verwenden Modell Erbe? Lassen Sie einfach alle drei Modelle von einem gemeinsamen Vorfahren erben, der Titel und Text hat, und führen Sie die Suche als einzelne Abfrage für das Vorfahrenmodell durch.

Benutzeravatar von Devang Padhiyar
Devang Padhiyar

Dies kann entweder auf zwei Wegen erreicht werden.

1. Weg, dies zu tun

Verwenden Sie den Union-Operator für den Abfragesatz | um die Vereinigung zweier Abfragesätze zu nehmen. Wenn beide Abfragesätze zum selben Modell / Einzelmodell gehören, ist es möglich, Abfragesätze mit dem Union-Operator zu kombinieren.

Für ein Beispiel

pagelist1 = Page.objects.filter(
    Q(title__icontains=cleaned_search_term) | 
    Q(body__icontains=cleaned_search_term))
pagelist2 = Page.objects.filter(
    Q(title__icontains=cleaned_search_term) | 
    Q(body__icontains=cleaned_search_term))
combined_list = pagelist1 | pagelist2 # this would take union of two querysets

2. Weg, dies zu tun

Eine andere Möglichkeit, eine Kombinationsoperation zwischen zwei Abfragesätzen zu erreichen, ist die Verwendung itertools Kettenfunktion.

from itertools import chain
combined_results = list(chain(pagelist1, pagelist2))

  • Anstatt itertools.chain (wobei jede Abfrage separat ausgeführt wird), functools.reduce(operator.or_, [pagelist1, pagelist2]) kann verwendet werden, um Ihren ersten Ansatz programmgesteuert anzuwenden. Dies führt zu einer einzigen Abfrage.

    – Cornflex

    19. August 2021 um 17:18 Uhr

1443460cookie-checkWie kombiniere ich mehrere Abfragesätze in Django?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy