一尘不染

在MySQL中取消枢轴化多列

sql

我有一些非规范化的数据,我试图将其移开,希望所有的人都能帮助我找出执行此操作的最佳方法。我已经使用多个并集语句完成了此操作,但是我希望做的是创建一个动态查询,随着向表中添加更多列,该查询可以一遍又一遍地执行此操作。我的数据看起来像这样:(数字列一直到50)

| Code  | Desc  | Code_0 | Desc_0| Period|    1    |    2   |    3    |    4     |  
|-------|-------|--------|-------|-------|---------|--------|---------|----------|
| NULL  | NULL  |  NULL  |  NULL |  Date |29-Nov-13|6-Dec-13|13-Dec-13| 20-Dec-13|  
|CTR07  |Risk   |  P1    | Phase1|  P    |   0.2   |  0.4   |   0.6   |    1.1   |         
|CTR07  |Risk   |  P1    | Phase1|  F    |   0.2   |  0.4   |   0.6   |    1.1   |          
|CTR07  |Risk   |  P1    | Phase1|  A    |   0.2   |  0.4   |   0.6   |    1.1   |
|CTR08  |Oper   |  P1    | Phase1|  P    |   0.6   |  0.6   |   0.9   |    2.7   |
|CTR08  |Oper   |  P1    | Phase1|  F    |   0.6   |  0.6   |   0.9   |    2.7   |
|CTR08  |Oper   |  P1    | Phase1|  A    |   0.6   |  0.6   |   0.9   |    2.7   |

列标题是最上面的行。如您所见,查看数据时,需要解决一些奇怪的问题。

日期字段开始前的前四个NULL列是一个问题。具有数字标题(1-50)的每一列代表一个星期。问题在于,每个星期在同一列中不仅具有日期字段,而且具有该周的百分比值。我想将其向下旋转,使其看起来像这样:

| Code  | Desc  |Code_0 |Desc_0 | Period| Date    |Percent|  
|-------|-------|-------|-------|-------|---------|-------|
|CTR07  | Risk  |  P1   | Phase |   P   | 11/29/13| 0.2   |   
|CTR07  | Risk  |  P1   | Phase1|   F   | 11/29/13| 0.2   |
|CTR07  | Risk  |  P1   | Phase1|   A   | 11/29/13| 0.2   |
|CTR08  | Oper. |  P1   | Phase1|   P   | 11/29/13| 0.6   |

每个星期的日期都在其自己的列中,并将百分比按其各自的日期分组。

由不同的代码,Desc,CODE_0,期间和日期作为键。我想将日期与数字列中的百分比分开,然后将数字列带入按日期连接的自己的列中。如前所述,我已经使用UNION语句静态完成了此操作,但是我想编写某种查询,该查询可以在表扩展时动态地执行。任何帮助将不胜感激。让我知道是否需要任何其他信息,这是我关于StackOverflow的第一个问题,我有两个漂亮的屏幕截图向您展示,但是在这次交流中我还不到10个。仅适用于科幻和幻想。我知道,对吧?

我在联合中用于静态创建底表的代码:

select `Code`, `Desc`, `Code_0`, `Desc_0`, `Period`, (select STR_TO_DATE(`1`, '%d%b%y') from combined_complete where `1` = '29Nov13') as `Date`, `1` as `Percent`
from combined_complete
where period <> 'Date'
union
select `Code`, `Desc`, `Code_0`, `Desc_0`, `Period`, (select STR_TO_DATE(`2`, '%d%b%y') from combined_complete where `2` = '06Dec13') as `Date`, `2`
from combined_complete
where period <> 'Date'
union
select `Code`, `Desc`, `Code_0`, `Desc_0`, `Period`, (select STR_TO_DATE(`3`, '%d%b%y') from combined_complete where `3` = '13Dec13') as `Date`, `3`
from combined_complete
where period <> 'Date'
union
select `Code`, `Desc`, `Code_0`, `Desc_0`, `Period`, (select STR_TO_DATE(`4`, '%d%b%y') from combined_complete where `4` = '20Dec13') as `Date`, `4`
from combined_complete
where period <> 'Date'

阅读 207

收藏
2021-03-08

共1个答案

一尘不染

对于这个建议,我创建了一个简单的50行表,称为TransPoser,在MySQL或您的数据库中可能已经有一个整数表,但是您想要类似的东西,将那些编号的列的数字从1转换为N。

然后,使用该表交叉连接到您的非规范化表(我称其为BadTable),但将其限制在第一行。然后使用一组case表达式将pivot这些日期字符串放入一列中。如果需要的话,可以将其转换为适当的日期(我建议这样做,但未包括在内)。

然后,将这种较小的换位用作主查询中的派生表。

主查询将忽略第一行,但也使用交叉联接将所有原始行强制为50行(在本示例中为4行)。然后将这种笛卡尔积乘回到上面讨论的派生表中以提供日期。然后是另一组case表达式,它们将百分比转换为与日期和各种代码对齐的列。

结果示例(来自示例数据),手动添加了空行:

