Krótki intoduction problemu ...Jak uniknąć nawiasów w języku SQL wokół wywołania funkcji niestandardowej bazy danych Django?
- PostgreSQL ma bardzo zgrabne pól tablicy (int tablica, tablica string) i funkcje dla nich jak
UNNEST
iANY
. - Te pola są obsługiwane przez Django (do tego używam
djorm_pgarray
), ale funkcje nie są obsługiwane natywnie. - Można użyć
.extra()
, ale Django 1.8 wprowadziło nową koncepcję database functions.
Pozwolę sobie przedstawić najbardziej prymitywny przykład tego, co zasadniczo robię z tymi wszystkimi. A Dealer
zawiera listę marek, które obsługuje. A Vehicle
ma markę i jest powiązana z dealerem. Ale zdarza się, że marka Vehicle
nie pasuje do listy make Dealer
, która jest nieunikniona.
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)
Posiadając bazę danych dealerów i marek, chcę policzyć wszystkie pojazdy, dla których marka pojazdu i lista sprzedanych dealerów są zgodne. Tak to robię unikając .extra()
.
from django.db.models import functions
class SelectUnnest(functions.Func):
function = 'SELECT UNNEST'
...
Vehicle.objects.filter(
make__in=SelectUnnest('dealer__make_list')
).count()
Wynikające 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"))
I to działa, i dużo szybciej niż w tradycyjnym podejściu M2M moglibyśmy wykorzystać w Django. ALE, dla tego zadania, UNNEST
nie jest bardzo dobrym rozwiązaniem: ANY
jest znacznie szybszy. Spróbujmy.
class Any(functions.Func):
function = 'ANY'
...
Vehicle.objects.filter(
make=Any('dealer__make_list')
).count()
Generuje następujące 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"))
I to się nie powiedzie, bo szelki około ANY
są fikcyjne. Jeśli je usuniesz, działa bez problemu w konsoli psql
i szybko.
Moje pytanie.
- Czy istnieje jakiś sposób, aby usunąć te szelki? Nie znalazłem nic na ten temat w dokumentacji Django.
- Jeśli nie, to czy możliwe są inne sposoby przeformułowania tego zapytania?
P. S. myślę, że obszerna biblioteka funkcji bazodanowych dla różnych backendów byłoby bardzo pomocne dla bazy ciężki Django apps.
Oczywiście większość z nich nie będzie przenośna. Zazwyczaj jednak nie migruje się takiego projektu z jednego serwera bazy danych do drugiego. W naszym przykładzie, używając pól tablicy i PostGIS utknęliśmy w PostgreSQL i nie zamierzamy się przenosić.
Czy ktoś coś takiego opracowuje?
P. P. S. Można powiedzieć, że w tym przypadku powinniśmy używać osobnej tabeli dla marek i intarsji zamiast tablic ciągów, co jest poprawne i zostanie wykonane, ale natura problemu nie ulegnie zmianie.
AKTUALIZACJA.
TextArrayField
jest zdefiniowana djorm_pgarray. W połączonym pliku źródłowym można zobaczyć, jak to działa.- Wartość jest listą ciągów tekstowych. W języku Python jest on reprezentowany jako lista. Przykład:
['honda', 'mazda', 'anything else']
.
Oto, o czym jest mowa w bazie danych.
=# select id, make from appname_tablename limit 3;
id | make
---+----------------------
58 | {vw}
76 | {lexus,scion,toyota}
39 | {chevrolet}
Typ bazowego pola PostgreSQL to text[]
.
To jest najbardziej interesujące, myślę, że jeśli nie jest wyraźnie wymienione w dokumentacji, Func i to podklasy może tylko być używane w agreate i adnotacji, ale nie w filtrze. – e4c5
Próbowałem nawet przesłonić metodę as_sql w Func, aby sprawdzić, czy można go użyć do usunięcia nawiasów. Ale okazuje się, że nawiasy są dodawane gdzie indziej. – e4c5
@ e4c5 tak, zajrzałem również do źródła. Może jest ktoś, kto jest głęboko zaangażowany w funkcje wewnętrzne Django i może odpowiedzieć na to pytanie. – Altaisoft