2014-07-23 13 views
8

Ich habe es schwer, meine SQLAlchemy-Abfragen zu optimieren. Mein SQL-Wissen ist sehr einfach und ich kann einfach nicht die Sachen bekommen, die ich von den SQLAlchemy-Dokumenten benötige.SQLAlchemy: mehrere Zählungen in einer Abfrage

die folgende sehr einfache Beziehung Eins-zu-viele Angenommen:

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) 

Wie könnte ich:

  • Abfrage Tupel (Parent, count_of_naughty_children, count_of_all_children) für jeden Elternteil?

Nach anständiger Zeit googeln verbrachte, fand ich, wie diese Werte separat abfragen:

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

ich versuchte, sich auf verschiedene Weise zu kombinieren, aber verwalten nicht bekommen, was ich will.

  • Frage alle Eltern ab, die mehr als 80% unartige Kinder haben? Edit: ungezogen könnte NULL sein.

Ich denke, diese Abfrage basiert auf dem vorherigen, Filtern nach ungezogen/alle Verhältnis.

Jede Hilfe wird geschätzt.

EDIT: Dank Antti Haapala Hilfe fand ich Lösung auf die zweite Frage:

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) 

Es Durchschnitt findet, wenn Kinder naughty Variable, die Behandlung von Falsch und NULL-Werte als 0 und Wahre als 1. Getestete mit MySQL-Backend, sollte aber auch bei anderen funktionieren.

Antwort

7

die count() SQL aggretate Funktion ist ziemlich einfach; Es gibt Ihnen die Gesamtzahl der Nicht-Null-Werte in jeder Gruppe. In diesem Sinne können wir Ihre Anfrage anpassen, um Ihnen das richtige Ergebnis zu liefern.

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

Welche der folgenden SQL-produziert:

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

Danke, das funktioniert perfekt! – meandrobo

4

Wenn Ihre Abfrage nur die Eltern, die> 80% Kinder ungezogen haben, können Sie auf den meisten Datenbanken die naughty zu Integer, dann nehmen Sie Durchschnitt davon; dann having dieser Durchschnitt größer als 0.8.

So erhalten Sie so etwas wie

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

Vielen Dank, dass der Trick. Aber ich habe nicht erwähnt, dass "unartig" in meinem realen Modell null sein könnte - sorry, mein Fehler. Nullwerte werden von avg ignoriert, daher ist diese Lösung nicht genau das, was ich möchte. – meandrobo

+2

func.coalesce() half mir, das zu beheben ^^ – meandrobo

Verwandte Themen