Пример того, что может сделать SQLAlchemy, а Django ORM не может


в последнее время я много исследовал использование пирамиды с SQLAlchemy против сохранения текущего приложения в Django. Это само по себе целая дискуссия, но я не собираюсь обсуждать это.

Я хочу знать, почему SQLAlchemy повсеместно считается лучше, чем Django ORM? Почти каждое, если не каждое, сравнение, которое я нашел между двумя преимуществами SQLAlchemy. Я предполагаю, что производительность является большой, так как структура SQLAlchemy позволяет ей переводить на SQL много более гладко.

но я также слышал, что с более сложными задачами Django ORM почти невозможно использовать. Я хочу охватить, насколько огромной проблемой это может быть. Я читал одну из причин перехода на SQLAlchemy, когда Django ORM больше не соответствует вашим потребностям.

короче говоря, может ли кто-то предоставить запрос (не обязательно фактический синтаксис SQL), который может сделать SQLAlchemy, но Django ORM не может обойтись без добавления дополнительного raw SQL?

обновление:

Я заметил, что этот вопрос получает довольно много внимания с тех пор, как я впервые спросил, поэтому я хотел бы бросить свои дополнительные два цента.

в конце концов мы закончили с помощью SQLAlchemy и я должен сказать, что я доволен решением.

я пересматриваю этот вопрос, чтобы предоставить дополнительную функцию SQLAlchemy, которую до сих пор мне не удалось воспроизвести в Django ORM. Если кто-то может привести пример того, как это сделать это я с удовольствием съем мои слова.

предположим, вы хотите использовать некоторую функцию postgresql, такую как similarity (), которая обеспечивает нечеткое сравнение (см.: поиск похожих строк с помощью PostgreSQL быстро - tl;dr input two strings get back a percent similarity).

Я сделал некоторые поиски о том, как это сделать с помощью Django ORM и не нашли ничего, кроме использования raw 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 для любой определенной функции sql / postgresql / etc и не требует необработанного sql.

2 59

2 ответа:

это опасно близко к тому, чтобы быть неконструктивным, но я укушу.

Предположим, нам нужно вести запасы определенных предметов для ряда различных, скажем, счетов. 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 не может работать с составными первичными ключами. Да, вы всегда можете добавить суррогатный ключ и уникальное ограничение, но это еще один столбец и один индекс, чем вам действительно нужно. Для большой таблицы с небольшим количеством столбцов это будет добавьте заметные накладные расходы на размер и производительность. Кроме того, у ORMs обычно возникают проблемы с сопоставлением идентификаторов с использованием чего-либо, кроме первичного ключа.

теперь предположим, что мы хотим запросить каждый элемент в инвентаре данного счета, сопровождаемый его количеством, но также включить все элементы, не присутствующие там, с количеством, установленным в 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;

нет способа выразить внешнее соединение с пользовательским условием в Джанго ОРМ. Да, вы можете сделать два простых отдельных запроса и выполнить соединение вручную в цикле Python. И производительность, вероятно, не пострадает много в данном случае. Но это к делу не относится, потому что результаты запрос может быть воспроизведен на стороне приложения, используя только basic 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.

Это должно работать в 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)))