2015-06-22 77 views
1

我有兩張表:居民和過敏原以及兩者之間的多對多關係。使用SQLAlchemy的超過PostgreSQL的,我想獲得通過他們的字母順序排列的過敏原名單下令居民的名單:SQLAlchemy多對多左外連接與實體排序列表

resident_id | resident_name |  allergs  
-------------+---------------+-------------------- 
      1 | John   | {milk,pollen,soy} 
      3 | Hopkins  | {pollen,stupidity} 
      2 | Mary   | {stupidity} 
      4 | Lee   | {NULL} 

我知道這可以在PostgreSQL中使用ARRAY_AGG來完成:

SELECT 
    resident.id AS resident_id, 
    resident.name AS resident_name, 
    array_agg(allergen.NAME ORDER BY allergen.NAME) AS allergs 
FROM resident 
LEFT OUTER JOIN (resident_allergens AS resident_allergens_1 
JOIN allergen 
    ON allergen.id = resident_allergens_1.allergen_id) 
    ON resident.id = resident_allergens_1.resident_id 
GROUP BY resident.id 
ORDER BY allergs 

但據我所知,SQLAlchemy不支持array_agg函數中的ORDER BY子句。

到目前爲止,我已經試過:

  • db.session.query(Resident).outerjoin(Resident.allergies).order_by(Allergen.name).from_self().group_by(Resident),但這並不過敏原正常
  • db.session.query(Resident, func.row_number().over(order_by=Allergen.name).label('rownum')).outerjoin(Resident.allergies).order_by(Allergen.name).from_self(Resident, func.min('rownum').label('maxrownum')).group_by(Resident).order_by('maxrownum').from_self(Resident)排序,但func.min('rownum')回報u'rownum',而不是引用的列
  • 得到的排序列表過敏原,加入居民,然後是GROUP BY居民或DISTINCT居民,但是這會遺失訂單

表定義吼叫:

resident_allergens = db.Table(
    'resident_allergens', 
    db.Column('resident_id', db.Integer, db.ForeignKey('resident.id'), nullable=False, index=True), 
    db.Column('allergen_id', db.Integer, db.ForeignKey('allergen.id'), nullable=False, index=True), 
    UniqueConstraint('resident_id', 'allergen_id')) 


class Allergen(db.Model): 
    id = Column(db.Integer, primary_key=True) 
    name = Column(db.String) 

    def __init__(self, name): 
     self.name = name 


class Resident(db.Model): 
    id = Column(db.Integer, primary_key=True) 
    name = Column(db.String) 

    allergies = db.relationship('Allergen', 
           collection_class=set, 
           secondary=resident_allergens, 
           backref=db.backref('residents', lazy='lazy')) 

    def __init__(self, name): 
     self.name = name 

如果它是相關的,我使用過SQLAlchemy 0.8.5PostgreSQL 9.3

回答

1

使用的SQLAlchemy的compilation extension,我可以添加自己的ARRAY_AGG的版本,支持ORDER BY:

from sqlalchemy.ext.compiler import compiles 
from sqlalchemy.sql.expression import ColumnClause, _literal_as_binds 


class array_agg(ColumnClause): 
    """Custom version of PostgreSQL's array_agg with support for ORDER BY. 

    Usage: ... .order_by(array_agg(Allergen.name, order_by=Allergen.name)) 
    """ 

    def __init__(self, expr, order_by=None): 
     self.expr = _literal_as_binds(expr) 
     self.order_by = _literal_as_binds(order_by) 

    @property 
    def _from_objects(self): 
     return self.expr._from_objects 


@compiles(array_agg) 
def compile_array_agg(element, compiler, **kwargs): 
    head = 'array_agg(%s' % (
     compiler.process(element.expr), 
    ) 
    if element.order_by is not None: 
     tail = ' ORDER BY %s)' % compiler.process(element.order_by) 
    else: 
     tail = ')' 
    return head + tail