Как избежать скобок в SQL вокруг вызова функции пользовательской базы данных Django?

Короткий ввод в проблему …

  • PostgreSQL имеет очень аккуратные поля массива (int array, string array) и функции для них, такие как UNNEST и ANY .
  • Эти поля поддерживаются Django (для этого я использую djorm_pgarray ), но функции не поддерживаются.
  • Можно использовать .extra() , но Django 1.8 представил новую концепцию функций базы данных .

Позвольте мне представить самый примитивный пример того, что я в основном делаю со всеми этими. У Dealer есть список марок, которые он поддерживает. Vehicle имеет марку и связан с дилером. Но бывает так, что Vehicle не соответствует списку изготовителя, что неизбежно.

 MAKE_CHOICES = [('honda', 'Honda'), ...] class Dealer(models.Model): make_list = TextArrayField(choices=MAKE_CHOICES) class Vehicle(models.Model): dealer = models.ForeignKey(Dealer, null=True, blank=True) make = models.CharField(max_length=255, choices=MAKE_CHOICES, blank=True) 

Имея базу данных дилеров и изготовителей, я хочу рассчитать все транспортные средства, для которых подходит автомобиль и список его дилера. Вот как я делаю это, избегая .extra() .

 from django.db.models import functions class SelectUnnest(functions.Func): function = 'SELECT UNNEST' ... Vehicle.objects.filter( make__in=SelectUnnest('dealer__make_list') ).count() 

Результат SQL:

 SELECT COUNT(*) AS "__count" FROM "myapp_vehicle" INNER JOIN "myapp_dealer" ON ( "myapp_vehicle"."dealer_id" = "myapp_dealer"."id" ) WHERE "myapp_vehicle"."make" IN (SELECT UNNEST("myapp_dealer"."make_list")) 

И это работает, и намного быстрее, чем традиционный подход M2M, который мы могли бы использовать в Django. НО, для этой задачи UNNEST – не очень хорошее решение: ANY намного быстрее. Давай попробуем.

 class Any(functions.Func): function = 'ANY' ... Vehicle.objects.filter( make=Any('dealer__make_list') ).count() 

Он генерирует следующий SQL:

  SELECT COUNT(*) AS "__count" FROM "myapp_vehicle" INNER JOIN "myapp_dealer" ON ( "myapp_vehicle"."dealer_id" = "myapp_dealer"."id" ) WHERE "myapp_vehicle"."make" = (ANY("myapp_dealer"."make_list")) 

И это терпит неудачу, потому что фигурные скобки вокруг ANY – фиктивные. Если вы их удалите, он запускается в консоли psql без проблем и быстро.

Так что мой вопрос.

  1. Есть ли способ удалить эти фигурные скобки? Я не мог найти ничего об этом в документации Django.
  2. Если нет, – возможно, есть другие способы перефразировать этот запрос?

PS Я думаю, что обширная библиотека функций базы данных для разных бэкэндов будет очень полезна для приложений Django с тяжелой базой данных.

Конечно, большинство из них не будут переносимыми. Но вы обычно не часто переносите такой проект из одного бэкенда базы данных в другой. В нашем примере, используя поля массива и PostGIS, мы привязаны к PostgreSQL и не намерены перемещаться.

Кто-нибудь развивает такую ​​вещь?

PPS Можно сказать, что в этом случае мы должны использовать отдельную таблицу для make и intarray вместо строкового массива, это правильно и будет сделано, но характер проблемы не изменится.

ОБНОВИТЬ.

  • TextArrayField определяется в djorm_pgarray . В связанном исходном файле вы можете увидеть, как это работает.
  • Значение – это список текстовых строк. В Python он представлен в виде списка. Пример: ['honda', 'mazda', 'anything else'] .

Вот что говорится об этом в базе данных.

 =# select id, make from appname_tablename limit 3; id | make ---+---------------------- 58 | {vw} 76 | {lexus,scion,toyota} 39 | {chevrolet} 

И базовым типом поля PostgreSQL является text[] .

One Solution collect form web for “Как избежать скобок в SQL вокруг вызова функции пользовательской базы данных Django?”

Мне удалось получить (более или менее) то, что вам нужно, используя следующие:

 from django.db.models.lookups import BuiltinLookup from django.db.models.fields import Field class Any(BuiltinLookup): lookup_name = 'any' def get_rhs_op(self, connection, rhs): return " = ANY(%s)" % (rhs,) Field.register_lookup(Any) 

и запрос:

 Vehicle.objects.filter(make__any=F('dealer__make_list')).count() 

в результате:

 SELECT COUNT(*) AS "__count" FROM "zz_vehicle" INNER JOIN "zz_dealer" ON ("zz_vehicle"."dealer_id" = "zz_dealer"."id") WHERE "zz_vehicle"."make" = ANY(("zz_dealer"."make_list")) 

Кстати. вместо этого djorm_pgarray и TextArrayField вы можете использовать собственное django:

 make_list = ArrayField(models.CharField(max_length=200), blank=True) 

(чтобы упростить ваши зависимости)

  • Sqlalchemy: обновление вторичных отношений
  • postgres и python
  • Один объект два внешних ключа к одной таблице
  • psycopg2.InternalError: как я могу получить более полезную информацию?
  • Создание базы данных postgresql с использованием psycopg2
  • Как я могу запросить ближайшую запись в заданных координатах (широта и долгота типа строки)?
  • Flask-SQLAlchemy Lower Case Index - пропуск функциональности, не поддерживаемый отражением SQLAlchemy
  • Могут ли Аламбик автогенерировать изменения колонны?
  • Как использовать SQLAlchemy для беспрепятственного доступа к нескольким базам данных?
  • Форматирование строки Psycopg2 с именами переменных для создания типа
  • Ошибка сокета Postgresql при запуске Django syncdb (mac OS 10.7.5)
  • Python - лучший язык программирования в мире.