สิ่งที่เข้าใจยากที่สุดในสูตร DAX [Context Transition]

คุณเคยใช้สูตร DAX ใน Power BI หรือ Power Pivot ไหมครับ?
ถ้าเคย คุณคิดว่าสิ่งที่เข้าใจยากที่สุดคืออะไรครับ?

ติ๊กต่อก ติ๊กต่อก…
.
.
บางคนอาจตอบว่า จำนวนฟังก์ชัน เพราะ DAX มีฟังก์ชันเยอะมาก เท่าที่มีตอนนี้ก็ไม่น่าจะน้อยกว่า 500 แถมมีแต่จะเพิ่มขึ้น (โห !)

บางคนอาจตอบว่า วิธีการเขียนสูตร เพราะ DAX เป็นภาษาแบบ function base เวลาเขียนต้องซ้อนกันไปซ้อนกันมา ไม่ใช่เขียนเป็นบรรทัดๆเหมือน VBA ถ้าเจอเคสซับซ้อนอาจต้องเขียนซ้อนกัน 10 ชั้น (บ้าไปแล้ว !)

แต่สำหรับผม สิ่งที่เข้าใจยากที่สุดคือ Context Transition

Context Transition คืออะไร?
ถ้าแปลตรงตัวตามศัพท์ภาษาอังกฤษ Context Transition คือ การเคลื่อนที่ของบริบท
ฟังแล้วอาจงงๆ
งั้นขอแปลตามความเข้าใจดีกว่า
Context Transition แปลว่า ผลกระทบของบริบท
นั่นคือ ถ้าสร้างตาราง (หรือกราฟ) ขึ้นมา ข้อมูลแต่ละบรรทัดของตารางจะมีผลกระทบต่อการคำนวณ Measure หรือ Calculated Column หรือไม่
หรือ ผลลัพธ์การคำนวณของแต่ละบรรทัดจะเหมือนหรือต่างกัน
ถ้าเหมือนกัน เรียกว่า ไม่มี context transition (ไม่มีผลกระทบของบริบท)
ถ้าต่างกัน เรียกว่า มี context transition (มีผลกระทบของบริบท)
อธิบายแล้วก็ยังดูงงใช่ไหมครับ
เพื่อให้เข้าใจง่ายขึ้น ขออธิบายด้วยตัวอย่างนี้ครับ ^__^

(หมายเหตุ: เทคนิคในบทความนี้ใช้ได้ทั้งใน Power Pivot และ Power BI แต่เพื่อให้เข้าใจง่าย ขออธิบายด้วย Power Pivot โดยใช้ Excel ใน Office 365)

สมมติบริษัทมีสินค้า 5 ตัว ดังนี้

ตั้งชื่อตารางนี้ว่า Products
ถือเป็น Product Master Data ของสินค้าทั้งหมด
นำตารางนี้เข้าไปใน Power Pivot โดยคลิกเซลล์ใดๆในตารางแล้วไปที่ Power Pivot/ Add To Data Model

จะพบว่ามีหน้าต่างของ Power Pivot แสดงขึ้นมา และได้ผลลัพธ์หน้าตาแบบนี้

สมมติบริษัทมีบันทึกการขายของแต่ละวัน ดังนี้

ตั้งชื่อตารางนี้ว่า Orders
นำเข้าไปใน Power Pivot เช่นกัน (Power Pivot/ Add To Data Model)
ได้ผลลัพธ์หน้าตาแบบนี้

สร้างความสัมพันธ์ของทั้งสองตารางโดยใช้คอลัมน์ ProdCode เป็นตัวเชื่อม
ได้หน้าตาความสัมพันธ์แบบนี้

สร้าง Pivot Table จาก Data Model เพื่อสรุปว่ายอดขายของสินค้าแต่ละตัวเป็นอย่างไร

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

นี่คือคอนเซ็ปต์ง่ายๆของ Power Pivot และ Data Model ซึ่งหลายคนน่าจะรู้อยู่แล้ว
ดูเผินๆก็ไม่น่าจะมีอะไรใช่ไหมครับ
คราวนี้ ถ้ามีคนถามว่า อยากเพิ่มคอลัมน์แสดงว่าสินค้าใดขายดีที่สุดตามลำดับ 1-5 โดยยังต้องเรียงข้อมูลเป็น Product A – Product E เหมือนเดิม
ทำไงดีครับ?

ติ๊กต่อก ติ๊กต่อก …
.
.
หลายคนน่าจะนึกถึงฟังก์ชันของ DAX ที่ชื่อว่า RANKX ซึ่งเป็นฟังก์ชันที่ถูกออกแบบมาเพื่อการนี้โดยเฉพาะ
สำหรับบางคนที่ยังไม่รู้จักฟังก์ชัน RANKX มาทำความรู้จักกันนิดนึงครับ ^_^
RANKX มีโครงสร้างง่ายๆว่า

