【发布时间】:2018-09-01 11:21:46
【问题描述】:
我正在尝试更充分地使用 SQLAlchemy,而不是在遇到困境的第一个迹象时退回到纯 SQL。在这种情况下,我在 Postgres 数据库 (9.5) 中有一个表,它通过将单个项目 atom_id 与组标识符 group_id 相关联,将一组整数存储为一个组。
给定atom_ids 的列表,我希望能够确定group_id 的集合(如果有的话)atom_ids 属于哪个集合。仅使用 group_id 和 atom_id 列即可解决此问题。
现在我试图概括这样一个“组”不仅由atom_ids 的列表组成,还由其他上下文组成。在下面的示例中,列表通过包含 sequence 列进行排序,但从概念上讲,可以使用其他列代替,例如 weight 列,它为每个 atom_id 提供一个 [0,1] 浮点值,表示atom 在组中的“份额”。
以下是演示我的问题的大部分单元测试。
首先,一些设置:
def test_multi_column_grouping(self):
class MultiColumnGroups(base.Base):
__tablename__ = 'multi_groups'
group_id = Column(Integer)
atom_id = Column(Integer)
sequence = Column(Integer) # arbitrary 'other' column. In this case, an integer, but it could be a float (e.g. weighting factor)
base.Base.metadata.create_all(self.engine)
# Insert 6 rows representing 2 different 'groups' of values
vals = [
# Group 1
{'group_id': 1, 'atom_id': 1, 'sequence': 1},
{'group_id': 1, 'atom_id': 2, 'sequence': 2},
{'group_id': 1, 'atom_id': 3, 'sequence': 3},
# Group 2
{'group_id': 2, 'atom_id': 1, 'sequence': 3},
{'group_id': 2, 'atom_id': 2, 'sequence': 2},
{'group_id': 2, 'atom_id': 3, 'sequence': 1},
]
self.session.bulk_save_objects(
[MultiColumnGroups(**x) for x in vals])
self.session.flush()
self.assertEqual(6, len(self.session.query(MultiColumnGroups).all()))
现在,我想查询上表以找出一组特定的输入属于哪个组。我正在使用(命名的)元组列表来表示查询参数。
from collections import namedtuple
Entity = namedtuple('Entity', ['atom_id', 'sequence'])
values_to_match = [
# (atom_id, sequence)
Entity(1, 3),
Entity(2, 2),
Entity(3, 1),
]
# The above list _should_ match with `group_id == 2`
原始 SQL 解决方案。我宁愿不要依赖于此,因为本练习的一部分是学习更多 SQLAlchemy。
r = self.session.execute('''
select group_id
from multi_groups
group by group_id
having array_agg((atom_id, sequence)) = :query_tuples
''', {'query_tuples': values_to_match}).fetchone()
print(r) # > (2,)
self.assertEqual(2, r[0])
这是上面的原始 SQL 解决方案,相当直接地转换为
损坏的 SQLAlchemy 查询。运行此程序会产生 psycopg2 错误:(psycopg2.ProgrammingError) operator does not exist: record[] = integer[]。我相信我需要将array_agg 转换为int[]?只要分组列都是整数(如果需要,这是一个可接受的限制),这将起作用,但理想情况下,这将适用于混合类型的输入元组/表列。
from sqlalchemy import tuple_
from sqlalchemy.dialects.postgresql import array_agg
existing_group = self.session.query(MultiColumnGroups).\
with_entities(MultiColumnGroups.group_id).\
group_by(MultiColumnGroups.group_id).\
having(array_agg(tuple_(MultiColumnGroups.atom_id, MultiColumnGroups.sequence)) == values_to_match).\
one_or_none()
self.assertIsNotNone(existing_group)
print('|{}|'.format(existing_group))
上面的session.query() 关闭了吗?我是否在这里蒙蔽了自己,并且错过了可以通过其他方式解决此问题的超级明显的东西?
【问题讨论】:
标签: python postgresql sqlalchemy