Chuyển cột thành dòng với vòng lặp trong Power query

Liên hệ QC

ptm0412

Bad Excel Member
Thành viên BQT
Administrator
Tham gia
4/11/07
Bài viết
13,804
Được thích
36,315
Donate (Momo)
Donate
Giới tính
Nam
Nghề nghiệp
Consultant
Trong power query nếu có 1 loại số liệu đang thiết kế dạng nhiều cột, muốn chuyển thành dòng chỉ cần Unpivot table các cột đó.
Trường hợp 2 loại số liệu như số lượng/ thành tiền, doanh thu/chi phí, mỗi nhóm là 2, 3 cột số liệu nối tiếp nhau thì việc unpivot trở nên khó khăn.
Với code sau đây dùng vòng lặp để Select Column khiến cho code nhẹ nhàng hơn rất nhiều. FIle đính kèm có dữ liệu tồn của 12 tháng, mỗi tháng 3 cột, tổng 36 cột.

Trường hợp 1: Tên cột có quy luật như Ma01, SL01, TT01, Ma02, SL02, TT02, ... Ma12, SL12, TT12:
Dùng cấu trúc tên cột để select đúng tên cột, ví dụ "Ma" & n với n từ 1 đến 12 và định dạng "00", tương tự là SL và TT

PHP:
let
    MonthList={1..12},
    Source = List.Transform(MonthList, (i) =>
    let
 
    Tablei=Table.SelectColumns(AllData,{"Ma" & Text.PadStart(Text.From(MonthList{i-1}),2,"0"),
        "SL" & Text.PadStart(Text.From(MonthList{i-1}),2,"0"),
        "TTien" & Text.PadStart(Text.From(MonthList{i-1}),2,"0")}),
    Tablei1=Table.AddColumn(Table.RenameColumns(Tablei,{{"Ma" & Text.PadStart(Text.From(MonthList{i-1}),2,"0"),"Ma"},{ "SL" &
        Text.PadStart(Text.From(MonthList{i-1}),2,"0"),"SL"},{"TTien" & Text.PadStart(Text.From(MonthList{i-1}),2,"0"),"TTien"}}),"Month",
         each "Th" &  Text.PadStart(Text.From(MonthList{i-1}),2,"0"))
    in Tablei1),
    Source1=Table.FromList(Source,Splitter.SplitByNothing()),
    Source2 = Table.ExpandTableColumn(Source1,"Column1",Table.ColumnNames(Source{0})),
    Result=Table.SelectRows(Source2, each [Ma] <> null)
in
    Result

Trường hợp 2: Tên cột không có quy luật nhưng vẫn theo thứ tự mỗi tháng 3 cột, tuần tự đúng ý nghĩa dữ liệu các cột là Ma, SL và TT dù tên không quy luật (ví dụ Mã1, Ma02, Ma3, Mã 4)
Trường hợp này lấy 1 List tên cột (36 cột), dùng công thức biến đổi biến i (từ 1 đến 12) thành những thứ tự tương ứng từ 1 đến 36, dùng để select.

PHP:
let
    ColumnList=Table.ColumnNames(AllData),
    TableList={1..12},

    Source = List.Transform(TableList, (i) =>
    let
        Col1 = ColumnList{(i-1) * 3 },
        Col2 = ColumnList{(i-1) * 3 +1},
        Col3 = ColumnList{(i-1) * 3 +2},
        Source0 = Table.SelectColumns(AllData,{Col1,Col2,Col3}),
        Source1 = Table.AddColumn(Source0, "Tháng", each "Th" & Text.PadStart(Text.From(TableList{i-1}),2, "0")),
        Source2 = Table.RenameColumns(Source1,{{Col1,"Ma"},{Col2,"SL"},{Col3,"TTien"}})
    in
        Source2),
    SourceTable=Table.FromList(Source, Splitter.SplitByNothing()),
 
    ColList = Table.ColumnNames(SourceTable[Column1]{0}),
    Result = Table.SelectRows(Table.ExpandTableColumn(SourceTable,"Column1",ColList),each [Ma] <> null)
in
    Result
Code trường hợp 2 này có thể tùy biến khi chưa biết số tháng đã có dữ liệu là bao nhiêu bằng cách thay
TableList={1..12}
bằng
TableList = {1..List.Count(ColumnList)/3}
 

File đính kèm

  • LoopCombine.xlsx
    995.4 KB · Đọc: 49
