2016-05-13 21 views
7

sorununa kısa intoduction ...SQL'de Django özel veritabanı işlev çağrısı etrafında parantez nasıl engellenir?

  • PostgreSQL UNNEST ve ANY gibi onlara çok düzgün dizi alanları (int dizi dize dizi) ve işlevlere sahiptir.
  • Bu alanlar, Django tarafından desteklenmektedir (bunun için djorm_pgarray kullanıyorum), ancak işlevler doğal olarak desteklenmemektedir.
  • Bir kullanıcı .extra() kullanabilir, ancak Django 1.8, database functions'un yeni bir konseptini tanıttı.

Tüm bunlarla temel olarak ne yaptığımın en ilkel örneğini vereyim. Bir Dealer, desteklediği markaların bir listesini içerir. Bir Vehicle bir satıcıya bağlıdır ve bir satıcıya bağlıdır. Ancak Vehicle 'un yapısının Dealer' un yapım listesiyle eşleşmesi kaçınılmazdır.

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) 

bayi veritabanına sahip ve yapar, ben aracın marka ve onun Satıcının yapmak liste maçı yapmak kendisi için tüm araçları saymak istiyor. Ben .extra() kaçınarak böyle yaparım.

from django.db.models import functions 

class SelectUnnest(functions.Func): 
    function = 'SELECT UNNEST' 

... 

Vehicle.objects.filter(
    make__in=SelectUnnest('dealer__make_list') 
).count() 

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

Ve çalıştığını ve biz Django kullanabilirsiniz geleneksel M2M yaklaşımı çok daha hızlı. AMA, bu görev için UNNEST çok iyi bir çözüm değildir: ANY çok daha hızlıdır. Hadi deneyelim.

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

Ve ANY etrafında parantez sahte oldukları için, başarısız:

class Any(functions.Func): 
    function = 'ANY' 

... 

Vehicle.objects.filter(
    make=Any('dealer__make_list') 
).count() 

Aşağıdaki SQL oluşturur. Onları kaldırırsanız, psql konsolunda sorunsuz ve hızlı bir şekilde çalışır.

Benim sorum.

  1. Bu diş tellerini çıkarmanın bir yolu var mı? Django belgelerinde bunun hakkında hiçbir şey bulamadım.
  2. Değilse, - belki bu sorguyu yeniden ifade etmenin başka yolları var mı?

P. S. Farklı backendleri için veritabanı geniş bir işlev kitaplık veritabanı ağır Django uygulamaları için çok yararlı olacağını düşünüyoruz. Tabii ki bunların çoğu taşınabilir olmayacaktır. Fakat genellikle böyle bir projeyi bir veritabanından arka uçtan diğerine geçiremezsiniz. Örneğimizde, dizi alanlarını ve PostGIS'i kullanarak PostgreSQL'e yapıştık ve hareket etmeyi düşünmüyoruz.

Böyle bir şey geliştiren var mı?

P. biri bu durumda biz yapar için ayrı bir tabloyu kullanarak olmalı ve yerine dize dizinin intarray, diyebilirsiniz, doğru ve yapılacaktır, ancak sorunun doğası değişmez.

GÜNCELLEME.

  • TextArrayFielddjorm_pgarray tanımlanır. Bağlı kaynak dosyada, nasıl çalıştığını görebilirsiniz.
  • değer metin dizeleri listesi aşağıdadır. Python'da bir liste olarak temsil edilir. Örnek: ['honda', 'mazda', 'anything else']. İşte

veritabanında bu konuda söylenen budur.

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

Ve altta yatan PostgreSQL alan türü text[] olduğunu.

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) 

ve sorguda:

+0

(bağımlılıkları basitleştirmek için), Func ve bu kadar alt sınıfları sadece can aggreate ve not açıklamalarında kullanılmalı, ancak filtrede kullanılmamalıdır. – e4c5

+1

Hatta, parantezleri çıkarmak için kullanılıp kullanılamayacağını görmek için Func'de as_sql yöntemini geçersiz kılmaya çalıştım. Ancak köşeli parantezlerin – e4c5

+0

@ e4c5 başka bir yerde eklendiğini görüyoruz, ben de kaynağa baktım. Belki de Django ORM internals ile derinden ilgilenen biri var ve buna cevap verebilir. – Altaisoft

cevap

3

sana kullanarak aşağıdaki gerekenleri (az ya da çok) başardı ettik

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

sonucu:

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. bunun yerine yerli django kullanabilirsiniz TextArrayField djorm_pgarray ve:

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

açıkça belgelerinde belirtilen olmasa da bu, ben en ilginç düşünüyorum edilir

+0

Çok teşekkür ederim. Özel veritabanı aramalarını hiç düşünmedim. Bu yaklaşımı kesinlikle kullanacağız. Tekrar teşekkürler, bu çok güzel. – Altaisoft

İlgili konular