2016-07-05 17 views
2

In Worten, ich versuche, dieses Ziel zu erreichen:SQLAlchemy alle Zeilen zu zählen, möchte ich bestimmte Zeilen

„Get 5 Kommentare wo comment.post_id == self.context.id und die von der höchsten Zahl sortieren von Comment_Vote.vote_type == 'wie' "

Derzeit werden die Modelle sind:

vote_enum = ENUM('like', 'dislike', name='vote_enum', create_type=False) 

class User(Base): 
    __tablename__='users' 
    id = Column(Integer, primary_key=True, autoincrement=True) 
    username = Column(String(65), nullable=False) 
    comments = relationship('Comment', backref='user') 
    comment_vote = relationship('Comment_Vote', backref='user') 
    posts=relationship('Post', backref='user') 

class Post(Base): 
    __tablename__ = 'post' 
    id = Column(Integer, primary_key=True, autoincrement=True) 
    body= Column(String(1500)) 
    comments= relationship('Comment',backref='post', order_by='desc(Comment.date_created)', lazy='dynamic') 
    owner_id= Column(Integer, ForeignKey('users.id')) 

class Comment(Base): 
    __tablename__='comment' 
    id = Column(Integer, primary_key=True, autoincrement=True) 
    body= Column(String(500)) 
    parent_id = Column(Integer, ForeignKey('comment.id')) 
    post_id= Column(Integer, ForeignKey('post.id')) 
    user_id= Column(Integer, ForeignKey('users.id')) 
    children = relationship("Comment", 
      backref=backref('parent', remote_side=[id]), 
      lazy='dynamic' 
      ) 
    del_flag= Column(Boolean, default=False) 
    date_created= Column(DateTime(), default=datetime.datetime.utcnow()) 
    last_edited= Column(DateTime(), default=datetime.datetime.utcnow()) 
    comment_vote= relationship("Comment_Vote", backref="comment", lazy='dynamic') 

class Comment_Vote(Base): 
    __tablename__='comment_vote' 
    id = Column(Integer, primary_key=True, autoincrement=True) 
    user_id= Column(Integer, ForeignKey('users.id')) 
    comment_id= Column(Integer, ForeignKey('comment.id')) 
    vote_type = Column('vote_enum', vote_enum) 

    @classmethod 
    def total_likes(cls, comment_id, session): 
     return session.query(cls).filter(cls.id == comment_id).first().comment_vote.filter(Comment_Vote.vote_type=='like').count() 

Meine Funktion Abfrage ist:

f = session.query(Comment_Vote.comment_id, funcfilter(func.count(1), Comment_Vote.vote_type == 'like').label('total_likes')).group_by(Comment_Vote.comment_id).subquery() 

comments = session.query(Comment, f.c.total_likes).join(f, Comment.id==f.c.comment_id).filter(Comment.post_id == self.context.id).order_by('total_likes DESC').limit(5) 

Das hat den unangenehmen Nebeneffekt, ALLE "likes" von comment_vote zu zählen, sogar für Kommentare, die für diesen Beitrag nicht relevant sind.

Ich wäre wirklich dankbar für ein paar Tipps, wie man das neu anordnet, so dass es nicht alles zuerst zählen musste. Was ich will, ist vielleicht nicht möglich, und ich arbeite meistens innerhalb des ORM.

DB hinter dem SQLAlchemy ist Postgresql.

+0

Was ist 'funcfilter'? – univerio

+0

http://docs.sqlalchemy.org/en/latest/core/sqlelement.html#sqlalchemy.sql.expression.funcfilter Eine andere Art zu sagen, filter(), so ziemlich. Es wurde gemacht, weil der Filter nicht richtig gezählt wurde. – Vyndion

+1

Heh, ich dachte es wäre etwas was du definiert hast. Sie können versuchen, einen Join direkt zu machen, anstatt einen Join zu einer Unterabfrage zu machen: 'SELECT comment.id, count (*) AS total_likes FROM Kommentar JOIN comment_vote ON comment.id = comment_id WHERE Kommentar.post_id =: id AND vote_type = 'like 'GROUP BY Kommentar.ID'. – univerio

Antwort

3

Dies könnte ein schöner Ort sein, um eine lateral subquery zu verwenden. Es ist die "foreach" von SQL, das heißt, dass eine laterale Unterabfrage Spalten von vorhergehenden FROM-Elementen referenzieren kann. Postgresql unterstützt lateral von Versionen 9.3 und höher, SQLAlchemy from versions 1.1 and up:

from sqlalchemy import true 

f = session.query(func.count(1).label('total_likes')).\ 
    filter(Comment_Vote.comment_id == Comment.id, # References Comment 
      Comment_Vote.vote_type == 'like').\ 
    subquery().\ 
    lateral() 

comments = session.query(Comment, f.c.total_likes).\ 
    join(f, true()).\ 
    filter(Comment.post_id == self.context.id).\ 
    order_by(f.c.total_likes.desc()).\ 
    limit(5) 

I bewegte Filterung basierend auf vote_type zu WHERE-Klausel der Unterabfrage, da es in diesem Fall nicht notwendig ist, zuerst alle Zeilen zu holen und dann in dem Aggregate filtert Funktion (die auch keine Indizes verwenden kann).

Natürlich in diesem Fall, dass Sie auch ein scalar subquery in der SELECT-Ausgabe für denselben Effekt verwenden:

f = session.query(func.count(1)).\ 
    filter(Comment_Vote.comment_id == Comment.id, # References Comment 
      Comment_Vote.vote_type == 'like').\ 
    label('total_likes') 

comments = session.query(Comment, f).\ 
    filter(Comment.post_id == self.context.id).\ 
    order_by(f.desc()).\ 
    limit(5) 
+0

Ich hatte keine Ahnung, dass es diese überhaupt gab; und die Querabfrage scheint zu funktionieren. Immer noch testen, aber kleine Kommentarabfragen führen zu winzigen Zeiten, große sind viel länger als andere Methoden, aber ich denke, das ist nur ein Nebeneffekt. Darf ich Ihre Meinung zu dem, was ich indexieren sollte? – Vyndion

+1

'Comment.post_id' mindestens und' Comment_Vote.comment_id'. Letzteres ist zwingend erforderlich, da die Leistung der Unterabfragen davon abhängt. Der Abstimmungstyp ist möglicherweise kein guter Kandidat, aber das hängt davon ab, wie er verteilt wird. –

+0

Heiliger Mist, die Geschwindigkeit ist unwirklich (250ms bis 3ms). Vielen Dank! – Vyndion