Lần chỉnh sửa cuối:
Áp dụng 1:
Giả sử ta có bảng kê doanh số, tiền lương và lợi nhuận theo cột như sau

1634530828124.png

Cấu trúc bảng là mỗi tháng 4 cột, nhưng tên cột không có quy luật, cũng mới có 6 tháng và sẽ có thêm dữ liệu cho những tháng tiếp theo.
Áp dụng code 2 ở bài 1 như sau:

PHP:
let
    AllData = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    ColumnList=Table.ColumnNames(AllData),
    TableList={1..List.Count(ColumnList)/4},

    Source = List.Transform(TableList, (i) =>
    let
        Col1 = ColumnList{(i-1) * 4 },
        Col2 = ColumnList{(i-1) * 4 +1},
        Col3 = ColumnList{(i-1) * 4 +2},
        Col4 = ColumnList{(i-1) * 4 +3},
        Source0 = Table.SelectColumns(AllData,{Col1,Col2,Col3,Col4}),
        Source1 = Table.AddColumn(Source0, "Tháng", each "Th" & Text.PadStart(Text.From(TableList{i-1}),2, "0")),
        Source2 = Table.RenameColumns(Source1,{{Col1,"Chi nhánh"},{Col2,"Doanh thu"},{Col3,"Tiền lương"},{Col4,"Lợi nhuận"}})
    in
        Source2),
    SourceTable=Table.FromList(Source, Splitter.SplitByNothing()),
   
    ColList = Table.ColumnNames(SourceTable[Column1]{0}),
    Result = Table.SelectRows(Table.ExpandTableColumn(SourceTable,"Column1",ColList),each [Chi nhánh] <> null)
in
    Result
Nếu có tiếp dữ liệu tháng 7 thì nối vào bên phải cột cuối là được.
 

File đính kèm

  • LoopCombine2.xlsx
    25.8 KB · Đọc: 25
Lần chỉnh sửa cuối:
Áp dụng:
Giả sử ta có bảng kê doanh số, tiền lương và lợi nhuận theo cột như sau

View attachment 267907

Cấu trúc bảng là mỗi tháng 4 cột, nhưng tên cột không có quy luật, cũng mới có 6 tháng và sẽ có thêm dữ liệu cho những tháng tiếp theo.
Áp dụng code 2 ở bài 1 như sau:

PHP:
let
    AllData = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    ColumnList=Table.ColumnNames(AllData),
    TableList={1..List.Count(ColumnList)/4},

    Source = List.Transform(TableList, (i) =>
    let
        Col1 = ColumnList{(i-1) * 4 },
        Col2 = ColumnList{(i-1) * 4 +1},
        Col3 = ColumnList{(i-1) * 4 +2},
        Col4 = ColumnList{(i-1) * 4 +3},
        Source0 = Table.SelectColumns(AllData,{Col1,Col2,Col3,Col4}),
        Source1 = Table.AddColumn(Source0, "Tháng", each "Th" & Text.PadStart(Text.From(TableList{i-1}),2, "0")),
        Source2 = Table.RenameColumns(Source1,{{Col1,"Chi nhánh"},{Col2,"Doanh thu"},{Col3,"Tiền lương"},{Col4,"Lợi nhuận"}})
    in
        Source2),
    SourceTable=Table.FromList(Source, Splitter.SplitByNothing()),
 
    ColList = Table.ColumnNames(SourceTable[Column1]{0}),
    Result = Table.SelectRows(Table.ExpandTableColumn(SourceTable,"Column1",ColList),each [Chi nhánh] <> null)
in
    Result
Nếu có tiếp dữ liệu tháng 7 thì nối vào bên phải cột cuối là được.
Tôi thấy bạn đang hardcode ở số lượng column, giả sử mỗi tháng 5 cột là phải sữa lại code (thêm Col5) nên chỗ này nên tự động chỉ cần khai báo số lượng cột ban đầu thôi
 
Lần chỉnh sửa cuối:
Tôi thấy bạn đang hardcode ở số lượng column, giả sử mỗi tháng 5 cột là phải sữa lại code (thêm Col5) nên chỗ này nên tự động chỉ cần khai báo số lượng cột ban đầu thôi
Nếu chỉ dùng unpivot có sẵn thì làm thế nào anh nhỉ?
 
