RELATED คืนค่า scalar value (ค่าเดี่ยว) จากคอลัมน์ในตารางที่มีความสัมพันธ์แบบ many-to-one โดยอาศัย relationship ที่กำหนดไว้ในโมเดล ฟังก์ชันนี้ต้องการ row context และสามารถ traverse relationship chain ข้ามหลายขั้นได้ตราบใดที่ทุก relationship อยู่ในทิศทางเดียวกัน
| อาร์กิวเมนต์ | ชนิด | คำอธิบาย |
|---|---|---|
| column | column | คอลัมน์ที่ต้องการดึงค่า ต้องอยู่ในตารางที่มี relationship แบบ many-to-one กับตารางปัจจุบัน (ตารางที่ดึงต้องอยู่ฝั่ง One) รูปแบบ: Table[Column] |
RELATED เป็นฟังก์ชันพื้นฐานที่สำคัญใน DAX สำหรับการดึงค่าจากตารางที่มีความสัมพันธ์แบบ many-to-one (จากตารางข้อมูลฝั่ง “Many” ไปยังตารางมาสเตอร์ฝั่ง “One”) โดยอาศัยความสัมพันธ์ (relationship) ที่ถูกกำหนดไว้ในโมเดลข้อมูล ฟังก์ชันนี้จะคืนค่าเป็น scalar value (ค่าเดี่ยว) จากคอลัมน์ที่ระบุในตารางที่เกี่ยวข้อง ซึ่งเป็นเครื่องมือหลักที่ใช้ใน calculated column และ iterator function เพื่อ enrich ข้อมูลจากตารางมาสเตอร์มาใช้งาน
การทำงานของ RELATED ต้องอาศัย row context (บริบทของแถว) ซึ่งหมายความว่าฟังก์ชันนี้ทำงานได้ใน calculated column หรือภายใน iterator function เช่น SUMX, FILTER เท่านั้น เพราะต้องมีแถวปัจจุบันที่ชัดเจนเพื่อให้ DAX สามารถ traverse (เดินตาม) relationship ไปยังตารางที่เกี่ยวข้อง และดึงค่าที่ตรงกับ foreign key ของแถวปัจจุบันกลับมาได้อย่างถูกต้อง ตัวอย่างเช่น ในตาราง Sales ที่มี ProductKey คุณสามารถใช้ RELATED(Product[ProductName]) เพื่อดึงชื่อสินค้าที่ตรงกับ ProductKey ของแถวนั้นๆ มาใช้งานได้
จุดสำคัญที่ต้องเข้าใจคือ row context does not propagate through relationships โดยอัตโนมัติ นั่นคือเมื่อคุณกำลัง iterate แต่ละแถวของตาราง Sales คุณไม่สามารถ reference คอลัมน์จากตาราง Product ได้โดยตรง (เช่น Product[ProductName]) เพราะ row context ไม่ได้ “ไหล” ผ่าน relationship ไปยังตารางอื่น RELATED จึงเป็นเครื่องมือที่ทำหน้าที่ เปิดช่องทาง ให้คุณสามารถเข้าถึงข้อมูลในตารางที่เกี่ยวข้องได้ โดยอาศัยการเดินตาม relationship chain จาก foreign key ในตารางปัจจุบันไปยัง primary key ในตารางเป้าหมาย
RELATED สามารถ traverse relationship chain ข้ามหลายขั้นได้ ตราบใดที่ทุก relationship อยู่ในทิศทาง many-to-one เดียวกัน ตัวอย่างเช่น หากมีตาราง Sales → Product → Category คุณสามารถใช้ RELATED(Category[CategoryName]) ใน calculated column ของตาราง Sales ได้โดยตรง โดย DAX จะเดินผ่าน Sales → Product → Category โดยอัตโนมัติ แต่หากมี relationship ใดขาดหายหรือมี direction ผิดทิศทาง RELATED จะเกิด error ทันที
ความแตกต่างหลักคือ ทิศทางการ traverse relationship และ ชนิดของค่าที่คืนกลับ:
RELATEDTABLE เป็น alias ของ CALCULATETABLE ที่ทำ context transition และสามารถข้าม limited relationship ได้ ในขณะที่ RELATED ต้องการ regular relationship เท่านั้น (ไม่สามารถทำงานข้าม DirectQuery boundary ที่เป็น limited relationship ได้)
Calculated Column: ใช้กรณีที่ต้องการ enrich ข้อมูลถาวรในตาราง เช่น เพิ่มคอลัมน์ชื่อสินค้า, หมวดหมู่, ต้นทุนต่อหน่วย โดย RELATED จะ evaluate ครั้งเดียวเมื่อโหลดข้อมูล และเก็บผลลัพธ์ไว้ในโมเดล ข้อเสียคือจะเพิ่มขนาดไฟล์โมเดล หากมี calculated column จำนวนมากควรพิจารณาทำใน ETL หรือใช้ใน measure แทน
Iterator Function (SUMX, FILTER): ใช้กรณีที่ต้องการคำนวณแบบ dynamic ใน measure เช่น คำนวณต้นทุนขาย (COGS) โดยใช้ SUMX วน Sales แต่ละแถว แล้วดึงต้นทุนต่อหน่วยจาก Product มาคูณกับจำนวน วิธีนี้ไม่เพิ่มขนาดโมเดล และสามารถตอบสนอง filter context ได้อย่างยืดหยุ่น แนะนำให้ใช้วิธีนี้มากกว่า calculated column เมื่อเป็นไปได้
⚠️ Calculated column ที่ใช้ RELATED มาก: หาก calculated column มีจำนวนมากและใช้ RELATED หลายครั้ง จะเพิ่มขนาดโมเดลและหน่วยความจำที่ใช้ ควรพิจารณาทำการ enrich ข้อมูลใน Power Query หรือใช้ใน measure แทน
⚠️ Limited Relationship: RELATED ไม่สามารถทำงานกับ limited relationship ได้ (เช่น relationship ที่ข้าม DirectQuery boundary) ถ้าต้องการ traverse limited relationship ให้ใช้ LOOKUPVALUE หรือ RELATEDTABLE แทน
💡 RELATED vs LOOKUPVALUE: RELATED เร็วกว่า LOOKUPVALUE มาก เพราะอาศัย relationship ที่ถูก optimize ไว้แล้วในโมเดล ใช้ RELATED เสมอเมื่อมี relationship อยู่แล้ว และใช้ LOOKUPVALUE เฉพาะกรณีที่ไม่มี relationship หรือต้องการ lookup ด้วยเงื่อนไขที่ซับซ้อนกว่า
ดึงข้อมูลจากตารางมาสเตอร์ (Product, Customer, Category) มาเติมใน fact table (Sales, Orders) เช่น ดึงชื่อสินค้า หมวดหมู่ ต้นทุนต่อหน่วย ประเภทลูกค้า เพื่อให้สามารถใช้งานโดยตรงในตารางข้อมูล
ใช้ SUMX iterate แต่ละแถวของ Sales แล้วดึงต้นทุนต่อหน่วยจาก Product มาคูณกับจำนวนที่ขาย เพื่อคำนวณต้นทุนขายรวม (Cost of Goods Sold)
ใช้ RELATED ภายใน iterator function เช่น SUMX, AVERAGEX เพื่อดึงข้อมูลจากตารางที่เกี่ยวข้อง เช่น SUMX(Sales, Sales[Quantity] * RELATED(Product[Cost])) ซึ่งเป็น use case หลักของ RELATED ใน DAX
ดึงราคาล่าสุดจากตาราง Price List ที่มี relationship กับ Product มาใช้คำนวณรายได้หรือส่วนลดตามราคาปัจจุบัน
เดินผ่าน relationship หลายขั้น เช่น Sales → Product → Category → Division เพื่อดึงข้อมูลจาก Division โดยตรงโดยไม่ต้องผ่าน intermediate tables
สร้าง calculated column ในตาราง Sales โดย RELATED จะ traverse relationship จาก Sales[ProductKey] → Product[ProductKey] แล้วดึง ProductName กลับมา
ทุกแถวของ Sales จะได้ชื่อสินค้าที่ตรงกับ ProductKey ของแถวนั้นๆ ครับ
สร้าง calculated column ในตาราง Sales โดยดึง UnitPrice จาก Product มาคูณกับ Quantity ในแต่ละแถว
ผลลัพธ์จะเก็บไว้ในโมเดลและไม่เปลี่ยนแปลงตาม filter context ครับ ส่วนตัวผมแนะนำให้ใช้ใน measure แทนถ้าต้องการความยืดหยุ่นมากกว่า
SUMX iterate แต่ละแถวของ Sales (สร้าง row context) แล้วใน expression ของแต่ละแถว ใช้ RELATED ดึง StandardCost จาก Product มาคูณกับ Quantity
จากนั้น SUMX รวมผลลัพธ์ทั้งหมด วิธีนี้เจ๋งตรงที่ measure จะตอบสนอง filter context ได้ครับ เช่น กรองเฉพาะปี 2024 หรือเฉพาะหมวด Electronics ก็ได้
⚠️ Golden Rule: Filter Columns, Not Tables!
✅ วิธีถูก: ใช้ CALCULATE กับ Boolean Expression กรองที่ Product[Category] โดยตรง DAX Engine จะ optimize ให้อัตโนมัติ เร็วกว่า 100+ เท่า
❌ วิธีผิด: FILTER(Sales, RELATED(…)) ต้องวนลูปทุกแถวใน Fact Table (หลักแสน/ล้านแถว) และกรอง "expanded table" ที่อาจให้ผลผิด
ส่วนตัวผมเคยเห็นหลายคนใช้ FILTER + RELATED แบบผิดๆ เพราะคิดว่าต้องกรองที่ Fact Table ทั้งที่จริงๆ แค่กรองที่ Dimension Table ก็พอครับ! 😎
ตัวอย่างนี้แสดง multi-hop relationship traversal โดย RELATED ข้ามจาก Sales → Product → Category → Division (สมมติมี relationship chain นี้) แล้วดึง DivisionName มาใช้
VAR แรกสร้างตารางชั่วคราวที่เติมคอลัมน์ Division VAR ที่สองกรองเฉพาะ Division ที่ต้องการ สุดท้าย SUMX รวมยอดขาย
ส่วนตัวผมชอบใช้ VAR…RETURN แบบนี้ครับ เพราะอ่านและ debug ได้ง่ายกว่าเขียนซ้อนกันเยอะๆ
RELATED อาศัย relationship ที่กำหนดไว้ในโมเดล ทำงานเร็วกว่ามาก และใช้ได้เฉพาะทิศทาง many-to-one เท่านั้น
ส่วน LOOKUPVALUE ไม่ต้องการ relationship สามารถ lookup ด้วยเงื่อนไขใดก็ได้ (หลายคอลัมน์ หลายเงื่อนไข) แต่ช้ากว่าและต้องระวังกรณีพบหลายแถวที่ตรงกัน (จะเกิด error)
ส่วนตัวผมแนะนำให้ใช้ RELATED เสมอเมื่อมี relationship อยู่แล้วครับ ใช้ LOOKUPVALUE เฉพาะกรณีที่ไม่มี relationship จริงๆ
ความแตกต่างหลักคือทิศทางและชนิดข้อมูลที่คืนกลับครับ
RELATED ใช้ในตารางฝั่ง Many → ดึงค่าเดี่ยวจากตารางฝั่ง One (คืนค่า scalar) ส่วน RELATEDTABLE ใช้ในตารางฝั่ง One → ดึงตารางจากฝั่ง Many (คืนค่า table ที่มีหลายแถว)
ตัวอย่างง่ายๆ ใน Sales ใช้ RELATED(Product[ProductName]) จะได้ชื่อสินค้า 1 ชื่อ แต่ใน Product ใช้ RELATEDTABLE(Sales) จะได้ตารางที่มีทุกแถวของ Sales ที่ขายสินค้าชิ้นนั้น
ใช้ได้ครับ แต่ต้องใช้ภายใน iterator function เช่น SUMX, FILTER ที่สร้าง row context ให้
เพราะ RELATED ต้องการ row context ในการทำงาน ไม่สามารถใช้ RELATED โดยตรงใน measure ที่มีแต่ filter context ได้ (จะเกิด error ทันที)
ตัวอย่าง: SUMX(Sales, Sales[Quantity] * RELATED(Product[UnitPrice])) ทำงานได้ แต่ RELATED(Product[UnitPrice]) เฉพาะๆ ใน measure ทำงานไม่ได้นะครับ
ดูที่ลูกศรของ relationship line ใน Model view ครับ ลูกศรจะชี้จากตารางฝั่ง Many ไปยังตารางฝั่ง One
เช่น Sales → Product (Sales คือ Many, Product คือ One) หรือดูที่ multiplicity indicator (1 กับ *) เช่น Sales (*) → Product (1) หมายความว่า Sales มีหลายแถวต่อ 1 Product
ดังนั้นใช้ RELATED ใน Sales ได้ แต่ใช้ใน Product ไม่ได้ (ต้องใช้ RELATEDTABLE แทน)
RELATED ทำงานได้ทั้งสองทิศทางถ้า relationship ตั้งค่าเป็น bidirectional (Both) ครับ
แต่ส่วนตัวผมแนะนำให้ใช้อย่างระมัดระวัง เพราะ bidirectional relationship อาจทำให้เกิด ambiguity และ performance issues ใน DAX ได้
ควรใช้ single direction (One) เสมอเมื่อเป็นไปได้ และใช้ CROSSFILTER หรือ USERELATIONSHIP ใน CALCULATE เมื่อต้องการเปลี่ยนทิศทางชั่วคราว
ไม่ได้ครับ RELATED ต้องการ regular relationship เท่านั้น ไม่สามารถทำงานกับ limited relationship ได้
(เช่น relationship ที่ข้าม DirectQuery boundary หรือ composite model boundary)
หากต้องการ traverse limited relationship ให้ใช้ LOOKUPVALUE แทน หรือใช้ RELATEDTABLE ซึ่งสามารถข้าม limited relationship ได้ผ่าน context transition
มีผลกระทบต่อขนาดโมเดลและหน่วยความจำครับ แต่ไม่ค่อยกระทบต่อ query performance มากนัก
เพราะ calculated column ถูก evaluate ครั้งเดียวเมื่อ refresh ข้อมูล แต่ผลลัพธ์จะเก็บไว้ในโมเดล ทำให้เพิ่มขนาดไฟล์และ RAM ที่ใช้
หาก calculated column มีจำนวนมาก ส่วนตัวผมแนะนำให้ทำ data transformation ใน Power Query แทน หรือใช้ใน measure แทนเมื่อเป็นไปได้ (measure คำนวณตอน query time ไม่เพิ่มขนาดโมเดล)
ฟังก์ชันที่ผู้เขียนโยงไว้กับ RELATED จัดกลุ่มตามหมวด · ชี้ที่ชื่อใดชื่อหนึ่ง แล้วฟังก์ชันที่ใช้คู่กันจะมีจุดทอง
CROSSFILTER กำหนดทิศทางการกรองข้ามความสัมพันธ์ระหว่าง 2 คอลัมน์ชั่วคราว (มักใช้ใน CALCULATE) เพื่อควบคุมการไหลของตัวกรองหรือปิดการกรองข้ามในบางการคำนวณ
RELATEDTABLE ทำ context transition คืนตารางของแถวฝั่ง Many ที่เกี่ยวข้องกับแถวปัจจุบัน โดยใช้ Relationship ในโมเดล เป็นทางลัดของ CALCULATETABLE แต่ Microsoft ไม่แนะนำใช้แล้ว
USERELATIONSHIP ช่วยให้คุณใช้ relationship ที่ไม่ใช่ active หรือเปลี่ยน relationship ที่ใช้ในการคำนวณ เหมาะสำหรับสถานการณ์เช่น sales data ที่มีหลาย date columns (OrderDate, ShippingDate, DeliveryDate)
CALCULATETABLE evaluate table expression ภายใต้ filter context ที่ถูกปรับเปลี่ยน แล้วคืนค่าเป็น table (ตาราง) ซึ่งแตกต่างจาก CALCULATE ที่คืนค่าเป็น scalar value รองรับ filter arguments 3 รูปแบบ: Boolean expression, table expression, และ filter modifier functions (REMOVEFILTERS, ALL, KEEPFILTERS, USERELATIONSHIP, CROSSFILTER) มีพฤติกรรมเหมือน CALCULATE ในทุกแง่มุมของการจัดการ filter context รวมถึง context transition แต่เหมาะสำหรับการสร้าง intermediate table ที่ถูกกรองแล้วส่งต่อให้ iterator functions หรือใช้ใน calculated table มักมี performance ดีกว่า FILTER ใน simple filtering scenarios เพราะ DAX engine สามารถทำ cardinality estimation และ optimization ได้ดีกว่า
LOOKUP ค้นหาและดึงค่าจากเมทริกซ์ภาพ (visual matrix) ในการคำนวณภาพโดยการระบุเงื่อนไขการกรอง ใช้เฉพาะในการคำนวณภาพเท่านั้น
ADDCOLUMNS เป็นฟังก์ชัน table transformation ที่ใช้สำหรับเพิ่มคอลัมน์ใหม่ (Calculated Columns) เข้าไปในตารางที่มีอยู่เดิม โดยคำนวณค่าในแต่ละแถวผ่าน Row Context และส่งคืนตารางเดิมพร้อมคอลัมน์ใหม่ต่อท้าย
COALESCE คืนค่าแรกที่ไม่เป็น BLANK() จากหลายอาร์กิวเมนต์ เหมาะกับการกำหนดค่าเริ่มต้น (fallback) เช่น ใช้ค่า 0 เมื่อ Measure เป็น BLANK หรือเลือกค่าจากหลายแหล่งตามลำดับความสำคัญ
Comments
อีเมลของคุณจะไม่ถูกเผยแพร่