How to combine two or more querysets in a Django view?

后端 未结 13 2407
猫巷女王i
猫巷女王i 2020-11-21 22:40

I am trying to build the search for a Django site I am building, and in that search, I am searching in 3 different models. And to get pagination on the search result list, I

相关标签:
13条回答
  • 2020-11-21 23:08

    This recursive function concatenates array of querysets into one queryset.

    def merge_query(ar):
        if len(ar) ==0:
            return [ar]
        while len(ar)>1:
            tmp=ar[0] | ar[1]
            ar[0]=tmp
            ar.pop(1)
            return ar
    
    0 讨论(0)
  • 2020-11-21 23:12

    This will do the work without using any other libs

    result_list = list(page_list) + list(article_list) + list(post_list)
    
    0 讨论(0)
  • 2020-11-21 23:13

    here's an idea... just pull down one full page of results from each of the three and then throw out the 20 least useful ones... this eliminates the large querysets and that way you only sacrifice a little performance instead of a lot

    0 讨论(0)
  • 2020-11-21 23:16

    You can use Union

    qs = qs1.union(qs2, qs3)
    

    But if you want to apply order_by on the foreign models of the combined queryset.. then you need to Select them before hand this way.. otherwise it won't work

    Example

    qs = qs1.union(qs2.select_related("foreignModel"), qs3.select_related("foreignModel"))
    qs.order_by("foreignModel__prop1")
    

    where prop1 is a property in the foreign model

    0 讨论(0)
  • 2020-11-21 23:20

    Concatenating the querysets into a list is the simplest approach. If the database will be hit for all querysets anyway (e.g. because the result needs to be sorted), this won't add further cost.

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

    Using itertools.chain is faster than looping each list and appending elements one by one, since itertools is implemented in C. It also consumes less memory than converting each queryset into a list before concatenating.

    Now it's possible to sort the resulting list e.g. by date (as requested in hasen j's comment to another answer). The sorted() function conveniently accepts a generator and returns a list:

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

    If you're using Python 2.4 or later, you can use attrgetter instead of a lambda. I remember reading about it being faster, but I didn't see a noticeable speed difference for a million item list.

    from operator import attrgetter
    result_list = sorted(
        chain(page_list, article_list, post_list),
        key=attrgetter('date_created'))
    
    0 讨论(0)
  • 2020-11-21 23:22

    Related, for mixing querysets from the same model, or for similar fields from a few models, Starting with Django 1.11 a QuerySet.union() method is also available:

    union()

    union(*other_qs, all=False)
    

    New in Django 1.11. Uses SQL’s UNION operator to combine the results of two or more QuerySets. For example:

    >>> qs1.union(qs2, qs3)
    

    The UNION operator selects only distinct values by default. To allow duplicate values, use the all=True argument.

    union(), intersection(), and difference() return model instances of the type of the first QuerySet even if the arguments are QuerySets of other models. Passing different models works as long as the SELECT list is the same in all QuerySets (at least the types, the names don’t matter as long as the types in the same order).

    In addition, only LIMIT, OFFSET, and ORDER BY (i.e. slicing and order_by()) are allowed on the resulting QuerySet. Further, databases place restrictions on what operations are allowed in the combined queries. For example, most databases don’t allow LIMIT or OFFSET in the combined queries.

    0 讨论(0)
提交回复
热议问题