Tôi thấy bạn đang hardcode ở số lượng column, giả sử mỗi tháng 5 cột là phải sữa lại code (thêm Col5) nên chỗ này nên tự động chỉ cần khai báo số lượng cột ban đầu thôi
Tôi cũng đã nghĩ đến việc đó và sẽ thử code "mềm" cho nó
 
Áp dụng 2:
Trường hợp chỉ có 1 cột tiêu đề dòng, phía sau là các nhóm cột số liệu (không có tiêu đề dòng) như hình, chỉ cần sửa việc tính toán theo i, trong đó Col1 luôn luôn bằng 0

1634546607161.png

Code như sau:
PHP:
let
    AllData = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    ColumnList=Table.ColumnNames(AllData),
    TableList={1..(List.Count(ColumnList)-1)/3},

    Source = List.Transform(TableList, (i) =>
    let
        Col1 = ColumnList{0 },
        Col2 = ColumnList{(i-1) * 3 +1},
        Col3 = ColumnList{(i-1) * 3 +2},
        Col4 = ColumnList{(i-1) * 3 +3},
        Source0 = Table.SelectColumns(AllData,{Col1,Col2,Col3,Col4}),
        Source1 = Table.AddColumn(Source0, "Tháng", each "Th" & Text.PadStart(Text.From(TableList{i-1}),2, "0")),
        Source2 = Table.RenameColumns(Source1,{{Col1,"Chi nhánh"},{Col2,"Doanh thu"},{Col3,"Tiền lương"},{Col4,"Lợi nhuận"}})
    in
        Source2),
    SourceTable=Table.FromList(Source, Splitter.SplitByNothing()),
    
    ColList = Table.ColumnNames(SourceTable[Column1]{0}),
    Result = Table.SelectRows(Table.ExpandTableColumn(SourceTable,"Column1",ColList),each [Doanh thu] <> null)
in
    Result
 

File đính kèm

  • LoopCombine3.xlsx
    25.9 KB · Đọc: 18
Lặp tổng quát cho bảng kết quả có số cột bất kỳ (1):
Theo gợi ý của @excel_lv1.5 ở bài 3, sửa code bài 2 trong file đính kèm bài 2 (LoopCombine2.xlsx), áp dụng cho số cột kết quả tùy ý.
Bước 1:
Thêm bảng tên cột mong muốn của kết quả, thay cho tất cả tên cột cần chuyển thành dòng. Có thể tạo tay hay lấy từ sheet cũng được.

1634613124272.png

Bước 2:
Code


PHP:
let
    AllData = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    n= List.Count(ResultColumnList),
    DataColumnsList =Table.ColumnNames(AllData),
    DataColumnsCount = List.Count(DataColumnsList),
    TableList={1..DataColumnsCount/n},
    ColNumList ={0..n-1},
    
    Source = List.Transform(TableList, (i) =>
    let
        ColNameListi = List.Transform(ColNumList, (t) =>
        let
            ColNum = (i-1) * n + t,
            ColName = DataColumnsList{ColNum}
        in ColName),
        Source0 = Table.SelectColumns(AllData,ColNameListi),
        Source1 = Table.AddColumn(Source0, "Tháng", each "Th" & Text.PadStart(Text.From(TableList{i-1}),2, "0")),
        ColRList = Table.ColumnNames(Source1),
        Result0= List.Accumulate(ColNumList, Source1, (state,current) =>
        let
            Result1 = Table.RenameColumns(state, {ColRList{current}, ResultColumnList{current}})
        in Result1)
    in Result0),
    SourceTable=Table.FromList(Source, Splitter.SplitByNothing()),
    ColList = Table.ColumnNames(SourceTable[Column1]{0}),
    Result = Table.SelectRows(Table.ExpandTableColumn(SourceTable,"Column1",ColList),each [Chi nhánh] <> null)
in
    Result
Trong đó thêm 2 vòng lặp con:
- Vòng lặp con ColNameListi tính toán và tạo list tên các cột cần lấy cho bảng thứ i
- Vòng lặp con Result0 đổi tên tuần tự từng cột trong bảng i thành tên cột trong list ở bước 1. Vòng lặp này không dùng List.Transform mà dùng List.Accumulate
 
