反转 Excel matrix/pivot-table?

Unpivot an Excel matrix/pivot-table?

有没有一种快速的方法 "unpivot" Excel matrix/pivot-table(在 Excel 或其他地方),无需编写宏或其他代码
同样,我可以自己编写代码(C# 或 VBA 或其他)来执行此操作。
我想知道是否可以 无需 代码,快速完成?

例如我需要转换这个权限矩阵(给定为 Excel-table/matrix)

到这个半规范化的 table 中(这样我就可以将它插入到 SQL 数据库中):

例如在 SQL 我可以这样做:

CREATE TABLE dbo.T_DocumentMatrix
(
    [Function] [varchar](255) NULL,
    [GROUP-Admin] [varchar](255) NULL,
    [GROUP-SuperUser] [varchar](255) NULL,
    [GROUP-Manager] [varchar](255) NULL,
    [GROUP-OLAP] [varchar](255) NULL,
    [GROUP-1] [varchar](255) NULL,
    [GROUP-2] [varchar](255) NULL,
    [GROUP-3] [varchar](255) NULL,
    [GROUP-4] [varchar](255) NULL,
    [GROUP-5] [varchar](255) NULL,
    [GROUP-6] [varchar](255) NULL,
    [GROUP-7] [varchar](255) NULL,
    [GROUP-8] [varchar](255) NULL,
    [Externals] [varchar](255) NULL
); 

从excel复制粘贴数据,然后

SELECT * 
FROM 
(
    SELECT 
         [Function]
        ,[GROUP-Admin]
        ,[GROUP-SuperUser]
        ,[GROUP-Manager]
        ,[GROUP-OLAP]
        ,[GROUP-1]
        ,[GROUP-2]
        ,[GROUP-3]
        ,[GROUP-4]
        ,[GROUP-5]
        ,[GROUP-6]
        ,[GROUP-7]
        ,[GROUP-8]
        ,[Externals]
    FROM T_DocumentMatrix
) AS p
UNPIVOT
(
    Rights FOR GroupName IN 
    (
         [GROUP-Admin]
        ,[GROUP-SuperUser]
        ,[GROUP-Manager]
        ,[GROUP-OLAP]
        ,[GROUP-1]
        ,[GROUP-2]
        ,[GROUP-3]
        ,[GROUP-4]
        ,[GROUP-5]
        ,[GROUP-6]
        ,[GROUP-7]
        ,[GROUP-8]
        ,[Externals]
    )
) AS unpvt
;

但是,这需要我为组中的每个更改更改 table-create 脚本和 unpivot-script...

哦,好吧,有点复杂。 问题之一是,向导调用快捷方式在 excels 的非英语版本中不起作用(该死的,在家里我会有英语版本,但在工作中...)

这是一个很好的视频: https://www.youtube.com/watch?v=pUXJLzqlEPk

但是 youtube 视频可以删除,所以要让它成为一个可靠的 SO 答案:

首先,您需要转到 "Options",然后添加菜单项 "Pivot table and PivotChart Wizard"。

创建多重整合枢轴table

并使用自定义变体

和select范围,以及新作品sheet

然后删除行和列字段

双击数字(图片中的 54)

和 excel 将为您提供中途归一化的数据。

Power Query还有另一种方式:

  • select 你的细胞
  • 菜单 Data > From a table or a range

  • 在 Power Query 编辑器中,选择所有列保存第一列,然后 Transform > Unpivot

  • table 未旋转。转到 Home > Close and load

  • 你的未旋转 table 在这里。 right-click 它并选择 Refresh 如果您的原始 table 已更新

我正在使用这个 VBA 代码

Sub Unpivot()
'
Dim Rowlabel As Range
Dim Columnlabel As Range
Dim Pap As Range
Dim Tabl As Range
Dim i As Integer
Dim j As Integer
Dim a As Integer
Dim b As Integer
Dim Data As Range
Dim k As Integer
Dim Label As Range
Dim pvtCache As PivotCache
Dim pvt As PivotTable
Dim SrcData As String
'
ActiveSheet.Copy Before:=Worksheets(1)
Set Tabl = Selection
    For Each Pap In Tabl
     If Pap.MergeCells Then
        With Pap.MergeArea
            .UnMerge
            .Value = Pap.Value
        End With
    End If
    Next
i = Application.InputBox("Number of row contain label:", "Excel", i, Type:=2)
j = Application.InputBox("Number of column contain label:", "Excel", j, Type:=2)
On Error Resume Next
Sheets("Unpivot_Table").Delete
Sheets.Add.Name = "Unpivot_Table"
Set Pap = Range("Unpivot_Table!B2")
b = Tabl.Rows.Count
a = Tabl.Columns.Count
Set Data = Range(Tabl.Cells(i + 1, j + 1), Tabl.Cells(b, a))
Set Columnlabel = Range(Tabl.Cells(i + 1, 1), Tabl.Cells(b, j))
Set Rowlabel = Range(Tabl.Cells(1, j + 1), Tabl.Cells(i, a))
Pap.Select
For Each Column In Data.Columns
    Column.Copy
    Selection.PasteSpecial Paste:=xlPasteValues
    Columnlabel.Copy
    Selection.Offset(0, 1).PasteSpecial Paste:=xlPasteValues
    Column.Copy
    Selection.Offset(b - i, -1).Select
Next Column
Pap.Offset(0, j + 1).Select
For Each Column In Rowlabel.Columns
    Column.Copy
    Range(Selection, Selection.Offset(b - i - 1, 0)).PasteSpecial Paste:=xlPasteValues, Transpose:=True
    Selection.End(xlDown).Offset(1, 0).Select
Next Column
Set Label = Range(Pap.Offset(-1, 0), Pap.Offset(0, i + j + 1))
    For k = 1 To i + j + 1
    Label.Cells(1, k).Value = Application.InputBox(Label.Cells(2, k).Value & " is belong to Fieldname", "Hoang", k, Type:=2)
    Next
Range(Pap.End(xlUp), Pap.End(xlDown).End(xlToRight)).Select
SrcData = ActiveSheet.Name & "!" & Selection.Address
On Error Resume Next
    Sheets("Pivot").Delete
    Sheets.Add.Name = "Pivot"
  Set pvtCache = ActiveWorkbook.PivotCaches.Create( _
    SourceType:=xlDatabase, _
    SourceData:=SrcData)
  Set pvt = pvtCache.CreatePivotTable( _
    TableDestination:="Pivot!" & Sheets("Pivot").Range("A3").Address(ReferenceStyle:=xlR1C1), _
    TableName:="PivotTable1")
End Sub

我相信您可以使用一种模块化算法,如下所示。使用列和行图例将您的数据放入此 UDF 的参数中。

Function MyUnpivot(matice As Range) As Variant
    Dim I As Integer
    Dim J As Integer

    Dim radka As Integer
    Dim sloupec As Integer

    I = matice.Rows.Count - 1
    J = matice.Columns.Count - 1

    Dim returnVal()
    ReDim Preserve returnVal(1 To I * J, 1 To 3)

    For x = 1 To I * J
        radka = ((x - 1) Mod I) + 2
        sloupec = WorksheetFunction.Floor_Math((x - 1 / 2) / I) + 2
        returnVal(x, 1) = matice.Cells(1, sloupec)
        returnVal(x, 2) = matice.Cells(radka, 1)
        returnVal(x, 3) = matice.Cells(radka, sloupec)
    Next

    MyUnpivot = returnVal
End Function

虽然这是一个非常古老的问题,并且 Stefan 在当天找到了一个开明的答案,但值得重新审视。我自己 运行 需要这样一种无代码的动态 Unpivot 方法,Google 搜索将我带到了这里。是的,Power Query 完成了这项工作,但这并不是完全无代码,因为在 Power BI 中有一个脚本化的后台解决方案 运行ning,它需要用户刷新数据(因此,它在工作簿中不是自动的) 并且它不会 运行 on Excel for Mac (tmk).

以下是基于动态数组并使用LET函数的方法,因此需要Excel2016或Microsoft 365

假设 Stefan 的数据在单元格 A1 到 N8 中。用 Power Query 的说法,我们会说 Stefan 想要“Unpivot B1:N8 By A1:A8”。

NB: The following approach would also accept multiple columns for By, e.g. you could have a need to "Unpivot D1:N8 By A1:C8".

=LET( unPivMatrix, B1:N8,
      byMatrix, A1:A8,
       upC, COLUMNS( unPivMatrix ),
       byC, COLUMNS( byMatrix ),
       dmxR, MIN( ROWS( unPivMatrix ), ROWS( byMatrix ) ) - 1,
       upCells, dmxR * upC,
       upSeq, SEQUENCE( upCells,, 0 ),
       upHdr, INDEX( INDEX( unPivMatrix, 1, ),  1,  SEQUENCE( upC ) ),
       upBody, INDEX( unPivMatrix,  SEQUENCE( dmxR ) + 1,  SEQUENCE( 1, upC ) ),
       byBody, INDEX( byMatrix,  SEQUENCE( dmxR ) + 1,  SEQUENCE( 1, byC ) ),
       attr, INDEX( upHdr, MOD( upSeq, upC ) + 1 ),
       mux, INDEX( upBody, upSeq/upC + 1, MOD( upSeq, upC ) + 1 ),
       demux, IFERROR( INDEX(
                             IFERROR( INDEX( byBody,
                                             IFERROR( INT( SEQUENCE( upCells, byC,0 )/byC/upC ) + 1, MOD( upSeq, upC ) + 1 ),
                                                      SEQUENCE( 1, byC + 1 ) ),
                                       attr ),
                             upSeq + 1, SEQUENCE( 1, byC + 2 ) ),
                        mux ),
       demux
     )

这是如何工作的 - 读取输入

输入是你想要反转的范围,我称之为 unPivMatrix B1:N8(可以是你需要的任何维度)和你想要的列通过我调用的 byMatrix A1:A8.

来反转它们

As a rule, the rows of the byMatrix need to be the same as the unPivMatrix, so you must start with A1 and not A2. I decided to take this convention because their might be header in A1 and with some small mods, this formula could produce exactly the same output as Power Query, but that is not what Stefan asked for.

公式首先计算每个矩阵的列数:upC unpivot columns 和 byC by columns。从这些它计算 dmxR(多路分解行):通过取 unPivMatrix 和 byMatrix 的行中的最小值并减去 1,因为 unPivMatrix 有一个 header。如果输入具有不同的行数,根据定义这是一个错误,则采用 MIN 是一种错误预防步骤。

这些值用于创建稍后将用于形成输出的整形变量。 upCells 是将被取消透视的值的数量,用于生成名为 upSeq 的索引模式,该模式从 0 开始计算值的数量稍后将在 INDEX 函数中使用。我们从 0 开始,因为 upSeq 将被调制以形成正确的输入和输出索引。

现在我们将分解矩阵的各个部分,以便我们可以将它们复用。零件看起来像这样:

有一个逆枢轴 header (upHdr),其中包含值数据的标识符(在 Stefan 的例子中,GROUP-Admin、GROUP-SuperUser, ETC。)。这些将被复用到一个列中,稍后将放置在每个未旋转的值旁边。 upHdr 是通过将整个 unPivMatrix 放入 INDEX 函数并读取第 1 行和所有列来创建的。我将该 INDEX 函数嵌套到另一个 INDEX 中,该 INDEX 使用大小为 upC 的垂直 SEQUENCE 将水平数组重塑为垂直数组。

upBody 包含我们要通过多路复用反旋转的值。它是通过将 upMatrix 放入 INDEX 并根据将输出的行数对其进行整形而创建的 SEQUENCE( dmxR ) + 1 § 针对要读取的每一列 SEQUENCE( upC ). byBody 包含将针对 upBody 中的每个值进行多路复用的数据。它的创建方式与 upBody 相同。

§ - 添加 1 以跳过 header 行

这是如何工作的 - 塑造和写入输出

输出的形状如下:

我们现在将 upHdr 多路复用到 attr 或属性(使用 Power Query 术语),方法是将 upHdr 放入 INDEX 并应用基于 upSeq 的调制序列,该序列每 upC 次重复一次(例如 {1;2;3;4;5;6;7;8;1;2;...} )。 注意:这就是 upSeq 中从 0 开始的问题。 attr 的输出看起来像(在 Stefan 的情况下){GROUP-Admin; GROUP-SuperUser, etc.}

mux 是将针对每个属性和 byBody 行复用的值(使用 Power Query 术语)。它是通过将 valBody 放入 INDEX 中,然后将其重塑为由

行创建的多重模式来创建的

upSeq/upC + 1 产生一行 {1,1,1,1,1,1,1,1,2,2,...}

的列

MOD( upSeq, upC ) + 1 产生一列 {1;2;3;4;5;6;7;8;1;...}.

mux 的输出将是 unPivMatrix 的内容。在 Stefan 的例子中,这会有点特别,因为他使用 * 和空白作为数据。此公式会将空格转换为 0。因此,如果这是一个问题,您可以将 mux 包装到一个 IF( ISBLANK( mux ), "", mux ) 中,但我没有添加它,因为我想要一个通用的 unpivot,因为我确信 Stefan 早已继续前进。

这是如何工作的 - 将部分解复用到输出中

现在简单的部分已经完成,是时候处理困难的部分了 - 将所有这些都整合到一个动态数组中。将多个数组放在一起需要一个技巧,这个技巧必须应用两次,因为如您所见,我们将三个 table 放在一起。诀窍就像有一个 APPEND 函数,如:

APPEND( APPEND( table1, table2 ), table3 )

要合并两个数组,请将第一个数组放入索引中,然后引用数组外部的单元格以强制使用#REF!错误。例如,如果我有一个由字母 A 到 F 组成的 3 x 2 数组,并且我引用单元格 3、3,它将引发引用错误。

现在,您可以通过将 INDEX 包装在 IFERROR 中,将错误替换为您要附加的 table 来利用这些错误。有点像:

IFERROR( INDEX( table1,
                SEQUENCE( table1.rows ),
                SEQUENCE( 1, table1.columns + table2.columns ) ),
         table2 )

从这个意义上说,上面的公式相当于 APPEND( table1, table2 ) 其中 APPEND 是两个 table 的 row-wise 追加这就是我们想要的。 (注意:切换序列模式,您可以进行 column-wise 追加。)

因此,希望这个解释能够清楚地说明在传递结果的名为 demux 的变量的最后阶段发生了什么。我为结果命名,然后对其进行引用,以便您可以轻松探索、修改或优化公式。所以,demux 真的是这样的:

APPEND( byBody, APPEND( attr, mux ) )

我不会深入探讨这个最后阶段是如何工作的机制,因为这已经是一个很长的答案,但一个简短的总结是,这个附加是使用由 upCells、upC 和 byC 创建的维度来形成输出。

我已经对此进行了测试,但我没有对其进行性能优化或使其达到#SwissEngineering 标准。