SQLAlchemy 中是否有等效的 LISTAGG WITHIN GROUP?
Is there a LISTAGG WITHIN GROUP equivalent in SQLAlchemy?
这是一个简单的 Oracle table:
+-----------+---------+
| 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 显示有人试图实施自定义 class 来完成这项工作,但这真的是最好的选择吗?
MySQL有一个group_concat
的特征,因此this questioner用func.group_concat(...)
解决了他的问题。遗憾的是,该功能在 Oracle 中不可用。
从 version 1.1 you can use 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)
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-preceded:
>>> [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 的困难部分已经完成了。
within_group
可以接受多个参数。 func.listagg
获取要分组的内容,后跟分隔符,within_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 分组,用逗号和 space,
分隔
并按 A.list_value 排序,然后按 A.other_column
排序
当 A.id 等于 B.id.
希望对您有所帮助。
这是一个简单的 Oracle table:
+-----------+---------+
| 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 显示有人试图实施自定义 class 来完成这项工作,但这真的是最好的选择吗?
MySQL有一个group_concat
的特征,因此this questioner用func.group_concat(...)
解决了他的问题。遗憾的是,该功能在 Oracle 中不可用。
从 version 1.1 you can use 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)
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-preceded:
>>> [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 的困难部分已经完成了。
within_group
可以接受多个参数。 func.listagg
获取要分组的内容,后跟分隔符,within_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 分组,用逗号和 space,
分隔
并按 A.list_value 排序,然后按 A.other_column
排序
当 A.id 等于 B.id.
希望对您有所帮助。