SQLAlchemy 多对多 LEFT OUTER JOIN 与排序的实体列表
SQLAlchemy many-to-many LEFT OUTER JOIN with sorted list of entities
我有两个表:居民和过敏原以及两者之间的多对多关系。在 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')
returns u'rownum'
而不是引用列
- 获取过敏原排序列表,加入居民,然后按居民分组或区分居民,但这会破坏顺序
Table 定义如下:
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.5
而不是 PostgreSQL 9.3
。
使用 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
我有两个表:居民和过敏原以及两者之间的多对多关系。在 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')
returnsu'rownum'
而不是引用列- 获取过敏原排序列表,加入居民,然后按居民分组或区分居民,但这会破坏顺序
Table 定义如下:
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.5
而不是 PostgreSQL 9.3
。
使用 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