使用交叉应用将列转置为行

Posted

技术标签:

【中文标题】使用交叉应用将列转置为行【英文标题】:transpose columns to row using cross apply 【发布时间】:2019-03-11 11:36:41 【问题描述】:

原数据

+--------+-------+---------------+------------+--------+--------------+-------+
| RowNum | SeqNo |     Name      | NameReason | Gender | GenderReason |  ID   |
+--------+-------+---------------+------------+--------+--------------+-------+
|      1 | A123  | IronMan       |            | P      |              | E8888 |
|      2 | A123  | CaptainMarvel | A          | L      | A            | E8888 |
|      3 | A123  | Yoooo         |            |        |              | E8888 |
|      4 | A123  | Heyyy         |            |        |              | E8888 |
|      1 | B456  | Hey           |            |        |              | D2222 |
|      2 | B456  | DOTS          | A          |        |              | D2222 |
|      1 | C1234 | Hulk          |            |        |              | E8989 |
|      2 | C1234 | Cap           |            |        |              | E8989 |
|      3 | C1234 | Hat           |            |        |              | E8989 |
+--------+-------+---------------+------------+--------+--------------+-------+

我想要的结果

+-------+-------+---------+---------------+----------+--------+
| SeqNo |  ID   | ColName |  From_Value   | To_Value | Reason |
+-------+-------+---------+---------------+----------+--------+
| A123  | E8888 | Name    | CaptainMarvel | IronMan  | A      |
| A123  | E8888 | Gender  | L             | P        | A      |
| B456  | D2222 | Name    | DOTS          | Hey      | A      |
| C1234 | E8989 | Name    | Cap           | Hulk     |        |
+-------+-------+---------+---------------+----------+--------+

查询:

select a.rownum, a.seqno, a.name, a.id,
       b.*
from #A a cross apply
( values ('Name', NameReason)
         ('Gender', GenderReason)
    ) b (colname, Reason)
where reason is not null

