การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 1

การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน

หลายๆ คนน่าจะได้ลองใช้ Power Query ผ่านเครื่องมือบน User Interface ปกติไปจนเป็นแล้ว และหลายคนก็จะเริ่มรู้แล้วว่าเครื่องมือ User Interface ปกตินั้นมันไม่สามารถตอบโจทย์ในงานบางอย่างได้ เรียกได้ว่าเครื่องมือ Power Query ปกตินั้นสามารถตอบโจทย์งานทั่วไป 80% ได้แหละ แต่งานอีก 20% จะต้องมีการยุ่งกับ M Code ถึงจะสามารถทำได้สำเร็จ

ซึ่งผมได้เคยมีการเขียนบทความเรื่อง บันได 10 ขั้น เริ่มเรียนรู้ M Code ขุมพลังของ Power Query ซึ่งเป็นแนวทางในการศึกษา M Code ว่ามีประเด็นไหนที่ควรจะต้องศึกษาบ้าง รวมถึงมีการเขียน บทความรายละเอียดเรื่อง M Code โดยเริ่มจากพื้นฐาน ไปแล้วเช่นกัน

แต่ในบทความนี้ผมจะเน้นไปที่ M Code ที่เราจะสามารถนำมาใช้ในงานของเราได้จริงๆ ในกรณีที่พบได้บ่อย เช่น เราสามารถใช้ M Code ใช้แก้ปัญหาเมื่อข้อมูลอยู่ตำแหน่งไม่แน่นอนได้ ทุกคนจะได้เห็นภาพมากขึ้นว่าเราจะเรียนรู้ M Code ไปทำไมกัน??

ปัญหาของ Remove Top Rows แบบปกติ

ปกติแล้ว ถ้าเรามีข้อมูลที่ต้องการอยู่ในแถวที่ไม่ใช่แถวแรกสุด เราก็ต้องทำการ Remove Top Rows ก่อน ซึ่งจะต้องระบุจำนวนแถวของข้อมูลด้วย เช่น ตามในรูปต้องเอาออก 3 แถว

การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 2

สมมติเราทำให้ MyTable เป็นตัวแปรที่เก็บตารางต้นฉบับก่อนจะตัดบรรทัดไว้ โปรแกรมก็จะเรียกใช้ฟังก์ชัน Table.Skip ให้

Table.Skip(MyTable,3)

แต่วิธีนี้จะ Work ได้ก็ต่อเมื่อ “ข้อมูลที่เราต้องการต้องอยู่แถว 4 อย่างแน่นอน” จริงมั้ยครับ?

และจะเกิดอะไรขึ้นถ้าข้อมูลที่เราจะต้องเจอ “มันอาจจะอยู่ที่แถวอื่นก็ได้” แบบนี้เราก็ต้องใช้ M Code มาช่วยแล้วล่ะ

หลักการคิดคือ เราจะต้องหาวิธีที่จะรู้ให้ได้ว่าหัวตารางอยู่ในบรรทัดที่เท่าไหร่?

การจะรู้ว่าหัวตารางอยู่บรรทัดที่เท่าไหร่ได้นั้น ก็ขึ้นอยู่กับตัวข้อมูลด้วย ว่าข้อมูลมาแบบไหน ซึ่งผมจะเริ่มจากรณีที่มันง่ายก่อน

หัวตารางอยู่ในคอลัมน์ที่แน่นอน และเป็นคำที่แน่นอน

สมมติว่าเรารู้แน่ๆ ว่า หัวตารางเป็นคำว่าผลไม้ และอยู่ใน Column1 เสมอ

วิธีการหามีหลายวิธี

วิธีที่ 1 : ใช้ Add Index Column และการ Filter

วิธีแรกเป็นวิธีที่ไม่ได้ใช้ M Code อะไรมากมายนอกจากการรวบ Step บางอันเข้าด้วยกัน แนวทางคือ เราใช้การ Add Index Column เริ่มจากเลข 1 เข้าไป

การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 3

แล้ว Filter Column1 (ที่เรารู้แน่นอน) ให้เป็นคำว่าผลไม้

การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 4

จากนั้น Drilldown เข้าไปที่ Index ของบรรทัดแรก (เผื่อมีผลไม้หลายบรรทัด) ก็จะรู้แล้วว่าคำว่าผลไม้อยู่บรรทัดที่เท่าไหร่เป็นครั้งแรก

การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 5

จากนั้นเอาตัวแปร FruitRow ไปเรียกใช้ใน Table.Skip แต่อย่าลืมลบไปอีก 1 แถว

สรุปแล้วเขียน MCode แบบนี้

