没有 UNPIVOT 的 Oracle SQL 列到行

Posted

技术标签:

【中文标题】没有 UNPIVOT 的 Oracle SQL 列到行【英文标题】:Oracle SQL Columns to Rows without UNPIVOT 【发布时间】:2017-01-25 07:33:11 【问题描述】:

我目前拥有的:

Team    User    Apples    Oranges    Pears
Red     Adam    4         5          6
Red     Avril   11        12         13
Blue    David   21        22         23

需要什么:

Team    User    Product    Count
Red     Adam    Apples     4
Red     Adam    Oranges    5
Red     Adam    Pears      6
Red     Avril   Apples     11
Red     Avril   Oranges    12
Red     Avril   Pears      13
Blue    David   Apples     21
....

这将使用 Oracle SQL 来实现。我知道这可以使用 UNPIVOT 来完成,但是我的 Oracle SQL 版本太旧,无法支持这种方法。有人可以提供一个如何使用 CROSS APPLY 或等效方法来实现这一目标的示例吗? 数量根据团队-用户-产品组合而变化,并且产品类型的数量在未来可能会略有变化,因此可能需要可扩展的解决方案。

这是时间敏感的,所以我很感激帮助。

【问题讨论】:

您可以使用一系列联合来做到这一点,但这会相当难看。你有没有尝试过? select team, "USER", apples as product from the_table union all select team, "USER", oranges from the_table ... 【参考方案1】:

您可以使用交叉联接和一些 case 语句来执行此操作,方法是使用一个虚拟子查询,该子查询包含与您想要取消透视的列相同的行数(因为您希望每列进入自己的行),例如所以:

WITH your_table AS (SELECT 'Red' Team, 'Adam' usr, 4 Apples, 5 Oranges, 6 Pears FROM dual UNION ALL
                    SELECT 'Red' Team, 'Avril' usr, 11 Apples, 12 Oranges, 13 Pears FROM dual UNION ALL
                    SELECT 'Blue' Team, 'David' usr, 21 Apples, 22 Oranges, 23 Pears FROM dual)
-- end of mimicking your table. See SQL below:
SELECT yt.team,
       yt.usr,
       CASE WHEN d.id = 1 THEN 'Apples'
            WHEN d.id = 2 THEN 'Oranges'
            WHEN d.id = 3 THEN 'Pears'
       END product,
       CASE WHEN d.id = 1 THEN yt.apples
            WHEN d.id = 2 THEN yt.oranges
            WHEN d.id = 3 THEN yt.pears
       END count_of_product
FROM   your_table yt
       CROSS JOIN (SELECT LEVEL ID
                   FROM   dual
                   CONNECT BY LEVEL <= 3) d -- number of columns to unpivot
ORDER BY team, usr, product;

TEAM USR   PRODUCT COUNT_OF_PRODUCT
---- ----- ------- ----------------
Blue David Apples                21
Blue David Oranges               22
Blue David Pears                 23
Red  Adam  Apples                 4
Red  Adam  Oranges                5
Red  Adam  Pears                  6
Red  Avril Apples                11
Red  Avril Oranges               12
Red  Avril Pears                 13

这样做意味着您只需要遍历表一次,而不是多次使用 union all 方法。


ETA:这是 Aleksej 所指的方法 - 我建议根据您的数据集(希望足够大以具有代表性)测试这两种方法,看看哪一个更有效:

WITH your_table AS (SELECT 'Red' Team, 'Adam' usr, 4 Apples, 5 Oranges, 6 Pears FROM dual UNION ALL
                    SELECT 'Red' Team, 'Avril' usr, 11 Apples, 12 Oranges, 13 Pears FROM dual UNION ALL
                    SELECT 'Blue' Team, 'David' usr, 21 Apples, 22 Oranges, 23 Pears FROM dual)
-- end of mimicking your table. See SQL below:
SELECT yt.team,
       yt.usr,
       CASE WHEN LEVEL = 1 THEN 'Apples'
            WHEN LEVEL = 2 THEN 'Oranges'
            WHEN LEVEL = 3 THEN 'Pears'
       END product,
       CASE WHEN LEVEL = 1 THEN yt.apples
            WHEN LEVEL = 2 THEN yt.oranges
            WHEN LEVEL = 3 THEN yt.pears
       END count_of_product
FROM   your_table yt
CONNECT BY PRIOR team = team
           AND PRIOR usr = usr
           AND PRIOR sys_guid() IS NOT NULL
           AND LEVEL <= 3
ORDER BY team, usr, product;

TEAM USR   PRODUCT COUNT_OF_PRODUCT
---- ----- ------- ----------------
Blue David Apples                21
Blue David Oranges               22
Blue David Pears                 23
Red  Adam  Apples                 4
Red  Adam  Oranges                5
Red  Adam  Pears                  6
Red  Avril Apples                11
Red  Avril Oranges               12
Red  Avril Pears                 13

【讨论】:

我喜欢这种方法;我相信您甚至可以通过删除JOIN 并直接在源表上应用CONNECT BY 来简化它,而无需DUAL @Aleksej 我没有考虑过;这是可能的,但我不知道它是否会像性能一样好。不幸的是,我没有时间 atm 来测试它。 这种方法解决了我的问题并大大缩短了工时。不过我想了解更多 baout Aleksej 的想法,你能提供更多细节吗? 第二种解决方案在 CONNECT BY 之后引入了太多的 AND(实际上将近 10 个)子句,导致代码混乱。我拥有的数据集有大量列并且交叉连接,因此首选第一种解决方案。 This question 讨论了从单行生成多行的不同方法及其相对性能。结论是,直接在源表上使用CONNECT BYSYS_GUID() hack 的性能低于加入分层生成的表(或使用递归子查询因式分解子句 - 又名 CTE)来生成行。但是,您应该分析不同的解决方案以找到最适合您的数据库的解决方案。【参考方案2】:

你可以像这样使用一个大联合:

select 
    Team,
    "User",
    'Apples' Product,
    Apples "Count"
from your_table
union all
select 
    Team,
    "User",
    'Oranges' Product,
    Oranges "Count"
from your_table
union all
select 
    Team,
    "User",
    'Pears' Product,
    Pears "Count"
from your_table
union all
. . .

另外,尽量不要使用诸如 UserCount 之类的关键字作为标识符,或者像我一样将它们用双引号括起来。

【讨论】:

以上是关于没有 UNPIVOT 的 Oracle SQL 列到行的主要内容,如果未能解决你的问题,请参考以下文章

oracle 多列 列转行

没有聚合的 sql server 中的 UNPIVOT

行转列UNPIVOT列转行PIVOT,注意是Oracle 11g及以后才支持

oracle unpivot 多列到多列

oracle行转列和列转行(pivot 和 unpivot 函数,wm_concat函数 )

对于以下场景,如何在 oracle sql 中执行类似 unpivot 的操作?