| N |  CODE | DESC | CODE_0 | DESC_0 |   THEDATE | PERCENTAGE |
|---|-------|------|--------|--------|-----------|------------|
| 1 | CTR07 | Risk |     P1 | Phase1 | 29-Nov-13 |        0.2 |
| 1 | CTR07 | Risk |     P1 | Phase1 | 29-Nov-13 |        0.2 |
| 1 | CTR07 | Risk |     P1 | Phase1 | 29-Nov-13 |        0.2 |
| 1 | CTR08 | Oper |     P1 | Phase1 | 29-Nov-13 |        0.6 |
| 1 | CTR08 | Oper |     P1 | Phase1 | 29-Nov-13 |        0.6 |
| 1 | CTR08 | Oper |     P1 | Phase1 | 29-Nov-13 |        0.6 |

| 2 | CTR07 | Risk |     P1 | Phase1 |  6-Dec-13 |        0.4 |
| 2 | CTR07 | Risk |     P1 | Phase1 |  6-Dec-13 |        0.4 |
| 2 | CTR07 | Risk |     P1 | Phase1 |  6-Dec-13 |        0.4 |
| 2 | CTR08 | Oper |     P1 | Phase1 |  6-Dec-13 |        0.6 |
| 2 | CTR08 | Oper |     P1 | Phase1 |  6-Dec-13 |        0.6 |
| 2 | CTR08 | Oper |     P1 | Phase1 |  6-Dec-13 |        0.6 |

| 3 | CTR07 | Risk |     P1 | Phase1 | 13-Dec-13 |        0.6 |
| 3 | CTR07 | Risk |     P1 | Phase1 | 13-Dec-13 |        0.6 |
| 3 | CTR07 | Risk |     P1 | Phase1 | 13-Dec-13 |        0.6 |
| 3 | CTR08 | Oper |     P1 | Phase1 | 13-Dec-13 |        0.9 |
| 3 | CTR08 | Oper |     P1 | Phase1 | 13-Dec-13 |        0.9 |
| 3 | CTR08 | Oper |     P1 | Phase1 | 13-Dec-13 |        0.9 |

| 4 | CTR07 | Risk |     P1 | Phase1 | 20-Dec-13 |        1.1 |
| 4 | CTR07 | Risk |     P1 | Phase1 | 20-Dec-13 |        1.1 |
| 4 | CTR07 | Risk |     P1 | Phase1 | 20-Dec-13 |        1.1 |
| 4 | CTR08 | Oper |     P1 | Phase1 | 20-Dec-13 |        2.7 |
| 4 | CTR08 | Oper |     P1 | Phase1 | 20-Dec-13 |        2.7 |
| 4 | CTR08 | Oper |     P1 | Phase1 | 20-Dec-13 |        2.7 |

查询:

select
       n.n
     , b.Code
     , b.Desc
     , b.Code_0
     , b.Desc_0
     , T.theDate
     , case
            when n.n =  1 then `1`
            when n.n =  2 then `2`
            when n.n =  3 then `3`
            when n.n =  4 then `4`
         /* when n.n =  5 then `5` */
         /* when n.n = 50 then `50`*/
       end as Percentage
from BadTable as B
cross join (select N from TransPoser where N < 5) as N
inner join (
            /* transpose just the date row */
            /* join back vis the number given to each row */
            select
                    n.n
                  , case
                        when n.n =  1 then `1`
                        when n.n =  2 then `2`
                        when n.n =  3 then `3`
                        when n.n =  4 then `4`
                     /* when n.n =  5 then `5` */
                     /* when n.n = 50 then `50`*/
                   end as theDate
            from BadTable as B
            cross join (select N from TransPoser where N < 5) as N
            where b.code is null
            and b.Period = 'Date'
           ) as T on N.N = T.N
where b.code is NOT null
and b.Period <> 'Date'
order by
       n.n
     , b.code
;

对于以上内容,请参见此SQLFIDDLE

期望恕我直言,结果期望一个完全准备好的可执行文件交付确实不公平-
这是“拉伸友谊”。但是要将上面的查询转换为动态查询并不难。这有点“乏味”,因为语法有点棘手。我对MySQL并没有那么的经验,但这就是我的做法:

set @numcols := 4;
set @casevar := '';

set @casevar := (
                  select 
                  group_concat(@casevar
                                       ,'when n.n =  '
                                       , n.n
                                       ,' then `'
                                       , n.n
                                       ,'`'
                                      SEPARATOR ' ')
                  from TransPoser as n
                  where n.n <= @numcols
                 )
;


set @sqlvar := concat(
          'SELECT n.n , b.Code , b.Desc , b.Code_0 , b.Desc_0 , T.theDate , CASE '
        , @casevar
        , ' END AS Percentage FROM BadTable AS B CROSS JOIN (SELECT N FROM  TransPoser WHERE N <='
        , @numcols
        , ') AS N INNER JOIN ( SELECT n.n , CASE '
        , @casevar                                                                                                       
        , ' END AS theDate FROM BadTable AS B CROSS JOIN (SELECT N FROM  TransPoser WHERE N <='
        , @numcols
        , ') AS N WHERE b.code IS NULL '
        , ' AND b.Period = ''Date'' ) AS T ON N.N = T.N WHERE b.code IS NOT NULL AND b.Period <> ''Date'' ORDER BY n.n , b.code ' 
        );

PREPARE stmt FROM @sqlvar;
EXECUTE stmt;

[Demo of the dynamic approach](http://sqlfiddle.com/#!2/d11f7d/2)

2021-03-08