Написание поиска кортежей с помощью Django ORM
Я пытаюсь написать поиск на основе кортежей с использованием синтаксиса Django ORM.
Итоговый sql-запрос должен выглядеть примерно так:
SELECT * FROM mytable WHERE (field_a,field_b) IN ((1,2),(3,4));
Я знаю, что могу достичь этого в django, используя дополнительное ключевое слово:
MyModel.objects.extra(
where=["(field_a, field_b) IN %s"],
params=[((1,2),(3,4))]
)
но ключевое слово "extra" в какой-то момент будет устаревшим в django, поэтому я бы хотел чистое ORM/django решение.
Исследуя Интернет, я нашел https://code.djangoproject.com/ticket/33015 и комментарий Саймона Шаретта, что-то вроде приведенного ниже фрагмента может быть в порядке, но я не могу заставить его работать.
from django.db.models import Func, lookups
class ExpressionTuple(Func):
template = '(%(expressions)s)'
arg_joiner = ","
MyModel.objects.filter(lookups.In(
ExpressionTuple('field_a', 'field_b'),
((1,2),(3,4)),
))
Я использую Django 3.2, но я не ожидаю, что Django 4.x сделает здесь большую разницу. Мой бэкенд базы данных - posgresql, если это имеет значение.
Я могу предложить одно решение, которое будет строить запрос заранее с помощью Q
и затем передавать его через функцию фильтрации:
q = Q()
for (item1, item2) in [(1,2),(3,4)]:
q |= Q(field_one=item1, field_two=item2)
Mymodel.objects.filter(q)
Другим более надежным решением было бы следующее:
q = Q()
fields = ['field_one', 'field_two']
for item in [(1,2),(3,4)]:
q |= Q(**dict(zip(fields, item)))
Здесь я запечатываю поля и элемент из списка элементов, затем передаю его как распакованный словарь в Q
. Реализация аналогична предыдущему примеру, но здесь количество полей может быть большим, но это не увеличит количество строк в коде.
from django.db.models import Func, lookups
class Tuple(Func):
function = '(%s)'
def as_sql(self, compiler, connection):
sql, params = super().as_sql(compiler, connection)
if sql.endswith(',)'):
sql = sql[:-2] + ')'
return sql, params
MyModel.objects.filter((Func('field_a', function='(%s)'), Func('field_b', function='(%s)'))__in=[(1,2),(3,4)])
С этими изменениями результирующий SQL-запрос должен выглядеть примерно так:
SELECT * FROM mytable WHERE (field_a, field_b) IN ((1, 2), (3, 4))
Для справки и под впечатлением от предложения Акшая-Джайна, мне удалось написать кое-что, что работает:
from django.db.models import Func,Value
def ValueTuple(items):
return tuple(Value(i) for i in items)
class Tuple(Func):
function = ''
qs = (
MyModel.objects
.alias(a=Tuple('field_a', 'field_b'))
.filter(a__in=ValueTuple([(1, 2), (3, 4)])
)
В результате получается sql-запрос вида
SELECT * FROM table WHERE (field_a,field_b) IN ((1,2),(3,4));
И может быть расширен на большее количество полей, чем просто два.
Я не делал никаких сравнительных тестов, чтобы сравнить его с фильтрацией Q-объектов.