1. ホーム
  2. python

[解決済み] SQLAlchemyにできて、Django ORMにできないことの例

2023-05-08 04:52:14

質問

私は最近、SQLAlchemyでPyramidを使うか、Djangoで現在のアプリケーションを維持するかについて、多くの研究をしています。 それ自体、全体の議論になりますが、私はそれを議論するためにここにいるわけではありません。

私が知りたいのは、なぜ SQLAlchemy が Django ORM よりも良いと普遍的に考えられているのか、ということです。 私が見つけた2つの比較では、すべてではないにしても、ほとんどすべてが SQLAlchemy を支持しています。 SQLAlchemyの構造によって、SQLへの変換がよりスムーズに行えるので、パフォーマンスが大きな要因だと思います。

しかし、より困難なタスクでは、Django ORM はほとんど使えないという話も聞きます。 これがどれほど大きな問題なのか、調査してみたいのです。 SQLAlchemyに乗り換える理由の1つは、Django ORMがニーズに合わなくなったときだと読んだことがあります。

要するに、SQLAlchemy はできるが、Django ORM は生の SQL を追加しないとできないようなクエリ (実際の SQL 構文である必要はない) を誰か教えてください。

更新 :

私が最初に質問してから、この質問がかなり注目されていることに気づいたので、私の追加意見を述べたいと思います。

最終的に私たちはSQLAlchemyを使うことになりましたが、その決断に満足していると言わざるを得ません。

私は、今のところ Django ORM で再現できていない SQLAlchemy の追加機能を提供するために、この質問を再確認しています。 もし誰かがこれを行う方法の例を提供してくれるなら、私は喜んで私の言葉を食べます。

例えば、ファジーな比較を行う similarity() のような postgresql の関数を使いたいとしましょう ( 参照。 PostgreSQLで類似した文字列を素早く見つける - tl;dr input two strings get back a percent similarity) のような関数を使いたいとします。

Django ORM を使ってこれを行う方法についていくつか検索してみましたが、彼らのドキュメントから明らかなように、生の SQL を使うこと以外には何も見つかりませんでした。 https://docs.djangoproject.com/en/dev/topics/db/sql/ .

すなわち

Model.objects.raw('SELECT * FROM app_model ORDER BY \
similarity(name, %s) DESC;', [input_name])

しかし、SQLalchemy はここで説明されているように、func() を持っています。 http://docs.sqlalchemy.org/en/latest/core/sqlelement.html#sqlalchemy.sql.expression.func

from sqlalchemy import desc, func
session.query(Model).order_by(func.similarity(Model.name, input_name))

これにより、定義されたsql/postgresql/etcの関数に対してsqlを生成し、生のsqlを必要としないようにすることができます。

どのように解決するのですか?

これは非建設的であることに危険なほど近いのですが、私は噛んでみます。

多くの異なる、例えば口座のための特定のアイテムのインベントリを維持する必要があるとします。DDLは次のとおりです。

CREATE TABLE account (
    id serial PRIMARY KEY,
    ...
);

CREATE TABLE item (
    id serial PRIMARY KEY,
    name text NOT NULL,
    ...
);

CREATE TABLE inventory (
    account_id integer NOT NULL REFERENCES account(id),
    item_id integer NOT NULL REFERENCES item(id),
    amount integer NOT NULL DEFAULT 0 CHECK (amount >= 0),
    PRIMARY KEY (account_id, item_id)
);

まず第一に、 Django ORM は複合主鍵を扱うことができません。確かに、サロゲートキーとユニーク制約を追加することはいつでもできますが、それは実際に必要な列とインデックスを1つ増やすことになります。カラム数が少ない大きなテーブルの場合、これはサイズとパフォーマンスのオーバーヘッドを著しく増加させることになります。また、ORM は一般に、主キー以外のものを使用する ID マッピングに問題を抱えています。

さて、与えられたアカウントのインベントリ内の各アイテムの数量を問い合わせたいが、数量が0に設定されたそこに存在しないすべてのアイテムも含めたいとします。そして、これを数量で降順にソートします。対応するSQLです。

SELECT item.id, item.name, ..., coalesce(inventory.amount, 0) AS amount
    FROM item LEFT OUTER JOIN inventory
        ON item.id = inventory.item_id AND inventory.team_id = ?
    ORDER BY amount DESC;

Django ORM には、カスタム条件による外部結合を表現する方法はありません。しかし、2つの単純なクエリを作成し、 Python ループの中で手作業で結合を実行することは可能です。そして、おそらくパフォーマンスはそれほど悪くならないでしょう この特別な場合 . しかし、それは重要ではありません。 それぞれ クエリの結果をアプリケーション側で再現するには、基本的な SELECT s.

SQLAlchemyで。

class Account(Base):
    __tablename__ = 'account'
    id = Column(Integer, primary_key=True)
    ...

class Item(Base):
    __tablename__ = 'item'
    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)
    ...

class Inventory(Base):
    __tablename__ = 'inventory'
    account_id = Column(Integer, ForeignKey('account.id'), primary_key=True,
            nullable=False)
    account = relationship(Account)
    item_id = Column(Integer, ForeignKey('item.id'), primary_key=True,
            nullable=False)
    item = relationship(Item)
    amount = Column(Integer, CheckConstraint('amount >= 0'), nullable=False,
            default=0)

account = session.query(Account).get(some_id)
result = (session
    .query(Item, func.coalesce(Inventory.amount, 0).label('amount'))
    .outerjoin(Inventory,
        and_(Item.id==Inventory.item_id, Inventory.account==account))
    .order_by(desc('amount'))
    .all())

余談ですが、SQLAlchemy は辞書ベースのコレクションを非常に簡単に作ることができ ます。以下のコードを Account との関係を作ることができます。 Inventory の関係は、アイテムからその数量へのマッピングとして表示されます。

items = relationship('Inventory',
    collection_class=attribute_mapped_collection('item_id'))
inventory = association_proxy('items', 'amount',
    creator=lambda k, v: Inventory(item_id=k, amount=v))

これにより、次のようなコードを書くことができます。

account.inventory[item_id] += added_value

のエントリを透過的に挿入または更新するものです。 inventory テーブルのエントリを挿入または更新します。

複雑な結合、サブクエリ、ウィンドウ集約 - Django ORM は、生の SQL にフォールバックしないと、そのようなものを扱うことができません。