2016-05-13 8 views
7

問題への短いintoduction ...Djangoのカスタムデータベース関数呼び出しの回りにSQLの括弧を避ける方法は?

  • PostgreSQLはUNNESTANYのような彼らのために非常にきちんと配列フィールド(int配列、文字列配列)と機能を持っています。
  • これらのフィールドはDjangoでサポートされていますが(私はdjorm_pgarrayを使用しています)、関数はネイティブにサポートされていません。
  • .extra()を使うことができましたが、Django 1.8はdatabase functionsの新しいコンセプトを導入しました。

私が基本的にこれらのすべてでやっていることの最も基本的な例を提供しましょう。 Dealerには、それがサポートするmakeのリストがあります。 Vehicleにはメイクがあり、ディーラーにリンクされています。しかし、VehicleのmakeがDealerのmakeリストと一致しないことは避けられません。

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

そして、それが動作する、と私たちはDjangoのに使用することができ、従来のM2Mのアプローチよりもはるかに高速。しかし、この作業のためにUNNESTは非常に良い解決策ではありません:ANYははるかに高速です。試してみよう。

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

... 

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

これは、次のSQLを生成:ANY周りにカッコが偽であるため、

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

をそして、それは失敗します。それらを削除すると、問題なく速くpsqlコンソールで実行されます。

私の質問です。

  1. これらのブレースを削除する方法はありますか?私はDjangoのドキュメントでそれについて何も見つかりませんでした。
  2. そうでない場合は、このクエリを別の言い方で書き換えることができますか?

P. S.私は別のバックエンドのデータベース機能の豊富なライブラリがデータベース・重いDjangoのアプリのために非常に参考になると思います。

もちろん、これらのほとんどはポータブルではありません。しかし、通常、そのようなプロジェクトをあるデータベースバックエンドから別のデータベースバックエンドに移行することはしばしばありません。この例では、配列フィールドとPostGISを使用してPostgreSQLに固執しており、移動するつもりはありません。

誰もこのようなことを開発していますか?

P. P.この場合、文字列配列の代わりにmakeとintarrayのために別のテーブルを使用する必要がありますが、それは正しく行われますが、問題の性質は変わりません。

UPDATE。

  • TextArrayFielddjorm_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[]です。ところで

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

:結果として

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

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) 

とクエリ:

+0

これが最も興味深いのですが、それが明示的にドキュメントに記載されていないが、私は、のFuncを考えると、それはサブクラスができるだけですaggreateとannotateでは使用できますが、フィルタでは使用できません。 – e4c5

+1

私はFuncでas_sqlメソッドをオーバーライドして括弧を取り除くことができるかどうかを調べることさえ試みました。しかし、大括弧が他の場所に追加されていることがわかりました。 – e4c5

+0

@ e4c5はい、私もソースを調べました。たぶん、Django ORMの内部に深く関わっている人がいて、それに答えることができます。 – Altaisoft

答えて

3

私はあなたが以下を使用して必要なもの(多かれ少なかれ)を得ることができました。代わりにdjorm_pgarrayとTextArrayFieldあなたはネイティブジャンゴを使用することができます。

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

(あなたの依存関係を簡単にするために)

+0

ありがとうございました。私はカスタムデータベースのルックアップについてまったく考えなかった。間違いなくこのアプローチを使用します。もう一度ありがとう、それは非常にクールです。 – Altaisoft

関連する問題