这是一个简单的Oracle表:
+-----------+---------+
| food | person |
+-----------+---------+
| pizza | Adam |
| pizza | Bob |
| pizza | Charles |
| ice cream | Donald |
| hamburger | Emma |
| hamburger | Frank |
+-----------+---------+
以下是我想做的聚合SELECT的结果:
+-----------+------------------+
| food | people |
+-----------+------------------+
| hamburger | Emma,Frank |
| ice cream | Donald |
| pizza | Adam,Bob,Charles |
+-----------+------------------+
使用Oracle 11g +,这很容易使用LISTAGG:
SELECT food, LISTAGG (person, ',') WITHIN GROUP (ORDER BY person) AS people
FROM mytable
GROUP BY food;
但我还没能找到在SQLAlchemy中做到这一点的方法。 old question from Stack Overflow显示有人试图实现自定义类来完成工作,但这真的是最好的选择吗?
MySQL具有group_concat
功能,因此this questioner解决了func.group_concat(...)
的问题。遗憾的是,该功能在Oracle中不可用。
答案 0 :(得分:3)
从version 1.1开始,您可以使用FunctionElement.within_group(*order_by)
:
In [7]: func.listagg(column('person'), ',').within_group(column('person'))
Out[7]: <sqlalchemy.sql.elements.WithinGroup object at 0x7f2870c83080>
In [8]: print(_.compile(dialect=oracle.dialect()))
listagg(person, :listagg_1) WITHIN GROUP (ORDER BY person)
答案 1 :(得分:0)
Ilja的回答对我有用。在这里它使用SQLAlchemy 1.2.2完全充实(我无法在1.1.10中使用它,但升级工作正在处理)
from sqlalchemy import Column, String, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from lib import project_config
from sqlalchemy import func
db_url = 'oracle://someuser:somepassword@some_connect_string'
Base = declarative_base()
engine = create_engine(db_url, echo=True)
Session = sessionmaker(bind=engine)
session = Session()
class MyTable(Base):
__tablename__ = 'my_table'
food = Column(String(30), primary_key=True)
person = Column(String(30), primary_key=True)
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
session.add(MyTable(food='pizza', person='Adam'))
session.add(MyTable(food='pizza', person='Bob'))
session.add(MyTable(food='pizza', person='Charles'))
session.add(MyTable(food='ice cream', person='Donald'))
session.add(MyTable(food='hamburger', person='Emma'))
session.add(MyTable(food='hamburger', person='Frank'))
session.commit()
entries = session.query(
MyTable.food,
func.listagg(MyTable.person, ',').within_group(MyTable.person).label('people')
).group_by(MyTable.food).all()
[print('{}: {}'.format(entry.food, entry.people)) for entry in entries]
打印出来:
hamburger: Emma,Frank
ice cream: Donald
pizza: Adam,Bob,Charles
太棒了!唯一剩下的谜团是为什么分隔符(,)前面有一个NULL:
>>> print(entries)
[('hamburger', 'Emma\x00,Frank'), ('ice cream', 'Donald'), ('pizza', 'Adam\x00,Bob\x00,Charles')]
事实上,如果我将func.listagg()
中的分隔符更改为<->
而不是,
之类的其他内容,那么形成分隔符字符串的每个字符都将以null为先:
>>> [print('{}: {}'.format(entry.food, entry.people)) for entry in entries]
hamburger: Emma<->Frank
ice cream: Donald
pizza: Adam<->Bob<->Charles
>>> print(entries)
[('hamburger', 'Emma\x00<\x00-\x00>Frank'), ('ice cream', 'Donald'), ('pizza', 'Adam\x00<\x00-\x00>Bob\x00<\x00-\x00>Charles')]
不确定那里发生了什么。但如果需要,它很容易从列中删除空值。至少LISTAGG的难点已经完成。
答案 2 :(得分:0)
within_group
可以接受多个参数。 func.listagg
进行分组,后跟分隔符,inside_group列出进行分组的顺序列表。
query = ( select([func.listagg(A.list_value, ', ')
.within_group(A.list_value, A.other_column)])
.where(A.id == B.id)
.label('list_values_of_a') )
这将转换为:
将A的list_value分组,以逗号和空格分隔,
并按A.list_value和A.other_column排序
当A.id等于B.id时。
希望有帮助。