How to dynamically compose an OR query filter in Django?

后端 未结 14 1341
清歌不尽
清歌不尽 2021-01-22 05:24

From an example you can see a multiple OR query filter:

Article.objects.filter(Q(pk=1) | Q(pk=2) | Q(pk=3))

For example, this results in:

相关标签:
14条回答
  • 2021-01-22 06:16

    Solution which use reduce and or_ operators to filter by multiply fields.

    from functools import reduce
    from operator import or_
    from django.db.models import Q
    
    filters = {'field1': [1, 2], 'field2': ['value', 'other_value']}
    
    qs = Article.objects.filter(
       reduce(or_, (Q(**{f'{k}__in': v}) for k, v in filters.items()))
    )
    

    p.s. f is a new format strings literal. It was introduced in python 3.6

    0 讨论(0)
  • 2021-01-22 06:19

    Another option I wasn't aware of until recently - QuerySet also overrides the &, |, ~, etc, operators. The other answers that OR Q objects are a better solution to this question, but for the sake of interest/argument, you can do:

    id_list = [1, 2, 3]
    q = Article.objects.filter(pk=id_list[0])
    for i in id_list[1:]:
        q |= Article.objects.filter(pk=i)
    

    str(q.query) will return one query with all the filters in the WHERE clause.

    0 讨论(0)
  • 2021-01-22 06:21

    For loop:

    values = [1, 2, 3]
    q = Q(pk__in=[]) # generic "always false" value
    for val in values:
        q |= Q(pk=val)
    Article.objects.filter(q)
    

    Reduce:

    from functools import reduce
    from operator import or_
    
    values = [1, 2, 3]
    q_objects = [Q(pk=val) for val in values]
    q = reduce(or_, q_objects, Q(pk__in=[]))
    Article.objects.filter(q)
    

    Both of these are equivalent to Article.objects.filter(pk__in=values)

    It's important to consider what you want when values is empty. Many answers with Q() as a starting value will return everything. Q(pk__in=[]) is a better starting value. It's an always-failing Q object that's handled nicely by the optimizer (even for complex equations).

    Article.objects.filter(Q(pk__in=[]))  # doesn't hit DB
    Article.objects.filter(Q(pk=None))    # hits DB and returns nothing
    Article.objects.none()                # doesn't hit DB
    Article.objects.filter(Q())           # returns everything
    

    If you want to return everything when values is empty, you should AND with ~Q(pk__in=[]) to ensure that behaviour:

    values = []
    q = Q()
    for val in values:
        q |= Q(pk=val)
    Article.objects.filter(q)                     # everything
    Article.objects.filter(q | author="Tolkien")  # only Tolkien
    
    q &= ~Q(pk__in=[])
    Article.objects.filter(q)                     # everything
    Article.objects.filter(q | author="Tolkien")  # everything
    

    It's important to remember that Q() is nothing, not an always-succeeding Q object. Any operation involving it will just drop it completely.

    0 讨论(0)
  • 2021-01-22 06:24
    from functools import reduce
    from operator import or_
    from django.db.models import Q
    
    values = [1, 2, 3]
    query = reduce(or_, (Q(pk=x) for x in values))
    
    0 讨论(0)
  • 2021-01-22 06:25

    You could chain your queries as follows:

    values = [1,2,3]
    
    # Turn list of values into list of Q objects
    queries = [Q(pk=value) for value in values]
    
    # Take one Q object from the list
    query = queries.pop()
    
    # Or the Q object with the ones remaining in the list
    for item in queries:
        query |= item
    
    # Query the model
    Article.objects.filter(query)
    
    0 讨论(0)
  • 2021-01-22 06:25

    To build more complex queries there is also the option to use built in Q() object's constants Q.OR and Q.AND together with the add() method like so:

    list = [1, 2, 3]
    # it gets a bit more complicated if we want to dynamically build
    # OR queries with dynamic/unknown db field keys, let's say with a list
    # of db fields that can change like the following
    # list_with_strings = ['dbfield1', 'dbfield2', 'dbfield3']
    
    # init our q objects variable to use .add() on it
    q_objects = Q(id__in=[])
    
    # loop trough the list and create an OR condition for each item
    for item in list:
        q_objects.add(Q(pk=item), Q.OR)
        # for our list_with_strings we can do the following
        # q_objects.add(Q(**{item: 1}), Q.OR)
    
    queryset = Article.objects.filter(q_objects)
    
    # sometimes the following is helpful for debugging (returns the SQL statement)
    # print queryset.query
    
    0 讨论(0)
提交回复
热议问题