Áp dụng lặp tổng quát cho bảng chỉ có 1 cột tiêu đề dòng
Tương tự như bài 6, với bước 1 tạo list tên cột kết quả như bài 7, code sẽ là:
PHP:
let
    AllData = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    n= List.Count(ResultColumnList) -1,
    DataColumnsList =Table.ColumnNames(AllData),
    DataColumnsCount = List.Count(DataColumnsList),
    TableList={1..(DataColumnsCount - 1)/n},
    ColNumList ={0..n},
    
    Source = List.Transform(TableList, (i) =>
    let
        ColNameListi = List.Transform(ColNumList, (t) =>
        let
            ColNum = if t = 0 then 0 else (i-1) * n + t,
            ColName = DataColumnsList{ColNum}
        in ColName),
        Source0 = Table.SelectColumns(AllData,ColNameListi),
        Source1 = Table.AddColumn(Source0, "Tháng", each "Th" & Text.PadStart(Text.From(TableList{i-1}),2, "0")),
        ColRList = Table.ColumnNames(Source1),
        Result0= List.Accumulate(ColNumList, Source1, (state,current) =>
        let
            Result1 = Table.RenameColumns(state, {ColRList{current}, ResultColumnList{current}})
        in Result1)
    in Result0),
    SourceTable=Table.FromList(Source, Splitter.SplitByNothing()),
    ColList = Table.ColumnNames(SourceTable[Column1]{0}),
    Result = Table.SelectRows(Table.ExpandTableColumn(SourceTable,"Column1",ColList),each [Doanh thu] <> null)
in
    Result

File đính kèm cũng dùng code này, cho bảng 5 cột chứ không phải 4 mà không phải sửa gì cả.

1634615739806.png
 

File đính kèm

  • LoopCombine4.xlsx
    27.9 KB · Đọc: 26
Lặp tổng quát cho bảng kết quả có số cột bất kỳ (1):
Theo gợi ý của @excel_lv1.5 ở bài 3, sửa code bài 2 trong file đính kèm bài 2 (LoopCombine2.xlsx), áp dụng cho số cột kết quả tùy ý.
Bước 1:
Thêm bảng tên cột mong muốn của kết quả, thay cho tất cả tên cột cần chuyển thành dòng. Có thể tạo tay hay lấy từ sheet cũng được.

Vòng lặp này không dùng List.Transform mà dùng List.Accumulate
Tôi góp vui cũng dùng 2 vòng lặp nhưng dùng chút thủ thuật để tách rời 2 vòng lặp nên số lượng vòng lặp sẽ ít, vòng lặp 2 chỉ là tạo một cái List và rename 1 lần duy nhất cho các cột
PHP:
let
    lst_header={"Chi nhánh","Doanh thu","Tiền lương","Lợi nhuận"},
    ncol=List.Count(lst_header),
    AllData = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    ColumnList=Table.ColumnNames(AllData),
    TableList={0..List.Count(ColumnList)/ncol-1},
    Append_Query=Table.Combine(
          List.Transform(TableList,(i)=>
             let
               tbl=Table.SelectColumns(AllData,List.Range(ColumnList,i*ncol,ncol)),
               tbl2=Table.Skip(Table.DemoteHeaders(tbl),1),
               tbl_res=Table.AddColumn(tbl2, "Tháng", each Number.ToText(i+1,"Th 00"),Text.Type)
            in
               tbl_res )
   ),
    Renamed = Table.RenameColumns(Append_Query,List.Transform({0..ncol-1},(i)=>{Number.ToText(i+1,"Column0"),lst_header{i}}))
in
    Renamed
 
Chỉnh sửa lần cuối bởi điều hành viên:
Tôi góp vui cũng dùng 2 vòng lặp nhưng dùng chút thủ thuật để tách rời 2 vòng lặp nên số lượng vòng lặp sẽ ít, vòng lặp 2 chỉ là tạo một cái List và rename 1 lần duy nhất cho các cột
Cám ơn bạn, tôi học thêm được 1 thủ thuật, và thêm hàm Number.ToText (tôi dùng Text.From nên không có tham số định dạng).
Ở bài trước dù tốn vòng lặp nhưng lại học được vòng lặp List.Accumulate.
Nói chung mọi thứ phải vọc vào mới học được, chứ công việc của tôi không liên quan, mà cũng đã nghỉ hưu rồi.

