2016-05-13 14 views
7

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 i ANY.
  • 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.

  1. Czy istnieje jakiś sposób, aby usunąć te szelki? Nie znalazłem nic na ten temat w dokumentacji Django.
  2. 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[].

+0

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

+1

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

+0

@ 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

Odpowiedz

3

udało mi się dostać (bardziej lub mniej), co trzeba przy użyciu następujących:

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) 

i zapytania:

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

w wyniku:

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")) 

btw. zamiast djorm_pgarray i TextArrayField można użyć natywną Django:

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

(aby uprościć zależności)

+0

Dziękuję bardzo. Nie myślałem o niestandardowych wyszukiwań bazy danych w ogóle. Zdecydowanie skorzystamy z tego podejścia. Jeszcze raz dziękuję, to jest BARDZO fajne. – Altaisoft

Powiązane problemy