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