PHP:
let
    AllData = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    n= List.Count(ResultColumnList)-1,
    DataColumnsList =Table.ColumnNames(AllData),
    DataColumnsCount = List.Count(DataColumnsList),
    TableList={1..(DataColumnsCount - 1)/n},
    ColNumList ={0..n},
    
    Source = List.Transform(TableList, (i) =>
    let
        ColNameListi = List.Transform(ColNumList, (t) =>
        let
            ColNum = if t = 0 then 0 else (i-1) * n + t, 
            ColName = DataColumnsList{ColNum}
        in ColName),
        Source0 = Table.SelectColumns(AllData,ColNameListi),
        Source1 = Table.AddColumn(Source0, "Tháng", each Number.ToText(TableList{i-1},"Th 00")),
        ColRList = Table.ColumnNames(Source1),
        Result0= Table.RenameColumns(Source1, List.Transform(ColNumList,(m) => {ColRList{m},ResultColumnList{m}}))
    in Result0),
    SourceTable=Table.FromList(Source, Splitter.SplitByNothing()),
    ColList = Table.ColumnNames(SourceTable[Column1]{0}),
    Result = Table.SelectRows(Table.ExpandTableColumn(SourceTable,"Column1",ColList),each [Chi nhánh] <> null and [Doanh thu] > 0)
in
    Result
 
Lần chỉnh sửa cuối:
Hi anh, chị
Xin phép em hỏi trong chủ đề này, nếu vi phạm xóa bài giúp em
Em có bảng data như file đính kèm, hiện tại em đang dùng Xlookup để dò tìm đơn giá theo từng công đoạn của từng size, Mặt hàng sang sheet BC.
Cho em hỏi mình có thể dùng Power query để báo cáo không ạ.
Trân trọng cảm ơn
 

File đính kèm

  • GPE.xlsx
    103.5 KB · Đọc: 14
Hi anh, chị
Xin phép em hỏi trong chủ đề này, nếu vi phạm xóa bài giúp em
Em có bảng data như file đính kèm, hiện tại em đang dùng Xlookup để dò tìm đơn giá theo từng công đoạn của từng size, Mặt hàng sang sheet BC.
Cho em hỏi mình có thể dùng Power query để báo cáo không ạ.
Trân trọng cảm ơn
Có phải dạng nó thế này không nhỉ?
Chỗ group tôi đang để "average"
 

File đính kèm

  • GPE._10-11.xlsx
    135.7 KB · Đọc: 21
1. Phần này tách cột vậy thì Ok rồi, nhưng thiếu tên mặt hàng ạ
1636591163472.png

2. Phần này thì em muốn group Bộ phận, công đoạn lại
1636591230482.png
Em cảm ơn nhiều
 
1. Phần này tách cột vậy thì Ok rồi, nhưng thiếu tên mặt hàng ạ
PHP:
let
    Source = Excel.CurrentWorkbook(){[Name="tbl_CongDoanChiTiet"]}[Content],
    SelectColumn = Table.SelectColumns(Source,{"MAT_HANG","Size","BO_PHAN","CONG_DOAN","DON_GIA_NEN"}),
    AddItem = Table.AddColumn(SelectColumn, "Custom", each [MAT_HANG] & "/ " & [Size]),
    Groupedby = Table.Group(AddItem, { "BO_PHAN", "CONG_DOAN", "Custom"}, {{"Don Gia nen",
    each List.Average([DON_GIA_NEN]), type nullable number}}),
    PivotedColumn = Table.Pivot(Groupedby, List.Distinct(Groupedby[Custom]), "Custom", "Don Gia nen", List.Sum)
in
    PivotedColumn

1636645384008.png
 
PHP:
let
    Source = Excel.CurrentWorkbook(){[Name="tbl_CongDoanChiTiet"]}[Content],
    SelectColumn = Table.SelectColumns(Source,{"MAT_HANG","Size","BO_PHAN","CONG_DOAN","DON_GIA_NEN"}),
    AddItem = Table.AddColumn(SelectColumn, "Custom", each [MAT_HANG] & "/ " & [Size]),
    Groupedby = Table.Group(AddItem, { "BO_PHAN", "CONG_DOAN", "Custom"}, {{"Don Gia nen",
    each List.Average([DON_GIA_NEN]), type nullable number}}),
    PivotedColumn = Table.Pivot(Groupedby, List.Distinct(Groupedby[Custom]), "Custom", "Don Gia nen", List.Sum)
in
    PivotedColumn

View attachment 269048
Dạ em cảm ơn nhiều
 
Web KT
Back
Top Bottom