SQLAlchemy 可以做什么,而 Django ORM 不能做什么的示例

我最近做了很多研究,比较使用金字塔和 SQLAlchemy 以及在 Django 中保留当前的应用程序。这本身就是一场辩论,但我不是来讨论这个的。

我想知道的是,为什么 SQLAlchemy 被普遍认为比 Django ORM 更好?几乎每一个,如果不是每一个,比较我发现这两个青睐 SQLAlchemy。我认为性能是一个很大的问题,因为 SQLAlchemy 的结构使它能够更顺利地转换为 SQL。

但是,我也听说,对于更难的任务,Django ORM 几乎不可能使用。我想知道这个问题有多严重。我一直在阅读切换到 SQLAlchemy 的原因之一是当 Django ORM 不再适合您的需要时。

因此,简而言之,是否有人可以提供一个 SQLAlchemy 可以执行的查询(不一定是实际的 SQL 语法) ,但是 Django ORM 不可能不添加额外的原始 SQL?

更新 :

自从我第一次提出这个问题以来,我就注意到这个问题得到了相当多的关注,所以我想多说两句。

最后,我们最终使用了 SQLAlchemy,我必须说我对这个决定很满意。

我重新讨论这个问题是为了提供 SQLalchemy 的另一个特性,到目前为止,我还没能在 Django ORM 复制这个特性。如果有人能提供一个如何做到这一点的例子,我很乐意收回我的话。

假设您希望使用一些 postgreql 函数,比如贼() ,它提供模糊比较(参见: 使用 PostgreSQL 快速查找类似的字符串-tl; dr 输入两个字符串返回百分比相似度)。

我搜索了一下如何使用 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])

然而,SQLchemy 具有 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/postgreql/etc 函数生成 sql,而不需要原始 sql。

23542 次浏览

This is dangerously close to being non-constructive, but I'll bite.

Suppose we need to maintain inventories of certain items for a number of different, let's say, accounts. DDL follows:

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

First of all, Django ORM can't work with composite primary keys. Yes, you can always add a surrogate key and unique constraint, but that's one more column and one more index than you actually need. For a big table with a small number of columns this would add noticeable size and performance overhead. Also, ORMs generally have trouble with identity mapping using anything other than primary key.

Now, let's say we want to query each item in the inventory of given account accompanied by its quantity, but also include all items not present there with quantity set to 0. And then sort this in descending order by quantity. Corresponding 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;

There is no way to express outer join with custom condition in Django ORM. Yes, you can make two simple separate queries and perform join by hand in Python loop. And performance probably won't suffer much in this particular case. But that's beside the point because results of every query could be reproduced on the application side using only basic SELECTs.

With 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())

As a side note, SQLAlchemy makes dictionary based collections very easy. With addition of the following code to the Account model you make relationship with Inventory appear as what it is: a mapping from items to their quantity.

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

This enables you to write code such as:

account.inventory[item_id] += added_value

that transparently inserts or updates entries in the inventory table.

Complex joins, subqueries, window aggregates — Django ORM fails to deal with anything of that without falling back to raw SQL.

This should work in Django 1.11:

inventory_amount = Subquery(account.inventory_set.filter(item=OuterRef('pk')).values('amount'))
Item.objects.annotate(inventory_amount=Coalesce(inventory_amount, Value(0)))