2013-08-13 17 views
59

Ultimamente ho fatto molte ricerche sull'uso di Pyramid con SQLAlchemy rispetto a mantenere un'applicazione corrente in Django. Questo è di per sé un intero dibattito, ma non sono qui per discuterne.Esempio di ciò che SQLAlchemy può fare e Django ORM non può

Quello che voglio sapere è, perché SQLAlchemy è universalmente considerato migliore di Django ORM? Quasi tutti, se non tutti, i confronti che ho trovato tra i due favori di SQLAlchemy. Presumo che le prestazioni siano grandi, in quanto la struttura di SQLAlchemy consente di tradurre in SQL molto più agevolmente.

Ma, ho anche sentito che con compiti più difficili, Django ORM è quasi impossibile da usare. Voglio scoprire quanto enorme possa essere un problema. Sto leggendo uno dei motivi per passare a SQLAlchemy è quando Django ORM non si adatta più alle tue esigenze.

Quindi, in breve, qualcuno potrebbe fornire una query (non deve essere la sintassi SQL effettiva) che SQLAlchemy può fare, ma Django ORM non può fare a meno di aggiungere in SQL raw aggiuntivo?

Aggiornamento:

Ho notato questa domanda ottenere un po 'di attenzione da quando ho chiesto, quindi mi piacerebbe buttare nei miei extra di due centesimi.

Alla fine abbiamo finito per usare SQLAlchemy e devo dire che sono contento della decisione.

Sto rivisitando questa domanda per fornire una funzionalità aggiuntiva di SQLAlchemy che, finora, non sono stato in grado di replicare in Django ORM. Se qualcuno può fornire un esempio di come farlo, mangerò volentieri le mie parole.

Diciamo che si desidera utilizzare una funzione postgresql, come similarity(), che fornisce un confronto fuzzy (vedi: Finding similar strings with PostgreSQL quickly - tl; dr input due stringhe restituiscono una somiglianza percentuale).

Ho effettuato alcune ricerche su come eseguire questa operazione utilizzando l'ORM Django e non ho trovato nient'altro che l'utilizzo di sql raw come sembra essere evidente dalla loro documentazione: https://docs.djangoproject.com/en/dev/topics/db/sql/.

cioè

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

SQLAlchemy, però, ha func(), come descritto qui: 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)) 

Ciò consente di generare SQL per qualsiasi sql/PostgreSQL definito/funzione, ecc e non richiede raw sql.

+0

https://docs.djangoproject.com/en/1.11/ref/models/expressions/#func-expressions –

risposta

48

Questo è pericolosamente vicino a non essere costruttivo, ma morderò.

Supponiamo di dover mantenere gli inventari di alcuni articoli per un numero di conti diversi, diciamo. Segue 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) 
); 

Prima di tutto, Django ORM non può funzionare con chiavi primarie composite. Sì, puoi sempre aggiungere una chiave surrogata e un vincolo univoco, ma questa è una colonna in più e un indice in più del necessario. Per una grande tabella con un numero ridotto di colonne, si aggiungono dimensioni e prestazioni notevoli. Inoltre, gli ORM hanno generalmente problemi con il mapping dell'identità usando qualcosa di diverso dalla chiave primaria.

Ora, supponiamo di voler interrogare ogni elemento nell'inventario di un determinato account accompagnato dalla sua quantità, ma anche di includere tutti gli elementi non presenti lì con la quantità impostata su 0. E poi ordinarlo in ordine decrescente per quantità. SQL corrispondente:

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; 

Non esiste alcun modo per esprimere join esterno con condizioni personalizzate in Django ORM. Sì, puoi creare due semplici query separate ed eseguire l'unione manualmente in loop Python. E le prestazioni probabilmente non soffriranno molto in questo caso particolare. Ma questo è oltre il punto perché i risultati di ogni query possono essere riprodotti sul lato dell'applicazione utilizzando solo i valori di base SELECT s.

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

Come nota a margine, SQLAlchemy rende collezioni basate dizionario molto facile. Con l'aggiunta del seguente codice al modello Account, la relazione con lo Inventory viene visualizzata come ciò che è: una mappatura dagli articoli alla loro quantità.

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

Ciò consente di scrivere codice come:

account.inventory[item_id] += added_value 

che inserisce in modo trasparente o le voci aggiorna nella tabella inventory.

Join complessi, sottoquery, aggregati di finestre - Django ORM non riesce a gestire nulla di ciò senza tornare a SQL grezzo.

+1

Grazie! Questo e 'esattamente quello che stavo cercando. Ero preoccupato che non fosse costruttivo, ma il mio obiettivo era ottenere un esempio solido, in cui la maggior parte delle risorse online diceva "SQLAlchemy è meglio" e non fornisce ulteriori dettagli. – limasxgoesto0

2

Questo dovrebbe funzionare 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))) 
Problemi correlati