RANKX( table, expression )

table คือ ตารางที่ใช้เป็นฐานสำหรับการเรียงลำดับข้อมูล
expression คือ สูตรคำนวณสำหรับการสร้างคอลัมน์เสมือนขึ้นมาใน table เพื่อใช้เรียงลำดับข้อมูล

สำหรับเคสนี้ ถ้าคิดแบบง่ายๆก็ใช้ฟังก์ชัน RANKX หาคำตอบตรงๆ โดยสร้าง Measure ขึ้นมา (Power Pivot/ Measures/ New Measure)

ตั้งชื่อว่า Rank1
หรือเขียนสูตรเป็น
Rank1 = RANKX( ALL(Products), SUM(Orders[Volume] )

(ต้องใช้ฟังก์ชัน ALL ครอบตาราง Products ด้วย มิฉะนั้นโครงสร้างของ Pivot Table จะมีผลต่อการคำนวณ)

ผลลัพธ์ของ Measure Rank1 คือ
แอ่น แอน แอ๊น …

ได้อันดับ 1 ทั้งหมดเลย
เฮ้ย !

ทำไมล่ะ?
เพราะการเขียนสูตรแบบนี้ เสมือนสร้าง calculated column ขึ้นมาในตาราง Products ด้วยสูตร SUM(Orders[Volume])

จะได้ค่าเท่ากันหมดทุกบรรทัด (210) ซึ่งไม่ใช่ Volume ของแต่ละ ProdCode
(เพื่อให้เข้าใจแนวคิด ขออธิบายด้วยภาพด้านบน แต่ในทางปฏิบัติจะไม่มีการสร้างคอลัมน์ Volume ขึ้นมา)
หรือพูดง่ายๆก็คือ การที่บรรทัดนั้นๆเป็น Product A, Product B, Product C ไม่ส่งผลกระทบใดๆต่อการคำนวณเลย
บริบทของความเป็น Product A, Product B ไม่ส่งผลกระทบต่อการคำนวณ
เราเรียกการไม่ส่งผลกระทบต่อการคำนวณนี้ว่า ไม่มี context transition

แล่วถ้าอยากให้บริบทของความเป็น Product A, Product B มีผลกระทบล่ะ?
ถ้าอยากให้บริบทของความเป็น Product A, Product B ของบรรทัดใดๆมีผลกระทบต่อการคำนวณด้วย เทคนิคที่ง่ายที่สุดคือทำเป็น Measure

นั่นคือทำ SUM(Orders[Volume]) เป็น Measure
หรือก็คือสร้าง Measure ใหม่ชื่อ TotalVolume
กำหนดสูตรเป็น
TotalVolume = SUM(Orders[Volume])

แล้วปรับสูตร RANKX โดยใช้ [TotalVolume] แทน SUM(Orders[Volume])
เช่น สร้าง Measure ใหม่ชื่อ Rank2
เขียนสูตรเป็น

Rank2 = RANKX( ALL(Products), [TotalVolume] )

ได้ผลลัพธ์แบบที่ต้องการแล้ว !!

มีใครแปลกใจเหมือนผมไหมครับ?
ทั้งที่ [TotalVolume] มีค่าเป็น SUM(Orders[Volume]) แท้ๆ แค่เปลี่ยนจาก
=RANKX( ALL(Products), SUM(Orders[Volume]) )
เป็น
=RANKX( ALL(Products), [TotalVolume] )
มันจะต่างกันได้ยังไง?

มันต่างกันเพราะ การทำเป็น Measure ทำให้บริบทของความเป็น Product A, Product B, Product C ของแต่ละบรรทัดมีผลกระทบต่อการคำนวณ
หรือมี context transition
ในขณะที่ SUM( Orders[Volume] ) ไม่มี context transition

อ๋อ !.. หรือยังครับ ^_^

อธิบายในอีกมุมนึงก็คือ เสมือนสร้าง Calculated Column ชื่อ TotalVolume
ผลลัพธ์ที่ได้จะแตกต่างจาก SUM(Orders[Volume])

ฟังก์ชัน RANKX ( Products, [TotalVolume] ) คือการสร้างคอลัมน์เสมือน ([TotalVolume]) ขึ้นมาในตาราง Products (ไม่ได้สร้างจริง) แล้วส่งค่ากลับมาว่าตัวเลขในคอลัมน์นี้มีค่าเป็นอันดับที่เท่าใด

อนึ่ง: เทคนิคการทำให้มี context transition อีกวิธีหนึ่งก็คือ ครอบด้วยฟังก์ชัน CALCULATE
หรือจากเคสนี้ อาจเขียนสูตรเป็น

RANKX( ALL(Products), CALCULATE( SUM(Orders[Volume]) ) )

ได้ผลลัพธ์เท่ากันเป๊ะ !

แต่การใช้ฟังก์ชัน CALCULATE ทำให้สูตรยาว ใช้เป็น Measure น่าจะง่ายกว่า
จริงไหมครับ ^_^

อนึ่ง 2: ถ้าต้องการแสดงอันดับของ ProdGroup ต้องสร้าง Measure ใหม่
เพราะ ProdGroup มี 3 ระดับ (Gold, Platinum, Silver) แต่ ProdName มี 5 ระดับ ไม่สามารถใช้ Measure เดียวกันได้
เช่น สร้าง Measure ชื่อ RankProdGroup
แล้วปรับ table ใน RANKX จาก ALL(Products) เป็น ALL(Products[ProdGroup])

=RANKX( ALL(Products[ProdGroup]), [TotalVolume] )

การใช้สูตร DAX กับฟังก์ชันตระกูล X จำเป็นอย่างยิ่งที่จะต้องเข้าใจคอนเซ็ปต์ context transition ไม่ว่าจะเป็น
– SUMX : สร้างคอลัมน์เสมือนแล้วรวมเข้าด้วยกัน
– COUNTX : สร้างคอลัมน์เสมือนแล้วนับ
– AVERAGEX : สร้างคอลัมน์เสมือนแล้วหาค่าเฉลี่ย
– MAXX : สร้างคอลัมน์เสมือนแล้วหาค่ามากสุด
– MINX : สร้างคอลัมน์เสมือนแล้วหาค่าน้อยสุด
– CONCATENATEX : สร้างคอลัมน์เสมือนแล้วเชื่อมข้อมูลในคอลัมน์นั้นเข้าด้วยกัน
– RANKX : สร้างคอลัมน์เสมือนแล้วหาลำดับ
เพราะถ้าไม่เข้าใจ ผลลัพธ์จะผิด และไม่มีทางรู้เลยว่าผิดตรงไหน …

อธิบายแล้วก็ยังฟังยากใช่ไหมครับ
ใช่ครับ context transition เป็นเรื่องที่เข้าใจยากมาก
ในมุมมองของผม เรื่องนี้เข้าใจยากที่สุดแล้ว
และการจะเข้าใจได้ต้องมีพื้นฐานการใช้สูตร DAX ระดับหนึ่ง
(ต้องเข้าใจเรื่อง Data Model, Calculated Column, Implicit Measure, Explicit Measure, Row Context, Filter Context, DAX Syntax เสียก่อน)

ผมเองก็เพิ่งเข้าใจเรื่องนี้ไม่นาน กลัวว่าถ้าเวลาผ่านไปอาจจะลืม งั้นก็เขียนเป็นบทความเลยละกัน
บันทึกสิ่งที่ตัวเองเข้าใจเอาไว้ ถ้าวันไหนลืม ก็กลับมาอ่านทบทวนได้
เขียนแล้วก็แชร์ให้คนอื่นอ่านด้วยดีกว่า เผื่ออาจเป็นประโยชน์กับหลายๆคนครับ ^__^

อนึ่ง 3: เคสนี้ถ้าจะทำให้ถูกต้อง ตัวเลขทุกตัวใน Values ของ Pivot Table ต้องเป็น Measure
หรือไม่ควรหาผลรวมจากการดึงคอลัมน์ Volume มาใส่ (เป็น Sum of Volume) แต่ควรใช้ Measure ชื่อ [TotalVolume] แทน

ทำไมน่ะหรือครับ?
เนื่องจากบทความนี้ยาว(มาก)แล้ว ถ้าจะอธิบายความแตกต่างของการคำนวณด้วย Sum of Volume (ชื่อทางเทคนิคคือ Implicit Measure) กับการใช้ Measure (ชื่อทางเทคนิคคือ Explicit Measure) จะยาววว..ขึ้นไปอีก
งั้นขอยกยอดไปอธิบายในบทความอื่นละกันนะครับ ^__^

สำหรับใครที่สนใจไฟล์ตัวอย่างของบทความนี้ สามารถดาวน์โหลดเพื่อศึกษาเพิ่มเติมได้เลยครับ

หากคุณชอบบทความแนวนี้ สามารถอัพเดตบทความใหม่ๆโดยคลิก Like เฟสบุ๊คแฟนเพจ วิศวกรรีพอร์ต หรือคลิก ที่นี่

อย่าลืมแชร์ให้เพื่อนอ่านเพื่อเป็นกำลังใจให้คนเขียนด้วยนะครับ ^__^

Credit: https://www.sqlbi.com/


วิศวกรรีพอร์ต

คนธรรมดาผู้มีประสบการณ์ทำงานหลากหลายตำแหน่ง คลุกคลีกับการทำรีพอร์ตมาโดยตลอด สุดท้ายค้นพบแนวทางของตัวเอง จึงอยากแบ่งปันเคล็ดลับและประสบการณ์ให้กับผู้สนใจ

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.