【问题标题】:Is there a LISTAGG WITHIN GROUP equivalent in SQLAlchemy?SQLAlchemy 中是否有 LISTAGG WITHIN GROUP 等价物?
【发布时间】:2018-07-25 17:41:31
【问题描述】:

这是一个简单的 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 questionerfunc.group_concat(...) 解决了他的问题。遗憾的是,该功能在 Oracle 中不可用。

【问题讨论】:

  • 不,group_concat(因此 SQLAlchemy 的 func.group_concat() 仅在 MySQL 中可用,在 Oracle 中不可用。
  • 您使用的是什么版本的 Oracle?
  • 我实际上必须安装多个 Oracle,从 11g (rel 2) 到 12c。
  • @xfix 我确实不得不质疑这是一个欺骗目标。尽管 OP 在他们的帖子中引用了它,但它并没有解决他们的问题。根据OP,Oracle中没有GROUP_CONCAT。

标签: python oracle sqlalchemy oracle12c listagg


【解决方案1】:

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)

【讨论】:

  • 太棒了——这太完美了!还有一个关于分隔符字符串中奇怪的空字符的问题(请参阅我自己发布的“答案”),但尽管如此,这让我有 95% 的把握。谢谢!
【解决方案2】:

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() 中的分隔符更改为 &lt;-&gt; 而不是 , 之类的其他内容,则分隔符字符串中的每个字符都以空值开头:

>>> [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 的难点已经完成。

【讨论】:

  • 会不会是一些行为不端的 unicode 转换?看起来像 UTF-16,双重编码或类似的。不幸的是,我无权访问 Oracle 数据库,因此无法测试。
  • 列本身是VARCHAR,当然分隔符只是一个python字符串。这是非常神秘的。但至少有一个简单的解决方法。再次感谢您的帮助。
【解决方案3】:

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。

希望对您有所帮助。

【讨论】:

    猜你喜欢
    • 2010-12-20
    • 1970-01-01
    • 1970-01-01
    • 2011-07-29
    • 2013-06-21
    • 2014-01-09
    • 2012-02-18
    相关资源
    最近更新 更多