let
    Source = ...,
    MyTable = ตารางต้นฉบับที่เตรียมไว้,
    MyTable = #"Replaced Value",
    AddIndex = Table.AddIndexColumn(MyTable, "Index", 1, 1, Int64.Type),
    FilterRows = Table.SelectRows(AddIndex, each ([Column1] = "ผลไม้")),
    FruitRow = FilterRows{0}[Index],
    Result = Table.Skip(MyTable,FruitRow-1)
in
    Result

ได้ผลลัพธ์แบบนี้

การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 6

วิธีที่ 2 : ใช้ความรู้เรื่อง List มาช่วย

แบบนี้เราสามารถทำให้ข้อมูลใน Column1 เป็น List แล้วใช้ List.PositionOf เพื่อหาตำแหน่งของคำที่ต้องการได้ เช่น

ทำให้คอลัมน์แรกเป็น List ก่อนด้วยการคลิ๊กขวาที่คอลัมน์แรกแล้วกด Drill Down เพื่อให้อ้างอิงข้อมูลคอลัมน์ให้เป็น List

= MyTable[Column1]
การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 7

จากนั้นใช้ List.PositionOf ครอบเข้าไป ก็จะหาตำแหน่งของผลไม้ ออกมาเป็น index ที่ 3

= List.PositionOf(MyTable[Column1],"ผลไม้")

แล้วเราก็อาจเก็บค่านี้ไว้ในตัวแปร PositionFruit

แล้วเอาไปเรียกใช้ใน Table.Skip ได้เลย เช่น

= Table.Skip(MyTable,PositionFruit)

ก็จะได้ผลลัพธ์ที่ต้องการครับ สรุป M Code ทั้งหมดเป็นแบบนี้

let
    Source = ...,
    MyTable = ตารางต้นฉบับที่เตรียมไว้,
    PositionFruit = List.PositionOf(MyTable[Column1],"ผลไม้"),
    Result = Table.Skip(MyTable,PositionFruit)
in
    Result

หัวตารางอยู่ในคอลัมน์ที่ไม่แน่นอน แต่เป็นคำที่แน่นอน

สมมติว่าคราวนี้ผมรู้ว่าหัวตารางจะต้องมีคำว่า “เกรด” แต่ไม่รู้ว่ามันจะอยู่ในคอลัมน์ไหน แถวไหนกันแน่ เราจะมีวิธีการหาได้ยังไงมาดูกันครับ

เราสามารถใช้ Table.FindText มาช่วยได้ เช่น

= Table.FindText(MyTable,"เกรด")

ผลลัพธ์จะได้แบบนี้ (ถ้ามีคำว่าเกรดอยู่หลาย Row มันก็จะออกมาหลายบรรทัดได้)

การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 8

วิธีที่จะหาว่าคำว่าเกรดอยู่ในคอลัมน์ที่ชื่อว่าอะไรนั้นทำได้หลายวิธี เช่น

วิธีที่1 : ใช้การหา item ใน List

  • เอา Record แรกออกมาเลยใส่ {0} ต่อท้าย
  • จากนั้นเราแปลง Record Value เป็น List ด้วย Record.FieldValues
  • แล้วหาตำแหน่งของคำว่า เกรด ใน List ด้วย List.PositionOf

สรุปแล้วเขียนแบบนี้

= List.PositionOf(Record.FieldValues(Custom1),"เกรด")

จะได้ว่าอยู่ใน index ที่ 1 หรือ เป็น item ลำดับที่ 2 ซึ่งก็คือ Column2 นั่นเอง

หรือจะหาชื่อ Field แบบเป๊ะๆ เลยก็ได้ อาจใช้ Record.FieldNames มาช่วย ก็ได้

= Record.FieldNames(Custom1)
การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 9

ซึ่งเราเอาเลข Index จาก Custom2 มาใส่ เพื่อเอาชื่อคอลัมน์ออกมาก็ได้

การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 10

สรุปว่าเขียนแบบนี้

    Custom1 = Table.FindText(MyTable,"เกรด"){0},
    Custom2 = List.PositionOf(Record.FieldValues(Custom1),"เกรด"),
    Custom3 = Record.FieldNames(Custom1){Custom2}

วิธีที่ 2 : Transpose ตารางแล้ว Filter หา

หรือเราจะใช้อีกวิธีนึงคือ Demote Header แล้ว Transpose ตารางให้ออกมาเป็นแบบนี้

= Table.Transpose(Table.DemoteHeaders( Table.FindText(MyTable,"เกรด")))
การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 11

แล้ว Filter หาคำว่าเกรดใน Column ขวา (เพื่อให้ได้เกรดแถวแรกที่เจอก่อนที่จะ Transpose)

