2014-07-23 19 views
8

Sto avendo difficoltà a ottimizzare le mie query SQLAlchemy. La mia conoscenza di SQL è molto semplice, e non riesco a ottenere le cose che mi servono dai documenti SQLAlchemy.SQLAlchemy: più conteggi in una query

Supponiamo la seguente relazione molto di base uno-a-molti:

class Parent(Base): 
    __tablename__ = "parents" 
    id = Column(Integer, primary_key = True) 
    children = relationship("Child", backref = "parent") 

class Child(Base): 
    __tablename__ = "children" 
    id = Column(Integer, primary_key = True) 
    parent_id = Column(Integer, ForeignKey("parents.id")) 
    naughty = Column(Boolean) 

come potevo:

  • tuple query di (Parent, count_of_naughty_children, count_of_all_children) per ciascun genitore?

Dopo tempo decente trascorso googling, ho trovato come interrogare quei valori separatamente:

# The following returns tuples of (Parent, count_of_all_children): 
session.query(Parent, func.count(Child.id)).outerjoin(Child, Parent.children).\ 
    group_by(Parent.id) 
# The following returns tuples of (Parent, count_of_naughty_children): 
al = aliased(Children, session.query(Children).filter_by(naughty = True).\ 
    subquery()) 
session.query(Parent, func.count(al.id)).outerjoin(al, Parent.children).\ 
    group_by(Parent.id) 

ho cercato di combinarli in modi diversi, ma non sono riuscito a ottenere ciò che voglio.

  • Interroga tutti i genitori che hanno oltre l'80% di bambini birichini? Modifica: cattivo potrebbe essere NULL.

Immagino che questa query sarà basata sul precedente, filtrando per cattivo/tutto rapporto.

Qualsiasi aiuto è apprezzato.

EDIT: Grazie all'aiuto di Antti Haapala, ho trovato la soluzione alla seconda domanda:

avg = func.avg(func.coalesce(Child.naughty, 0)) # coalesce() treats NULLs as 0 
# avg = func.avg(Child.naughty) - if you want to ignore NULLs 
session.query(Parent).join(Child, Parent.children).group_by(Parent).\ 
    having(avg > 0.8) 

Essa trova media se di naughty variabile, trattando False e NULL come 0, e Vero bambini 1. Testato con back-end MySQL, ma dovrebbe funzionare anche su altri.

risposta

7

la funzione aggretate count() SQL è abbastanza semplice; ti dà il numero totale di valori non nulli in ciascun gruppo. Con questo in mente, possiamo adattare la tua richiesta per darti il ​​risultato giusto.

print (Query([ 
    Parent, 
    func.count(Child.id), 
    func.count(case(
     [((Child.naughty == True), Child.id)], else_=literal_column("NULL"))).label("naughty")]) 

    .join(Parent.children).group_by(Parent) 
    ) 

che produce il seguente SQL:

SELECT 
parents.id AS parents_id, 
count(children.id) AS count_1, 
count(CASE WHEN (children.naughty = 1) 
     THEN children.id 
     ELSE NULL END) AS naughty 
FROM parents 
JOIN children ON parents.id = children.parent_id 
GROUP BY parents.id 
+0

Grazie, funziona perfettamente! – meandrobo

4

Se la tua domanda è solo per ottenere i genitori che hanno> 80% di bambini cattivi, è possibile nella maggior parte dei database eseguire il cast di naughty in numero intero, quindi prendere la media di esso; quindi having questa media superiore a 0.8.

Così si ottiene qualcosa di simile

from sqlalchemy.sql.expression import cast 

naughtyp = func.avg(cast(Child.naughty, Integer)) 
session.query(Parent, func.count(Child.id), naughtyp).join(Child)\ 
    .group_by(Parent.id).having(naughtyp > 0.8).all() 
+0

Grazie mille, che ha fatto il trucco. Ma non ho detto che nel mio vero modello "cattivo" potrebbe essere nullo - scusa, colpa mia. I valori nulli vengono ignorati da avg, quindi questa soluzione non è esattamente quello che voglio. – meandrobo

+2

func.coalesce() mi ha aiutato a risolvere questo ^^ – meandrobo

Problemi correlati