注意:要查找更改。 RowNum=1 是最新更新 (To_Value),RowNum=2 (From_Value)。 Rownum 已经过滤了顶部更新的结果 (rownumber () over (partitition)我只需要 rownum=2(from_Value), rownum=1(to_value) 其他人忽略, 因为 1 个应用程序可以更新 100 次,我只需要找到最新的更改即可。

从上面的查询中,如何修改为我想要的结果?如何添加from_value和to_value,原因?

【问题讨论】:

请解释一下转换的逻辑。不明显。 @GordonLinoff 嗨。我只是想知道有什么变化。 RowNum=1 绝对是最新的更新。我想要的结果,From_Value=RowNum=2,To_Value=RowNum=1。我只想知道如何修改我的脚本以便满足 From_Value 和 To_Value 【参考方案1】:

假设您可以链接 RowNum + 1 上的“From”记录

SELECT q.SeqNo, q.ToName AS Name, q.ID, ca.ColName, ca.From_Value, ca.To_Value 
FROM
(
  SELECT 
   a1.ID, a1.SeqNo, a1.NameReason, a1.GenderReason, a1.RowNum, 
   a2.Name as FromName, a1.Name as ToName,
   a2.Gender AS FromGender, a1.Gender AS ToGender
  FROM #A a1
  JOIN #A a2 ON (a2.ID = a1.ID AND a2.RowNum = a1.RowNum + 1)
  WHERE (a1.NameReason IS NOT NULL OR a1.GenderReason IS NOT NULL)
    AND a1.Name != a2.Name
) q
CROSS APPLY (VALUES 
 (1, 'Name', NameReason, FromName, ToName), 
 (2, 'Gender', GenderReason, FromGender, ToGender)
) ca (ColSeq, ColName, Reason, From_Value, To_Value)
WHERE ca.Reason IS NOT NULL
ORDER BY q.SeqNo, ca.ColSeq;

但根据 cmets,只有 RowNum 1 和 2 重要。 然后可以简化。

SELECT q.SeqNo, q.ToName AS Name, q.ID, ca.ColName, ca.From_Value, ca.To_Value 
FROM
(
  SELECT 
   a1.ID, a1.SeqNo, 
   a1.NameReason, a2.Name as FromName, a1.Name as ToName,
   a1.GenderReason, a2.Gender AS FromGender, a1.Gender AS ToGender
  FROM #A a1
  JOIN #A a2 ON (a2.ID = a1.ID AND a2.RowNum = 2)
  WHERE a1.RowNum = 1
) q
CROSS APPLY (VALUES 
 (1, 'Name', NameReason, FromName, ToName), 
 (2, 'Gender', GenderReason, FromGender, ToGender)
) ca (ColSeq, ColName, Reason, From_Value, To_Value)
WHERE ca.Reason IS NOT NULL
ORDER BY q.SeqNo, ca.ColSeq;

结果:

SeqNo Name    ID    ColName From_Value    To_Value
----- ------- ----- ------- ------------- --------
A123  IronMan E8888 Name    CaptainMarvel IronMan
A123  IronMan E8888 Gender  L             P
B456  Hey     D2222 Name    DOTS          Hey

dbfiddle here

的测试

【讨论】:

如果我只想要 seqno、id、colname、from_value、to_value、reason 怎么办?使用此查询,它会显示所有更改。我只想要rownum = 1和rownum = 2。 2(from_value) > 1(to_value) 其他人忽略它。因为 1 个应用程序我可能更新了 100 次(不可能全部显示,所以只需要前 2 个)就可以了。这些 rownum 进行了过度分区(按日期和 idno 排序) 哦...好吧,这在这个 SQL 中很容易做到。只需更改子查询中的 WHERE 子句。 WHERE a1.RowNum = 1 似乎工作正常。我需要时间来“吸收又理解”查询。 如果 RowNum 是通过 ROW_NUMBER(PARTITION BY ID ORDER BY [date] desc) 计算的,那么 a2 上的 JOIN 不需要 AND a2.SeqNo = a1.SeqNo @luke 你的回答很棒。但是,如果我有理由命名并更新 2 次,我只需要最近的更改,我该如何修改它。顺便说一句,我使用你的第一个脚本而不是 1。【参考方案2】:

您可以使用UNION 编写如下查询。

  select t1.SeqNo, 
       t1.Name, 
       t1.ID, 
       'Name'                  as ColName, 
       (select name 
        from   mytable mt 
        where  mt.SeqNo = t1.seqNo 
               and RowNum = 2) as From_Value, 
       t1.Name                 as To_Value 
from   mytable t1 
where  t1.RowNum = 1 
union all
select * 
from   (select t1.SeqNo, 
               t1.Name, 
               t1.ID, 
               'Gender'                as ColName, 
               t1.Gender               as From_Value, 
               (select Gender 
                from   mytable mt 
                where  mt.SeqNo = t1.seqNo 
                       and RowNum = 1) as To_Value 
        from   mytable t1 
        where  t1.RowNum = 2) t 
where  From_Value is not null 

Online Demo

输出

+-------+---------------+-------+---------+---------------+----------+
| SeqNo | Name          | ID    | ColName | From_Value    | To_Value |
+-------+---------------+-------+---------+---------------+----------+
| A123  | CaptainMarvel | E8888 | Gender  | L             | P       |
+-------+---------------+-------+---------+---------------+----------+
| A123  | IronMan       | E8888 | Name    | CaptainMarvel | IronMan  |
+-------+---------------+-------+---------+---------------+----------+
| B456  | Hey           | D2222 | Name    | DOTS          | Hey      |
+-------+---------------+-------+---------+---------------+----------+

【讨论】:

我正在处理 10mils 记录 + 20+ cols。当我运行您的脚本时,出现错误“子查询返回 > 1 值” 您可以使用 LEAD 和 LAG

以上是关于使用交叉应用将列转置为行的主要内容,如果未能解决你的问题,请参考以下文章

Oracle:将列转置为行

将列转置为行 SQL Server

Postgresql 查询将列转置为行

Postgres 表选择多列并将结果(列)动态转换为行 - 将列转置为行

oracle sql 11G中如何将列转置为行

t-sql 将列转置为行