แค่นี้ก็จะรู้ชื่อคอลัมน์ว่าเป็น Column2 นั่นเอง

สรุปแล้วเขียนแบบนี้

    Custom1 = Table.Transpose(Table.DemoteHeaders( Table.FindText(MyTable,"เกรด"))),
    FilterRow = Table.SelectRows(Custom1, each ([Column2] = "เกรด")),
    Column1 = FilterRow{0}[Column1]

พอได้ชื่อคอลัมน์ที่แน่นอนแบบนี้ เราค่อยเอาไปหาตามเคสแรกอีกทีก็จะจบแล้ว

หัวตารางอยู่ในคอลัมน์ที่แน่นอน แต่เป็นคำที่ไม่แน่นอน

สมมติว่าเรารู้ว่าหัวตารางจะอยู่ใน Column1 แน่ๆ แต่ไม่รู้ว่าจะเป็นคำว่าอะไร ซึ่งเราจะต้องมี Logic เพิ่มเติม เช่น มันจะเป็น คำแรกหลังจากข้อมูลเป็นค่า null ตั้งแต่ 2 ตัวขึ้นไป

แบบนี้ก็สามารถใช้ MCode มาช่วยได้อยู่ แต่จะยากขึ้นกว่าเดิมเล็กน้อย

ในเคสนี้ผมจะใช้ Group By มาช่วย

การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 12

แต่จะทำตรงๆ ไม่ได้เพราะอาจเกิดปัญหาที่มันดันรวบ null ทุกตัวเข้าด้วยกันไปเลยแบบนี้

การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 13

เราเลยจะมีการแก้ Parameter เล็กน้อยให้มีการ GroupKind แบบ Local เพื่อให้ Group ขึ้นอยู่กับการเรียงลำดับต่อเนื่องด้วย เช่น

= Table.Group(MyTable, {"Column1"}, {{"Count", each Table.RowCount(_), Int64.Type}},GroupKind.Local)
การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 14

จากนั้นเราจะทำ Count สะสมเพิ่มอีกคอลัมน์ เพื่อที่จะได้รู้ว่า null ตัวแรกที่ติดต่อกัน 2 ค่าขึ้นไป มันคือบรรทัดที่เท่าไหร่กันแน่??

โดยสร้าง Index Column เพื่อช่วยกำหนดลำดับในการสะสมค่า แล้วเขียน Custom Column ใหม่เพื่อสร้าง Count แบบสะสมด้วยสูตรนี้

List.Sum(List.FirstN(AddIndex[Count],[Index]))
การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 15

จากนั้น Filter เลือก Column1 ที่เป็น null และ Column2 >=2

= Table.SelectRows(AddAccumCount, each [Column1] = null and [Count] >= 2)
การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 16

แล้วเลือกเอาแถวแรกออกมา จากนั้นเลือกเอา AccumCount จะได้เลข 3 ออกมาแบบนี้

การใช้ Power Query M Code จัดการข้อมูลที่อยู่ในตำแหน่งไม่แน่นอน 17

สรุปแล้วเขียนแบบนี้

let
    Source = ...,
    MyTable = ตารางต้นฉบับที่เตรียมไว้,
    GroupTable = Table.Group(MyTable, {"Column1"}, {{"Count", each Table.RowCount(_), Int64.Type}},GroupKind.Local),
    AddIndex = Table.AddIndexColumn(GroupTable, "Index", 1, 1, Int64.Type),
    AddAccumCount = Table.AddColumn(AddIndex, "AccumCount", each List.Sum(List.FirstN(AddIndex[Count],[Index]))),
    FilterRows = Table.SelectRows(AddAccumCount, each [Column1] = null and [Count] >= 2),
    PositionFruit = Table.FirstN(FilterRows,1){0}[AccumCount],
    Result = Table.Skip(MyTable,PositionFruit)
in
    Result

นี่เป็นแค่แนวทางที่เป็นไปได้

วิธีแก้ปัญหาใน Power Query นั้นสามารถแก้ผัญหาได้หลายวิธี ดังนั้นยังมีความเป็นไปได้อื่นๆ อีกมากมายที่จะช่วยแก้ปัญหาให้เราได้ และถ้าเรารู้ M Code มากขึ้นก็จะช่วยให้มีวิธีแก้ปัญหามากขึ้นไปด้วยนะครับ ดังนั้นอย่าลืมลองสำรวจหาและเรียนรู้ฟังก์ชันที่คุณยังไม่รู้จัด้วยนะครับ เผื่อชีวิตจะสบายขึ้นเยอะ