如何将此混合行/列表转换为所需的输出。 (Oracle SQL/发行版:Ora12c)

Posted

技术标签:

【中文标题】如何将此混合行/列表转换为所需的输出。 (Oracle SQL/发行版:Ora12c)【英文标题】:How to turn this mixed Row/Column Table into desired output. (Oracle SQL/Release:Ora12c) 【发布时间】:2019-11-27 13:50:47 【问题描述】:

我有这个 Excel 输入文件被远程传递,并试图将它导入到这个最终结果中,但没有任何运气。任何帮助/指针将不胜感激。到目前为止,我已经尝试过 Oracle LEAD 函数来获取前 6 行的数据(如果在这种情况下这是正确的术语),其余的都有问题。

所以我的传入数据采用这种格式(我在第 4 列的最后添加了行/行号,以使我的 LEAD 函数正常工作)。


+-----------+--------------+-------+------+
|   Col1    |     Col2     | Col3  | Col4 |
+-----------+--------------+-------+------+
| FYear     | 2019         |       | 1    |
| Office    | ABC          |       | 2    |
| Org       | xyz          |       | 3    |
| Acct      | 11122233     |       | 4    |
| SubAcct   | 555666       |       | 5    |
| Status    | C            |       | 6    |
| 1000      | blahblahblah | $1000 | 7    |
| 1001      | blahblahxxyy | $999  | 8    |
| 1029      | blahblahxxyy | $7676 | 9    |
| .....     | ..........   | ..... | ..   |
| FYear     | 2019         |       | 55   |
| Office    | EFG          |       | 56   |
| Org       | xyz          |       | 57   |
| Acct      | 11122233     |       | 58   |
| SubAcct   | 555888       |       | 59   |
| Status    | C            |       | 60   |
| 1000      | blahblahblah | $4440 | 61   |
| 1001      | blahblahxxyy | $3875 | 62   |
| 1029      | blahblahxxyy | $5029 | 63   |
| ......... | ......       | ....  | ...  |
+-----------+--------------+-------+------+

我正试图让它成为这种输出格式。


+------+------+------+--------+--------+------+------+--------------+-------+
| Col1 | Col2 | Col3 |  Col4  |  Col5  | Col6 | Col7 |     Col8     | Col9  |
+------+------+------+--------+--------+------+------+--------------+-------+
| 2019 | ABC  | xyz  | 112233 | 555666 | C    | 1000 | blahblahblah | $1000 |
| 2019 | ABC  | xyz  | 112233 | 555666 | C    | 1001 | blahblahxxyy | $999  |
| 2019 | ABC  | xyz  | 112233 | 555666 | C    | 1029 | blahblahxxyy | $7676 |
| ...  | ...  | ...  | ...    | ...    | .    | ...  | ...          | ...   |
| 2019 | EFG  | xyz  | 112233 | 555888 | C    | 1000 | blahblahblah | $4440 |
| 2019 | EFG  | xyz  | 112233 | 555888 | C    | 1001 | blahblahxxyy | $3875 |
| 2019 | EFG  | xyz  | 112233 | 555888 | C    | 1029 | blahblahxxyy | $5029 |
| ...  | ...  | ...  | ...    | ...    | .    | ...  | ...          | ...   |
+------+------+------+--------+--------+------+------+--------------+-------+

本质上,我需要将前 6 行(如果您愿意,可以是标题行)转换为水平/重复值,以用于其下方的行号。然后在 n 上重复逻辑,因为这些标题部分不断重复,下面有行号表。

任何指针/帮助表示赞赏!

P.s:- 这是我迄今为止尝试/想出的。

select (CASE WHEN Col1 = 'FYear' THEN Col2 END) New_Col1, 
LEAD((CASE WHEN Col1 = 'Office' THEN Col2 END)) OVER (ORDER BY Col4) New_Col2,
LEAD((CASE WHEN Col1 = 'Org' THEN Col2 END),2,0) OVER (ORDER BY Col4) New_Col3,
LEAD((CASE WHEN Col1 = 'Acct' THEN Col2 END),3,0) OVER (ORDER BY Col4) New_Col4,
LEAD((CASE WHEN Col1 = 'SubAcct' THEN Col2 END),4,0) OVER (ORDER BY Col4) New_Col5,
LEAD((CASE WHEN Col1 = 'Status' THEN Col2 END),5,0) OVER (ORDER BY Col4) New_Col6
from demo_table
where col4 <7;

【问题讨论】:

