在 Oracle 中动态地将行转为列

Posted

技术标签:

【中文标题】在 Oracle 中动态地将行转为列【英文标题】:Pivoting rows into columns dynamically in Oracle 【发布时间】:2011-12-05 12:26:14 【问题描述】:

我有以下名为 _kv 的 Oracle 10g 表:

select * from _kv

ID       K       V
----     -----   -----
  1      name    Bob
  1      age     30
  1      gender  male
  2      name    Susan
  2      status  married

我想使用普通 SQL(不是 PL/SQL)将我的键转换为列,以便生成的表看起来像这样:

ID       NAME    AGE    GENDER  STATUS
----     -----   -----  ------  --------
  1      Bob      30     male 
  2      Susan                   married
查询的列数应与表中存在的唯一 Ks 一样多(没有那么多) 在运行查询之前无法知道可能存在哪些列。 我试图避免运行初始查询来以编程方式构建最终查询。 空白单元格可能是 null 或空字符串,这并不重要。 我使用的是 Oracle 10g,但 11g 解决方案也可以。

当你知道你的旋转列可能被称为什么时,有很多例子,但我就是找不到适用于 Oracle 的通用旋转解决方案。

谢谢!

【问题讨论】:

亲爱的上帝,那是...元数据库吗?? 你应该阅读这个:***.com/questions/7340422/… 在阅读有关“实体属性值”表的文章时,我总是觉得很幽默:asktom.oracle.com/pls/apex/… 不幸的是,这不是我的设计(_kv 表就是一个例子)...我需要从该表中生成一些实时报告,任何过滤或排序的尝试都会让我发疯。 哇,如果您想要一个键值对数据库,那么使用 Oracle 的代价太大了。查看 Berkeley db(仍然免费,但具有讽刺意味的是由 Oracle 控制;)。更好的是,重新设计那个吸盘……不惜一切代价。祝你好运 【参考方案1】:

首先,再次使用pivot xml 动态旋转需要被解析。我们还有另一种方法,将列名存储在一个变量中,并将它们传递到动态 sql 中,如下所示。

假设我们有一个如下表。

如果我们需要将YR 列中的值显示为列名,并将这些列中的值显示为QTY,那么我们可以使用以下代码。

declare
  sqlqry clob;
  cols clob;
begin
  select listagg('''' || YR || ''' as "' || YR || '"', ',') within group (order by YR)
  into   cols
  from   (select distinct YR from EMPLOYEE);


  sqlqry :=
  '      
  select * from
  (
      select *
      from EMPLOYEE
  )
  pivot
  (
    MIN(QTY) for YR in (' || cols  || ')
  )';

  execute immediate sqlqry;
end;
/

结果

【讨论】:

listagg('''' || YR || ''' as "' || YR || '"', ',') 有疑问。为什么不listagg(YR, ',')'''' || 在那里做什么?【参考方案2】:

为了处理可能存在多个值的情况(在您的示例中为 v),我使用 PIVOTLISTAGG

SELECT * FROM
(
  SELECT id, k, v
  FROM _kv 
)
PIVOT 
(
  LISTAGG(v ,',') 
  WITHIN GROUP (ORDER BY k) 
  FOR k IN ('name', 'age','gender','status')
)
ORDER BY id;

由于您需要动态值,因此请使用动态 SQL 并传入通过在调用数据透视语句之前对表数据运行 select 确定的值。

【讨论】:

【参考方案3】:

恰好有一个枢轴任务。以下对我有用,正如刚才在 11g 上测试的那样:

select * from
(
  select ID, COUNTRY_NAME, TOTAL_COUNT from ONE_TABLE 
) 
pivot(
  SUM(TOTAL_COUNT) for COUNTRY_NAME in (
    'Canada', 'USA', 'Mexico'
  )
);

【讨论】:

【参考方案4】:

Oracle 11g 提供了一个PIVOT 操作,可以满足您的需求。

Oracle 11g 解决方案

select * from
(select id, k, v from _kv) 
pivot(max(v) for k in ('name', 'age', 'gender', 'status')

(注意:我没有 11g 的副本来测试它,所以我没有验证它的功能)

我从:http://orafaq.com/wiki/PIVOT获得此解决方案

编辑——pivot xml 选项(也是 Oracle 11g) 显然,当您不知道您可能需要的所有可能的列标题时,还有一个 pivot xml 选项。 (请参阅位于页面底部附近的 XML TYPE 部分,位于http://www.oracle.com/technetwork/articles/sql/11g-pivot-097235.html

select * from
(select id, k, v from _kv) 
pivot xml (max(v)
for k in (any) )

(注意:和以前一样,我没有 11g 的副本来测试它,所以我没有验证它的功能)

Edit2:pivotpivot xml 语句中的 v 更改为 max(v),因为它应该按照其中一个 cmets 中所述进行聚合。我还添加了in 子句,这对于pivot 来说不是可选的。当然,必须在in 子句中指定值会破坏拥有完全动态的枢轴/交叉表查询的目标,而这正是该问题发布者的愿望。

【讨论】:

pivot(v for k) 部分在 11g 中不起作用,for 需要一个 in,我不知道在 in 子句中添加什么 @dave - PIVOT 操作仅适用于一组固定的列。您的查询无法突然返回更多列,因为向表中添加了额外的行。 @JustinCave , @ojosilva 我认为in 子句可能是可选的,因为如果您想将列限制为特定值,并且如果需要所有值,则可以省略它。显然有一个pivot xml 选项可以处理这个问题(请参阅更新的答案)。 xml 枢轴在 v:(max(v) for k in (any)) 上添加了一个聚合,但结果是一个名为 K_XML 的 clob 列中的 xml - 这不是我所需要的。

以上是关于在 Oracle 中动态地将行转为列的主要内容,如果未能解决你的问题,请参考以下文章

根据 Oracle 10g PL/SQL 中的内容将行转为列

如何将行旋转为列,并按过去 7 天显示 - SQL SERVER

如何将Oracle中同一列的多行记录拼接成一个字符串

如何动态地将行转换为列,并为每列使用不同的列名

Oracle怎样将可变列转为行??

oracle 结果集行转列,多行数据转为一行显示,第一列内容拼接生成字段名