2017-08-20 2 views
1

Ceci est mon SQLAlchemy Code requêteComment limiter les résultats N par `group_by` dans SQLAlchemy/Postgres?

medium_contact_id_subq = (g.session.query(distinct(func.unnest(FUContact.medium_contact_id_lis))).filter(FUContact._id.in_(contact_id_lis))).subquery() 
q = (g.session.query(FUMessage). 
     filter(FUMessage.fu_medium_contact_id.in_(medium_contact_id_subq)) 
     .order_by(desc(FUMessage.timestamp_utc)) 
     ) 

Je voudrais limiter FUMessage regroupés par medium_contact_id avec N résultats.


Pour contourner ce problème, voici mon code actuel laid et unoptimized:

medium_contact_id_lis = (g.session.query(distinct(func.unnest(FUContact.medium_contact_id_lis))).filter(FUContact._id.in_(contact_id_lis))).all() 
    q = None 
    for medium_contact_id_tup in medium_contact_id_lis: 
     medium_contact_id = medium_contact_id_tup[0] 
     if q is None: 
      q = (g.session.query(FUMessage) 
       .filter(FUMessage.fu_medium_contact_id == medium_contact_id) 
       .limit(MESSAGE_LIMIT) 
       ) 
     else: 
      subq = (g.session.query(FUMessage) 
       .filter(FUMessage.fu_medium_contact_id == medium_contact_id) 
       .limit(MESSAGE_LIMIT) 
       ) 
      q = q.union(subq) 
    q = q.order_by(desc(FUMessage.timestamp_utc)) 
+0

Depuis les sous-requêtes qui forment le syndicat ne commande pas avant la limite, vos résultats sont indéterminés. –

Répondre

3

Une façon d'aller chercher haut N lignes par groupe est d'utiliser une fonction de fenêtre, comme rank() ou row_number() dans un sous-ensemble avec le regroupement et l'ordre requis, puis filtrer par celui dans le select englobant. Pour N = 1, vous pouvez utiliser la combinaison DISTINCT ON ... ORDER BY dans Postgresql.

L'adoption que pour SQLAlchemy est simple en utilisant la méthode de l'élément de fonction over() pour produire une expression de la fenêtre:

medium_contact_id_subq = g.session.query(
     func.unnest(FUContact.medium_contact_id_lis).distinct()).\ 
    filter(FUContact._id.in_(contact_id_lis)).\ 
    subquery() 

# Perform required filtering in the subquery. Choose a suitable ordering, 
# or you'll get indeterminate results. 
subq = g.session.query(
     FUMessage, 
     func.row_number().over(
      partition_by=FUMessage.fu_medium_contact_id, 
      order_by=FUMessage.timestamp_utc).label('n')).\ 
    filter(FUMessage.fu_medium_contact_id.in_(medium_contact_id_subq)).\ 
    subquery() 

fumessage_alias = aliased(FUMessage, subq) 

# row_number() counts up from 1, so include rows with a row num 
# less than or equal to limit 
q = g.session.query(fumessage_alias).\ 
    filter(subq.c.n <= MESSAGE_LIMIT) 
+0

brillant, cela a fonctionné! – nubela