表中的行(在 SQL 中)没有排序。那么,您是如何在Col4 中添加数字的?在导入 SQL 数据库之前,在 Excel 中这样做是有意义的;如果您是在导出后完成的,并且无论如何都得到了正确的数字,那纯属运气。 你说得对,我之前在 excel 中做过,所以我可以使用 LEAD(order by)来进行查询。 有趣的问题,并且解释得很好。您包含了您的 Oracle 版本(总是非常重要);说明确切的版本(如 12.2.0.1)比 Oracle 用于营销的通用“12c”更有用。您还展示了您尝试的代码。比这里的绝大多数海报要好得多。请只提出一个请求:将来,不要像您那样为您的输入发布 ASCII 格式的表格,而是以您可以在 Soren Kongstad 的回答中看到的形式发布测试数据更有帮助:create table 和 @987654326 @ 语句(或 with 子句)。谢谢! 谢谢,注意!!下次发帖时我一定会尝试这样做。 【参考方案1】:

我支持数据库中的行没有排序的评论。 此外,您没有指定段是否可以超过 9 行。

我有一个适用于任意数量行 pr 段的解决方案,如果订单以某种方式保留,它需要每个段以 col1 中的“FYear”开头,并保留订单,但不不关心标题行之后是否正好有三行。

设置:

create table testtbl
(
  col1 varchar2(100)
  ,col2 varchar2(100)
  ,col3 varchar2(100)
  ,col4 int
  )
insert into testtbl values ('FYear'  ,2019,'',1);
insert into testtbl values ('Office' ,'ABC','',2);
insert into testtbl values ('Org'    ,'xyz','',3);
insert into testtbl values ('Acct'   ,11122233,'',4);
insert into testtbl values ('SubAcct',555666,'',5);
insert into testtbl values ('Status' ,'C','',6);
insert into testtbl values (1000     ,'blahblahblah',1000,7);
insert into testtbl values (1001     ,'blahblahxxyy',999,8);
insert into testtbl values (1029     ,'blahblahxxyy',7676,9);
insert into testtbl values ('FYear'  ,2019,'',10);
insert into testtbl values ('Office' ,'EFG','',11);
insert into testtbl values ('Org'    ,'xyz','',12);
insert into testtbl values ('Acct'   ,11122233,'',13);
insert into testtbl values ('SubAcct',555888,'',14);
insert into testtbl values ('Status' ,'C','',15);
insert into testtbl values (1000     ,'blahblahblah',4440,16);
insert into testtbl values (1001     ,'blahblahxxyy',3875,17);
insert into testtbl values (1029     ,'blahblahxxyy',5029,18);

解决方案:

我首先创建一个跨越序列中所有行的 grp,然后在该组中找到标题值并将它们放在前 6 列中。然后我将原来的 3 列添加为 col7-9,最后过滤掉包含标题的行。

select 
col1,col2,col3,col4,col5,col6,col7,col8,col9
from
(
select 
   max(case when col1='FYear' then Col2 else '' end)    over (partition by grp) Col1
  ,max(case when col1='Office' then Col2 else '' end)   over (partition by grp) Col2
  ,max(case when col1='Org' then Col2 else '' end)      over (partition by grp) Col3
  ,max(case when col1='Acct' then Col2 else '' end)     over (partition by grp) Col4
  ,max(case when col1='SubAcct' then Col2 else '' end)  over (partition by grp) Col5
  ,max(case when col1='Status' then Col2 else '' end)   over (partition by grp) Col6  
  , col1  col7
  , col2  col8
  , col3  col9
from 
(
select
  col1,col2,col3,col4
  ,sum(case when col1='FYear' then 1 else 0 end ) over (order by col4) grp
from testtbl t 
) a
) b
where  col7 not in('FYear','Office','Org','Acct','SubAcct','Status' )

【讨论】:

你说得对,我在 excel 中添加了行号,只是为了让 LEAD 函数运行。是的,一个段中可以有超过 9 行,或者更少,一些组每个段有 50-100 行等等。谢谢回复,我试试看。 哦,这就像一个魅力!不仅仅是代码,逻辑很漂亮!! :) 从那里的代码中脱颖而出。欣赏它。【参考方案2】:

由于您使用的是 Oracle 12,您可以利用match_recognize,它可以快速解决这个问题。 (在 Oracle 11 中,您可以使用联接和旋转,正如 Plnder Stibbons 所展示的那样,但这会更慢。)

select fyear, office, org, acct, subacct, status,
       col1 as col7, col2 as col8, col3 as col9
