87

我最近一直在做很多研究,将 Pyramid 与 SQLAlchemy 结合使用,而不是将当前应用程序保留在 Django 中。这本身就是一场完整的辩论,但我不是来讨论这个的。

我想知道的是,为什么普遍认为 SQLAlchemy 比 Django ORM 更好?我发现的几乎每一个(如果不是每一个)比较都支持 SQLAlchemy。我认为性能很重要,因为 SQLAlchemy 的结构让它更顺利地转换为 SQL。

但是,我也听说对于更艰巨的任务,Django ORM 几乎无法使用。我想弄清楚这可能是一个多么巨大的问题。我一直在阅读切换到 SQLAlchemy 的原因之一是 Django ORM 不再适合您的需求。

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

更新

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

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

我正在重新审视这个问题,以提供 SQLAlchemy 的附加功能,到目前为止,我还无法在 Django ORM 中进行复制。如果有人可以提供如何做到这一点的例子,我会很乐意接受我的话。

假设您想使用一些 postgresql 函数,例如similarity(),它提供了模糊比较(请参阅:使用 PostgreSQL 快速查找相似字符串- tl;dr 输入两个字符串得到百分比相似度)。

我已经对如何使用 Django ORM 进行了一些搜索,除了使用原始 sql 之外什么也没发现,这从他们的文档中可以看出:https ://docs.djangoproject.com/en/dev/topics/db /sql/

IE

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。

4

2 回答 2

70

这危险地接近于非建设性的,但我会咬人的。

假设我们需要为多个不同的账户维护某些项目的库存。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 不能使用复合主键。是的,您始终可以添加代理键和唯一约束,但这比您实际需要的多一列和多一个索引。对于具有少量列的大表,这会增加显着的大小和性能开销。此外,ORM 通常在使用主键以外的任何东西进行身份映射时都会遇到问题。

现在,假设我们要查询给定帐户库存中的每个项目及其数量,还包括所有不存在的项目,数量设置为 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 中没有办法用自定义条件来表达外连接。是的,您可以进行两个简单的单独查询并在 Python 循环中手动执行连接。在这种特殊情况下,性能可能不会受到太大影响。但这无关紧要,因为每个查询的结果都可以仅使用 basic SELECTs 在应用程序端重现。

使用 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。

于 2013-08-13T10:43:54.763 回答
15

这应该在 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)))
于 2017-01-18T22:31:07.577 回答