from   input_table
match_recognize(
  order by col4
  measures to_number(fyear.col2)   as fyear, 
           office.col2             as office,
           org.col2                as org,
           to_number(acct.col2)    as acct,
           to_number(subacct.col2) as subacct,
           status.col2             as status
  all rows per match
  pattern ( - fyear office org acct subacct status - x* )
  define   fyear   as col1 = 'FYear'  , office  as col1 = 'Office',
           org     as col1 = 'Org'    , acct    as col1 = 'Acct'  ,
           subacct as col1 = 'SubAcct', status  as col1 = 'Status',
           x as col1 not in ('FYear', 'Office', 'Org', 'Acct', 'SubAcct', 'Status')
             or col1 is null
);

请注意,我为输出中的前六列赋予了更有意义的名称;对于最后三列,您也应该找到比 col7, col8, col9 更好的名称。我还将fyearacctsubacct 转换为number 数据类型,您可能需要它们。我允许col1(如'1000' 等)中的“已转轴”值是任何 值,而不是六个特殊值'FYear', 'Office' 等(包括该值的可能性)有时可能是null) - 这可以在match_recognizedefine 子句中分类为“x”的行的定义中看到。

使用您的输入数据,我得到以下输出:

FYEAR OFFICE ORG     ACCT SUBACCT STATUS COL7 COL8         COL9
----- ------ --- -------- ------- ------ ---- ------------ -----
 2019 ABC    xyz 11122233 555666  C      1000 blahblahblah $1000
 2019 ABC    xyz 11122233 555666  C      1001 blahblahxxyy $999
 2019 ABC    xyz 11122233 555666  C      1029 blahblahxxyy $7676
 2019 EFG    xyz 11122233 555888  C      1000 blahblahblah $4440
 2019 EFG    xyz 11122233 555888  C      1001 blahblahxxyy $3875
 2019 EFG    xyz 11122233 555888  C      1029 blahblahxxyy $5029

您可能还应该将col9 转换为数字;我没有表现出来,因为这取决于你真正拥有的东西。它总是null 还是美元值(前面有美元符号的数字)?无论如何,这是与您的问题无关的附带问题,但请考虑一下。

另一个重要说明:我假设您的“段”始终将前六行作为“特殊”行,在您的示例中显示 col1 中的确切值。剩余的行可以在col1 中包含任何内容(除了特殊值)并且这些行的数量可以是任何内容,包括没有,在这种情况下,相应的“段”将产生 输出中绝对没有。如果在这种特殊情况下需要不同的处理,它可以很容易地适应,您只需要解释这种处理是什么。

【讨论】:

哦,我知道你在那里做了什么!很好,我将重构我的代码以使用 match_rec,非常感谢!伙计,这一天从非常糟糕变得非常酷:) 谢谢。【参考方案3】:

根据 col4 除以 9,将每个行号的行“1000”、“1001”、“1029”“传统地”连接​​到第 1-6 行:

with 
  d as (select t.*, floor((col4 - 1) / 9) + 1 rn from t),
  a as (select rn, 
               max(case col1 when 'FYear' then col2 end) col1,
               max(case col1 when 'Office' then col2 end) col2,
               max(case col1 when 'Org' then col2 end) col3,
               max(case col1 when 'Acct' then col2 end) col4,
               max(case col1 when 'SubAcct' then col2 end) col5,
               max(case col1 when 'Status' then col2 end) col6
          from d group by rn),
  b as (select rn, col1 as col7, col2 as col8, col3 as col9 
          from d where col1 in ('1000', '1001', '1029'))
select * 
  from a join b using (rn)
  order by rn, col7

dbfiddle

【讨论】:

快速思考:您可以使用ceil(col4 / 9) 代替floor(col4 - 1) / 9)。这会将组标记为 1, 2, ... 而不是 0, 1, ... 但结果是相同的,并且公式更简单一些。请记住这一点,当您实际上想要结果值 1、2、...时 - 在这种情况下,您仍然需要将 1 添加到 floor(),但您不需要添加任何内容到 @ 987654326@. 感谢 Ponder Stibbons,我也存储了您的代码。非常感谢。

以上是关于如何将此混合行/列表转换为所需的输出。 (Oracle SQL/发行版:Ora12c)的主要内容,如果未能解决你的问题,请参考以下文章

如何查询/将数据转换为所需的格式

xsl 未转换为所需的输出

将python中的LIST转换为所需的输出

将列表中的内容拆分为所需的输出

如何将 python 字典转换为所需的格式

如何将熊猫系列转换为所需的 JSON 格式?