Thep Excel

CALCULATETABLE – Evaluate Table Expression ใน Modified Filter Context

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 ได้ดีกว่า

=CALCULATETABLE(<table>[, <filter>[, <filter>[, ...]]])

By ThepExcel AI Agent
19 December 2025

Function Metrics


Popularity
7/10

Difficulty
7/10

Usefulness
8/10

Syntax & Arguments

=CALCULATETABLE(<table>[, <filter>[, <filter>[, ...]]])

Argument Type Required Default Description
table table Yes Table expression ที่ต้องการ evaluate เช่น ชื่อตาราง (Sales, Products), VALUES(column), FILTER(table, condition), SUMMARIZE(…) จะถูกประเมินค่าเป็นขั้นตอนสุดท้ายหลังจากเตรียม filter context ใหม่เสร็จแล้ว ต้องเป็น expression ที่คืนค่าเป็น table ไม่สามารถใช้ scalar value ได้
filter boolean / table / filter modifier Optional ไม่มีตัวกรองเพิ่มเติม (ไม่บังคับ) เงื่อนไขการกรองข้อมูล รองรับ 3 รูปแบบ: (1) Boolean Expression เช่น Sales[Channel] = “Online” ซึ่งจะถูกแปลงเป็น FILTER(ALL(…)) อัตโนมัติและทำการ override filter เดิม ต้อง reference column จากตารางเดียว (2) Table Expression เช่น FILTER(Sales, Sales[Amount] > 1000) สำหรับเงื่อนไขซับซ้อนและยืดหยุ่นมากขึ้น (3) Filter Modifier Functions เช่น REMOVEFILTERS, ALL, ALLEXCEPT, KEEPFILTERS, USERELATIONSHIP, CROSSFILTER สำหรับควบคุม filter context อย่างละเอียด สามารถใส่หลาย filter ได้โดยจะรวมกันด้วย AND logic สำคัญมาก: filter arguments ทั้งหมดจะถูกประเมินค่าใน original context ก่อนที่จะเกิด context transition

How it works

เตรียม Intermediate Table สำหรับ Iterator Functions

ใช้ CALCULATETABLE เพื่อสร้าง table ที่ถูกกรองตามเงื่อนไขเฉพาะ แล้วส่งต่อให้ iterator functions เช่น SUMX, AVERAGEX, COUNTX เพื่อทำการคำนวณที่ซับซ้อน เหมาะกับการทำ conditional aggregation ที่ต้องการควบคุม filter context อย่างละเอียด

สร้าง Calculated Table ที่มีการกรองแบบ Dynamic

ใช้ CALCULATETABLE ใน calculated table definition เพื่อสร้าง table ที่ถูกกรองตามเงื่อนไขพิเศษ เช่น สร้าง table Customers ที่มียอดซื้อมากกว่า 10,000 โดยการกรองจะทำงานแบบ dynamic ตาม filter context ที่มีอยู่

Visual-Level Totals ด้วย ALLSELECTED

ใช้ CALCULATETABLE ร่วมกับ ALLSELECTED เพื่อสร้าง totals ที่เคารพ filter จาก slicers แต่ไม่รับผลกระทบจาก row/column context ภายใน visual เหมาะสำหรับการคำนวณเปอร์เซ็นต์ของยอดรวมใน matrix หรือ table visual

ใช้ Inactive Relationships ชั่วคราว (Role-Playing Dimensions)

ใช้ CALCULATETABLE ร่วมกับ USERELATIONSHIP เพื่อเปิดใช้งาน inactive relationship ชั่วคราวสำหรับการสร้าง table เช่น ใช้ ShipDate แทน OrderDate ในการกรองข้อมูล เหมาะกับ role-playing dimensions เช่น OrderDate, ShipDate, DueDate ที่เชื่อมกับ Date table เดียวกัน

Custom Date Tables สำหรับ Time Intelligence

ใช้ CALCULATETABLE เพื่อสร้าง custom date table ที่ถูกกรองตามช่วงเวลาเฉพาะ เช่น เฉพาะ business days หรือ fiscal calendar แล้วใช้ร่วมกับ time intelligence functions สำหรับการวิเคราะห์แบบ temporal

Examples

ตัวอย่างที่ 1: กรอง Table ด้วย Boolean Expression (Foundation)
// Data Model Context: // Sales table มี columns: SalesID, Channel, Amount, Quantity // มีข้อมูล 1,000 rows โดย Channel มีค่า "Online" และ "Retail" Online Sales…
CALCULATETABLE evaluate table Sales ภายใต้ filter context ที่ถูกปรับเปลี่ยนโดย Boolean expression Sales[Channel] = "Online" โดย DAX engine จะแปลง Boolean expression นี้เป็น FILTER(ALL(Sales[Channel]), Sales[Channel] = "Online") โดยอัตโนมัติ
.
ซึ่งหมายความว่าจะ override filter เดิมของ Channel ทั้งหมด (ถ้ามี) และเหลือเฉพาะ Online ผลลัพธ์ที่ได้คือ table ที่สามารถนำไปใช้ต่อกับฟังก์ชันอื่นได้ทันที
.
ส่วนตัวผมใช้ pattern นี้เป็น foundation สำหรับการกรองตารางบ่อยมากเลยครับ เช่น COUNTROWS(Online Sales Table) จะได้ 600 หรือส่งต่อให้ SUMX iterate แต่ละแถว 💡
DAX Formula:

// Data Model Context:
// Sales table มี columns: SalesID, Channel, Amount, Quantity
// มีข้อมูล 1,000 rows โดย Channel มีค่า "Online" และ "Retail"

Online Sales Table = 
CALCULATETABLE(
    Sales,
    Sales[Channel] = "Online"
)

// Result: Table ที่มีเฉพาะแถวที่ Channel = "Online"
// ถ้า Sales มี 600 rows ของ Online และ 400 rows ของ Retail
// ผลลัพธ์จะเป็น table ที่มี 600 rows (เฉพาะ Online)

Result:

Table ที่มีเฉพาะแถวที่ Sales[Channel] = "Online" (600 rows)

ตัวอย่างที่ 2: ส่ง Filtered Table ให้ SUMX คำนวณ (Practical)
// Data Model Context: // Sales table: SalesID, Channel, Amount, Quantity, Date // ตัวอย่างข้อมูล: // | Channel | Amount | Quantity | // | Online | 1500 | 3 | /…
ตัวอย่างนี้แสดง practical pattern ที่ใช้บ่อยมาก คือการใช้ VAR เพื่อเก็บ filtered table ไว้ในตัวแปร FilteredTable แล้วส่งต่อให้ SUMX iterate แต่ละแถว CALCULATETABLE ใช้สอง filter arguments (Online AND Amount > 1000) ซึ่ง DAX จะรวมกันด้วย AND logic
.
ทำให้ FilteredTable มีเฉพาะ rows ที่ผ่านทั้งสองเงื่อนไข จากนั้น SUMX iterate แต่ละแถวใน FilteredTable และคำนวณ Amount × Quantity แล้วรวมผลลัพธ์ทั้งหมด
.
ที่เจ๋งคือการใช้ VAR ทำให้โค้ดอ่านง่าย debug ง่าย และ efficient เพราะ FilteredTable ถูกคำนวณเพียงครั้งเดียว ส่วนตัวผมใช้ pattern นี้บ่อยมากครับ 😎
DAX Formula:

=// Data Model Context:
// Sales table: SalesID, Channel, Amount, Quantity, Date
// ตัวอย่างข้อมูล:
// | Channel | Amount | Quantity |
// | Online  | 1500   | 3        |
// | Online  | 800    | 2        | (ถูกกรองออกเพราะ Amount <= 1000)
// | Retail  | 2000   | 4        | (ถูกกรองออกเพราะ Channel != "Online")
// | Online  | 1200   | 5        |

High Value Online Sales = 
VAR FilteredTable = 
    CALCULATETABLE(
        Sales,
        Sales[Channel] = "Online",
        Sales[Amount] > 1000
    )
VAR TotalValue = 
    SUMX(
        FilteredTable,
        Sales[Amount] * Sales[Quantity]
    )
RETURN
    TotalValue

// FilteredTable จะมี 2 rows: (1500, 3) และ (1200, 5)
// SUMX คำนวณ: (1500 * 3) + (1200 * 5) = 4,500 + 6,000 = 10,500

Result:

10,500 (ผลรวมของ Amount × Quantity สำหรับ Online sales ที่ Amount > 1,000)

ตัวอย่างที่ 3: ใช้ REMOVEFILTERS ลบ Filter หลาย Columns (Advanced)
// Data Model Context: // Product table: ProductID, ProductName, Category, Color, Size // สมมติ user เลือก slicer: Category = "Electronics", Color = "Black", Si…
ตัวอย่างนี้แสดง advanced technique ในการควบคุม filter context อย่างละเอียด สมมติว่า user เลือก slicer Category = "Electronics", Color = "Black", Size = "Medium" โดยปกติ VALUES(Product[ProductName]) จะถูก filter ทั้งสามเงื่อนไข
.
แต่เราใช้ REMOVEFILTERS เพื่อลบ filter ของ Color และ Size ออก โดยยังคง filter ของ Category ไว้ ผลลัพธ์คือ CategoryProducts จะมีรายชื่อสินค้าทั้งหมดใน Electronics category (500 products) ไม่ว่า Color หรือ Size จะเป็นอะไร
.
Pattern นี้มีประโยชน์มากเมื่อต้องการนับหรือแสดงรายการทั้งหมดใน dimension ใดๆ โดยไม่สนใจ filter บางตัว ที่ต้องระวังคือการใช้ REMOVEFILTERS แทน ALL ทำให้โค้ดอ่านง่ายกว่าและเป็น modern DAX best practice ครับ 💡
DAX Formula:

// Data Model Context:
// Product table: ProductID, ProductName, Category, Color, Size
// สมมติ user เลือก slicer: Category = "Electronics", Color = "Black", Size = "Medium"
// Product table มี 500 products ใน Electronics category
// โดย 50 products เป็น Black, 30 products เป็น Medium
// แต่มี 500 products ทั้งหมดใน Electronics (ไม่สนใจ Color และ Size)

All Products By Category = 
VAR CategoryProducts = 
    CALCULATETABLE(
        VALUES(Product[ProductName]),
        REMOVEFILTERS(Product[Color]),
        REMOVEFILTERS(Product[Size])
    )
VAR ProductCount = 
    COUNTROWS(CategoryProducts)
RETURN
    ProductCount

// Result: 500 (จำนวน products ทั้งหมดใน Electronics category)
// ไม่ใช่ 50 หรือ 30 เพราะเราลบ filter ของ Color และ Size ออกแล้ว

Result:

500 (จำนวน products ทั้งหมดใน Category ปัจจุบัน โดยไม่สนใจ Color และ Size)

ตัวอย่างที่ 4: ALLSELECTED สำหรับ Visual-Level Percentage (Advanced Real-World)
// Data Model Context: // Sales table: SalesID, ProductID, Amount, Date // Matrix visual แสดง Amount by Product, มี slicer Year = 2024 // ข้อมูลตัวอย่างหลัง fil…
ตัวอย่างนี้แสดง real-world pattern ที่ใช้บ่อยมากใน business reporting คือการคำนวณเปอร์เซ็นต์ของ visual total CurrentValue คำนวณยอดขายปัจจุบันตามปกติซึ่งจะรับผลจาก filter ทั้งหมดรวมถึง row context (เช่น Product = "Laptop")
.
ส่วน VisualTotal ใช้ ALLSELECTED(Sales) เพื่อลบ filter ที่เกิดจาก row/column context ภายใน visual (เช่น Product filter จาก row) แต่ยังคง filter จาก slicers (Year = 2024) และ page filters ไว้ ทำให้ VisualTotal = 75,000 (ยอดรวมทั้งหมดที่แสดงใน visual)
.
ที่เจ๋งคือผลลัพธ์เป็นเปอร์เซ็นต์ที่เคารพ user selections จาก slicers แต่คำนวณเทียบกับยอดรวมที่แสดงใน visual ปัจจุบัน ส่วนตัวผมใช้ pattern นี้บ่อยมากสำหรับ matrix และ table visuals ครับ 😎
DAX Formula:

// Data Model Context:
// Sales table: SalesID, ProductID, Amount, Date
// Matrix visual แสดง Amount by Product, มี slicer Year = 2024
// ข้อมูลตัวอย่างหลัง filter Year = 2024:
// | Product  | Amount |
// | Laptop   | 50,000 |
// | Mouse    | 10,000 |
// | Keyboard | 15,000 |
// Visual Total: 75,000

% of Visual Total = 
VAR CurrentValue = SUM(Sales[Amount])
VAR VisualTotal = 
    CALCULATE(
        SUM(Sales[Amount]),
        ALLSELECTED(Sales)
    )
VAR Percentage = 
    DIVIDE(CurrentValue, VisualTotal, 0)
RETURN
    Percentage

// ใน row Laptop: CurrentValue = 50,000, VisualTotal = 75,000
// Percentage = 50,000 / 75,000 = 0.6667 (66.67%)
// ใน row Mouse: CurrentValue = 10,000, VisualTotal = 75,000
// Percentage = 10,000 / 75,000 = 0.1333 (13.33%)

Result:

Laptop: 66.67%, Mouse: 13.33%, Keyboard: 20.00% (เทียบกับยอดรวม 75,000 ที่ถูก filter ด้วย Year = 2024)

ตัวอย่างที่ 5: USERELATIONSHIP กับ Role-Playing Dimensions (Complex Real-World)
// Data Model Context: // Sales table: SalesID, OrderDate, ShipDate, Amount // Date table: Date, Year, Month, Quarter // Relationships: // - ACTIVE: Sales[Order…
ตัวอย่างนี้แสดง complex real-world scenario ที่พบบ่อยคือ role-playing dimensions โดยปกติ data model จะมี active relationship เพียง 1 relationship ระหว่างสองตาราง ในกรณีนี้ Sales[OrderDate] → Date[Date] เป็น active relationship
.
แต่บางครั้งเราต้องการใช้ inactive relationship (Sales[ShipDate] → Date[Date]) แทน การใช้ USERELATIONSHIP(Sales[ShipDate], Date[Date]) จะเปิดใช้งาน inactive relationship ชั่วคราว ทำให้ filter Date[Year] = 2024 ใช้ ShipDate แทน OrderDate
.
ผลลัพธ์คือ ShippedSales จะเป็น table ที่ถูกกรองเฉพาะ transactions ที่ ship ในปี 2024 ไม่ว่า order date จะเป็นเมื่อไหร่ Pattern นี้มีประโยชน์มากสำหรับการวิเคราะห์ที่ต้องการมุมมองแตกต่างกันของเวลา เช่น Order vs Ship vs Due Date ครับ 😎
DAX Formula:

=// Data Model Context:
// Sales table: SalesID, OrderDate, ShipDate, Amount
// Date table: Date, Year, Month, Quarter
// Relationships:
//   - ACTIVE: Sales[OrderDate] → Date[Date]
//   - INACTIVE: Sales[ShipDate] → Date[Date]
// ต้องการนับ transactions ที่ ship ในปี 2024 (ใช้ ShipDate ไม่ใช่ OrderDate)

Shipped Sales Count = 
VAR ShippedSales = 
    CALCULATETABLE(
        Sales,
        USERELATIONSHIP(Sales[ShipDate], 'Date'[Date]),
        'Date'[Year] = 2024
    )
VAR ShippedCount = 
    COUNTROWS(ShippedSales)
RETURN
    ShippedCount

// สมมติ Sales table มี:
// | OrderDate  | ShipDate   | Amount |
// | 2023-12-28 | 2024-01-05 | 1000   | ✓ นับ (ship ในปี 2024)
// | 2024-01-10 | 2024-01-15 | 1500   | ✓ นับ (ship ในปี 2024)
// | 2024-03-20 | 2024-04-01 | 2000   | ✓ นับ (ship ในปี 2024)
// | 2024-11-25 | 2025-01-03 | 1200   | ✗ ไม่นับ (ship ในปี 2025)
// Result: 3 transactions

Result:

3 (จำนวน transactions ที่ ship ในปี 2024 ตาม ShipDate)

ตัวอย่างที่ 6: KEEPFILTERS สำหรับ Intersect Behavior (Expert Pattern)
// Data Model Context: // Sales table: SalesID, Channel, Amount // User เลือก slicer Channel = "Retail" // มี measure ที่ต้องการ filter เฉพาะ "Online" // Patter…
ตัวอย่างนี้แสดง expert-level pattern ที่อธิบายความแตกต่างระหว่าง override และ intersect behavior โดยปกติ CALCULATETABLE จะ override filter เดิม หมายความว่าถ้า slicer เลือก Retail อยู่ และเรากรองด้วย Sales[Channel] = "Online" filter ใหม่จะเขียนทับ filter เดิม ทำให้ได้ Online sales เสมอ (600 rows) ไม่ว่า slicer จะเลือกอะไร
.
แต่ถ้าใช้ KEEPFILTERS พฤติกรรมจะเปลี่ยนเป็น intersect หมายความว่า filter ใหม่และ filter เดิมต้องเป็นจริงพร้อมกัน (AND logic) ในกรณีนี้ filter context มี Retail (จาก slicer) AND Online (จาก KEEPFILTERS) ซึ่งเป็นไปไม่ได้เพราะ Channel เป็นได้แค่ค่าเดียว ผลลัพธ์จึงเป็น 0 rows
.
เอาจริงๆ นะครับ ใช้ KEEPFILTERS เมื่อต้องการให้ filter ใหม่เคารพ filter เดิมและทำงานร่วมกันแบบ AND logic เหมาะกับ scenarios ที่ต้องการให้ measure รับผลจาก user selections เสมอครับ 💡
DAX Formula:

// Data Model Context:
// Sales table: SalesID, Channel, Amount
// User เลือก slicer Channel = "Retail"
// มี measure ที่ต้องการ filter เฉพาะ "Online"

// Pattern 1: ไม่ใช้ KEEPFILTERS (Override Behavior)
Online Sales Override = 
VAR FilteredTable = 
    CALCULATETABLE(
        Sales,
        Sales[Channel] = "Online"
    )
RETURN
    COUNTROWS(FilteredTable)
// Result: จำนวน Online sales (ไม่สนใจ slicer ที่เลือก Retail)
// ถ้า Online มี 600 rows → Result = 600

// Pattern 2: ใช้ KEEPFILTERS (Intersect Behavior)
Online Sales Intersect = 
VAR FilteredTable = 
    CALCULATETABLE(
        Sales,
        KEEPFILTERS(Sales[Channel] = "Online")
    )
RETURN
    COUNTROWS(FilteredTable)
// Result: Retail AND Online (impossible!) → Result = 0
// เพราะไม่มี row ไหนที่ Channel เป็นทั้ง Retail และ Online พร้อมกัน

Result:

Override: 600 rows, Intersect: 0 rows (เพราะ Retail AND Online เป็นไปไม่ได้)

FAQs

ความแตกต่างหลักระหว่าง CALCULATETABLE และ CALCULATE คืออะไร?

ความแตกต่างหลักคือ return type โดย CALCULATE คืนค่าเป็น scalar value (ตัวเลข, ข้อความ, หรือค่าเดี่ยวใดก็ตาม) ในขณะที่ CALCULATETABLE คืนค่าเป็น table (ตาราง)
.
อย่างไรก็ตาม ทั้งสองฟังก์ชันมีพฤติกรรมเหมือนกันในทุกแง่มุมของการจัดการ filter context รองรับ filter modifier functions เหมือนกัน ทำ context transition เหมือนกัน และมีลำดับการ evaluate filter arguments เหมือนกัน
.
ใช้ CALCULATE เมื่อต้องการ scalar result (เช่น Total Sales = CALCULATE(SUM(Sales[Amount]), …)) และใช้ CALCULATETABLE เมื่อต้องการ table result ที่จะส่งต่อให้ฟังก์ชันอื่น (เช่น SUMX(CALCULATETABLE(…), …)) หรือใช้ใน calculated table ครับ 😎

เมื่อไหร่ควรใช้ CALCULATETABLE แทน FILTER?

ใช้ CALCULATETABLE เมื่อต้องการปรับเปลี่ยน filter context หรือใช้ filter modifier functions (REMOVEFILTERS, ALL, KEEPFILTERS, USERELATIONSHIP) และมักจะมี performance ดีกว่า FILTER ใน simple filtering scenarios เพราะ DAX engine สามารถใช้ cardinality estimation และ optimization techniques ได้ดีกว่า
.
ส่วน FILTER เหมาะกับ complex row-by-row conditions ที่ต้องการ row context เช่น เงื่อนไขที่อ้างอิง measure (FILTER(Products, [Total Sales] > 10000)) หรือการคำนวณที่ซับซ้อนแบบทีละแถว
.
ส่วนตัวผมแนะนำให้ใช้ CALCULATETABLE เป็น first choice และเปลี่ยนไปใช้ FILTER เฉพาะเมื่อต้องการ row context จริงๆ ครับ 💡

ALLSELECTED ทำงานอย่างไรกับ CALCULATETABLE?

ALLSELECTED คืนค่า filter context ที่ “มองเห็นได้” ใน visual (visual-level context) โดยลบ filter ที่เกิดจาก row/column context ภายใน visual แต่ยังคง filter จาก slicers, page filters, และ report filters ไว้
.
เมื่อใช้กับ CALCULATETABLE จะได้ table ที่ถูกกรองตาม visual-level filters เหมาะสำหรับการคำนวณเปอร์เซ็นต์ของยอดรวม (percentage of total) ใน matrix หรือ table visual
.
ตัวอย่างเช่น CALCULATETABLE(Sales, ALLSELECTED(Sales)) ใช้ใน visual ที่มี slicer Year = 2024 จะได้ Sales table ที่ถูก filter ด้วย Year = 2024 แต่ไม่รับผลจาก row context ภายใน visual Pattern นี้ใช้บ่อยมากใน visual-level calculations ครับ 😎

Boolean Filter vs Table Expression ใน CALCULATETABLE แตกต่างกันอย่างไร?

Boolean Filter เช่น Sales[Channel] = “Online” จะถูกแปลงเป็น FILTER(ALL(Sales[Channel]), Sales[Channel] = “Online”) โดยอัตโนมัติ ทำให้เกิดพฤติกรรม override (เขียนทับ filter เดิมเสมอ) ข้อจำกัดคือต้อง reference column จากตารางเดียวเท่านั้น (ไม่สามารถใช้ Sales[Channel] = “Online” && Product[Category] = “Electronics” ได้)
.
ส่วน Table Expression เช่น FILTER(Sales, Sales[Amount] > 1000) ยืดหยุ่นกว่า สามารถใช้เงื่อนไขซับซ้อน reference หลายตารางพร้อมกัน (FILTER(Sales, Sales[Amount] > 1000 && RELATED(Product[Category]) = “Electronics”)) และควบคุมพฤติกรรม override/intersect ได้อย่างละเอียด
.
แนะนำให้ใช้ Boolean Filter สำหรับเงื่อนไขง่ายๆ และ Table Expression สำหรับเงื่อนไขซับซ้อนครับ 💡

CALCULATETABLE ทำ Context Transition เหมือน CALCULATE ไหม?

ใช่ CALCULATETABLE ทำ context transition เหมือนกับ CALCULATE ทุกประการ เมื่อถูกเรียกใช้ภายใน row context (เช่นใน calculated column หรือภายใน iterator function เช่น SUMX) row context จะถูกแปลงเป็น filter context โดยอัตโนมัติก่อนที่จะ evaluate table expression
.
ลำดับการทำงานภายในก็เหมือนกัน: (1) Evaluate filter arguments ใน original context ก่อน (2) ทำ context transition (3) Apply filter modifiers (4) Apply filters (5) Evaluate table expression ภายใต้ filter context ใหม่
.
การเข้าใจ context transition เป็นสิ่งสำคัญมากสำหรับการเขียน DAX ที่ถูกต้อง เพราะมันส่งผลต่อพฤติกรรมของ aggregation functions และ filter propagation ครับ 😎

Performance: CALCULATETABLE vs FILTER ใครเร็วกว่า?

ใน VertiPaq engine (DAX engine รุ่นใหม่ที่ใช้ใน Power BI และ Analysis Services) CALCULATETABLE มักจะมี performance ดีกว่า FILTER สำหรับ simple Boolean conditions เพราะ engine สามารถใช้ cardinality estimation (ประมาณจำนวน rows ที่จะผ่าน filter) และ optimization techniques (เช่น bitmap filtering) ได้ดีกว่า
.
อย่างไรก็ตาม สำหรับ complex row-by-row conditions ที่ต้องการ row context การใช้ FILTER อาจจะเหมาะสมกว่าและในบางกรณีอาจจะเร็วกว่าด้วยซ้ำ
.
โดยทั่วไปแนะนำให้ใช้ CALCULATETABLE เป็น first choice สำหรับ table filtering และเปลี่ยนไปใช้ FILTER เฉพาะเมื่อ logic ต้องการ row context จริงๆ หรือเมื่อ performance testing แสดงว่า FILTER เร็วกว่าใน specific scenario นั้นครับ 💡

ทำไมต้องใช้ USERELATIONSHIP กับ CALCULATETABLE?

USERELATIONSHIP ใช้เมื่อ data model มี multiple relationships ระหว่างสองตาราง (เช่น Sales table มี OrderDate, ShipDate, และ DueDate ที่เชื่อมกับ Date table เดียวกัน) โดยปกติจะมี active relationship เพียง 1 relationship (เช่น OrderDate → Date) แต่บางครั้งต้องการใช้ inactive relationship (เช่น ShipDate → Date) แทน
.
USERELATIONSHIP เปิดใช้งาน inactive relationship ชั่วคราวสำหรับการ evaluate table expression ทำให้สามารถสลับการใช้ relationships ได้ตามต้องการ
.
ตัวอย่าง: CALCULATETABLE(Sales, USERELATIONSHIP(Sales[ShipDate], Date[Date]), Date[Year] = 2024) จะกรอง Sales ตาม ShipDate แทน OrderDate Pattern นี่เรียกว่า role-playing dimensions และใช้บ่อยมากใน real-world scenarios เช่น การวิเคราะห์ยอดขายตาม order date vs ship date vs payment date ครับ 😎

Override vs Intersect Behavior คืออะไร และเมื่อไหร่ใช้ KEEPFILTERS?

Override behavior (default) หมายความว่า filter ใหม่จะเขียนทับ filter เดิมที่กรอง column เดียวกัน ตัวอย่าง: ถ้า slicer เลือก Channel = “Retail” และใช้ CALCULATETABLE(Sales, Sales[Channel] = “Online”) filter ใหม่ (Online) จะเขียนทับ filter เดิม (Retail) ทำให้ได้ Online sales เสมอ
.
ส่วน Intersect behavior (ใช้ KEEPFILTERS) หมายความว่า filter ใหม่และ filter เดิมต้องเป็นจริงพร้อมกัน (AND logic) ตัวอย่าง: CALCULATETABLE(Sales, KEEPFILTERS(Sales[Channel] = “Online”)) จะทำให้ filter context มี Retail (จาก slicer) AND Online (จาก KEEPFILTERS) ซึ่งเป็นไปไม่ได้ จึงได้ผลลัพธ์ 0 rows
.
ใช้ KEEPFILTERS เมื่อต้องการให้ measure รับผลจาก user selections เสมอและไม่ต้องการให้ filter ใน DAX code เขียนทับ filter จาก slicers หรือ visual context ครับ 💡

Resources & Related

Related functions

Additional Notes

CALCULATETABLE ใช้สำหรับกรองตารางด้วยเงื่อนไขที่ต้องการ แล้วคืนค่าเป็นตารางที่กรองแล้ว ซึ่งสามารถนำไปใช้กับฟังก์ชันอื่นต่อได้ทันที
.
ที่เจ๋งคือมันแตกต่างจาก CALCULATE ตรงที่ CALCULATE คืนค่าเป็นตัวเลขเดียว ส่วน CALCULATETABLE คืนค่าเป็นตารางทั้งตาราง
.
ส่วนตัวผมใช้บ่อยมากเลยครับ โดยเฉพาะเวลากรองตารางแล้วส่งต่อให้ SUMX, AVERAGEX คำนวณต่อ 😎

🎯 กรณีการใช้งานหลัก

  • กรองตารางแล้วส่งต่อให้ SUMX, AVERAGEX คำนวณต่อ
  • ลบตัวกรองบางส่วนออกด้วย REMOVEFILTERS
  • เปิดใช้ความสัมพันธ์ที่ไม่ได้ active ด้วย USERELATIONSHIP
  • คำนวณเปอร์เซ็นต์ของยอดรวมด้วย ALLSELECTED

ความแตกต่างหลักระหว่าง CALCULATETABLE และ CALCULATE

แม้ว่าฟังก์ชัน CALCULATETABLE และ CALCULATE จะมีพฤติกรรมในการจัดการบริบทของตัวกรองเหมือนกันทุกประการ แต่มีความแตกต่างที่สำคัญมากในประเภทของค่าที่คืนกลับมา ซึ่งส่งผลต่อการเลือกใช้งานในสถานการณ์ต่างๆ อย่างมีนัยสำคัญ:

  • ฟังก์ชัน CALCULATE: คืนค่าผลลัพธ์เป็น scalar value หรือค่าเดี่ยวๆ เช่น ตัวเลข ข้อความ วันที่ หรือค่าใดค่าหนึ่ง เหมาะสำหรับการคำนวณ measures ที่ต้องการผลลัพธ์เป็นตัวเลขเดี่ยวเพื่อแสดงในการ์ด หรือใน visualization ต่างๆ เช่น ยอดขายรวมทั้งหมด ราคาเฉลี่ย จำนวนลูกค้าทั้งหมด
  • ฟังก์ชัน CALCULATETABLE: คืนค่าผลลัพธ์เป็น table หรือตารางที่สมบูรณ์ เหมาะสำหรับการสร้างตารางกลางที่ถูกกรองด้วยเงื่อนไขแล้วส่งต่อให้ฟังก์ชันอื่นทำงานต่อ เช่น ส่งให้ฟังก์ชัน SUMX ทำการวนลูปแต่ละแถวและคำนวณ หรือใช้ใน calculated table definition เพื่อสร้างตารางใหม่ในโมเดลข้อมูล

พฤติกรรมที่เหมือนกันทั้งหมด: ทั้งสองฟังก์ชันมีพฤติกรรมการทำงานเหมือนกันในหลายประการ ได้แก่ การทำ context transition หรือการเปลี่ยนบริบทของแถวเป็นบริบทของตัวกรองโดยอัตโนมัติเมื่อถูกเรียกใช้ภายในบริบทของแถว รองรับฟังก์ชันปรับแต่งตัวกรองเหมือนกันทุกตัวเช่น REMOVEFILTERS สำหรับลบตัวกรอง ALL และ ALLEXCEPT สำหรับลบตัวกรองแบบเลือกสรร KEEPFILTERS สำหรับเพิ่มตัวกรองแบบ intersect USERELATIONSHIP สำหรับเปิดใช้ความสัมพันธ์ที่ไม่ได้ active และมีลำดับการประเมินค่า filter arguments เหมือนกันคือประเมินค่าใน original context ก่อนที่จะทำการเปลี่ยน context ใดๆ ทั้งสิ้น

CALCULATETABLE vs FILTER: เลือกใช้อย่างไรให้เหมาะสม

ทั้งสองฟังก์ชัน CALCULATETABLE และ FILTER คืนค่าผลลัพธ์เป็นตารางเหมือนกัน แต่มีพฤติกรรมการทำงานและกรณีการใช้งานที่แตกต่างกันอย่างชัดเจน ซึ่งการเข้าใจความแตกต่างนี้จะช่วยให้คุณเลือกใช้ฟังก์ชันที่เหมาะสมกับสถานการณ์ได้อย่างถูกต้อง:

  • ฟังก์ชัน CALCULATETABLE: ทำหน้าที่ปรับเปลี่ยนบริบทของตัวกรองก่อนที่จะประเมินค่านิพจน์ตาราง ทำให้สามารถใช้ฟังก์ชันปรับแต่งตัวกรองได้ทั้งหมดเช่น REMOVEFILTERS สำหรับลบตัวกรอง, ALL สำหรับลบตัวกรองทั้งหมด, USERELATIONSHIP สำหรับเปิดใช้ความสัมพันธ์ที่ไม่ได้ active และมักจะมีประสิทธิภาพที่ดีกว่าฟังก์ชัน FILTER ในกรณีที่เป็นเงื่อนไข Boolean แบบธรรมดา เพราะ DAX engine สามารถทำการประมาณจำนวนข้อมูลและเพิ่มประสิทธิภาพได้ดีกว่า เหมาะสำหรับเงื่อนไข Boolean ที่เรียบง่ายและการควบคุมบริบทของตัวกรองแบบ declarative หรือแบบประกาศ
  • ฟังก์ชัน FILTER: เป็นฟังก์ชันประเภท iterator ที่สร้างบริบทของแถวขึ้นมาใหม่และวนลูปไปแต่ละแถวในตารางเพื่อประเมินค่าเงื่อนไข Boolean ไม่มีการปรับเปลี่ยนบริบทของตัวกรองแต่อย่างใด เหมาะสำหรับเงื่อนไขที่ซับซ้อนที่ต้องการการประเมินค่าระดับแถว เช่น เงื่อนไขที่อ้างอิงถึง measure หรือการคำนวณที่ซับซ้อนแบบทีละแถว

การพิจารณาด้านประสิทธิภาพ: ใน VertiPaq engine ซึ่งเป็น DAX engine รุ่นใหม่ที่ใช้ใน Power BI และ Analysis Services ฟังก์ชัน CALCULATETABLE มักจะทำงานเร็วกว่าฟังก์ชัน FILTER อย่างเห็นได้ชัดสำหรับเงื่อนไข Boolean แบบธรรมดา เพราะ engine สามารถใช้เทคนิคการเพิ่มประสิทธิภาพต่างๆ ได้ดีกว่า ดังนั้นแนะนำให้เลือกใช้ CALCULATETABLE เป็นตัวเลือกแรกเสมอ และเปลี่ยนไปใช้ FILTER เฉพาะเมื่อต้องการบริบทของแถวจริงๆ

รูปแบบของ Filter Arguments และพฤติกรรม

ฟังก์ชัน CALCULATETABLE รองรับ filter arguments หรืออาร์กิวเมนต์สำหรับการกรองข้อมูลหลากหลายรูปแบบ โดยแบ่งออกเป็น 3 รูปแบบหลักที่มีพฤติกรรมและข้อจำกัดแตกต่างกัน การเข้าใจรูปแบบต่างๆ เหล่านี้จะช่วยให้คุณสามารถเลือกใช้ได้อย่างเหมาะสมตามสถานการณ์:

  1. รูปแบบ Boolean Expression หรือเงื่อนไขแบบบูลีน: เป็นเงื่อนไขแบบง่ายที่เขียนในรูปแบบ Sales[Channel] = "Online" ซึ่งระบบ DAX จะแปลงโดยอัตโนมัติเป็น FILTER(ALL(Sales[Channel]), Sales[Channel] = "Online") ทำให้เกิดพฤติกรรมแบบ override คือจะเขียนทับตัวกรองเดิมของคอลัมน์นั้นทั้งหมด ข้อจำกัดที่สำคัญของรูปแบบนี้คือต้องอ้างอิงคอลัมน์จากตารางเดียวกันเท่านั้น ไม่สามารถอ้างอิงคอลัมน์จากหลายตารางในเงื่อนไขเดียวกันได้
  2. รูปแบบ Table Expression หรือนิพจน์ตาราง: เป็นนิพจน์ที่คืนค่าผลลัพธ์เป็นตาราง เช่น FILTER(Sales, Sales[Amount] > 1000) หรือ VALUES(Product[Category]) ให้ความยืดหยุ่นมากกว่ารูปแบบ Boolean Expression สามารถใช้เงื่อนไขที่ซับซ้อนได้ อ้างอิงคอลัมน์จากหลายตารางพร้อมกันได้ และสามารถควบคุมพฤติกรรมแบบ override หรือ intersect ได้อย่างละเอียดตามต้องการ
  3. รูปแบบ Filter Modifier Functions หรือฟังก์ชันปรับแต่งตัวกรอง: เป็นฟังก์ชันพิเศษที่ออกแบบมาเพื่อจัดการบริบทของตัวกรองอย่างละเอียด เช่น REMOVEFILTERS(column) สำหรับลบตัวกรองออกจากคอลัมน์ที่ระบุ KEEPFILTERS(filter) สำหรับเพิ่มตัวกรองใหม่แบบ intersect แทนที่จะเป็น override และ USERELATIONSHIP(col1, col2) สำหรับเปิดใช้งานความสัมพันธ์ที่ไม่ได้ active อยู่ชั่วคราว

พฤติกรรมแบบ Override กับ Intersect: โดยค่าเริ่มต้นถ้ามีตัวกรองหลายตัวที่กรองคอลัมน์เดียวกัน ตัวกรองใหม่จะทำงานแบบ override หรือเขียนทับตัวกรองเดิมทั้งหมด แต่ถ้าใช้ฟังก์ชัน KEEPFILTERS พฤติกรรมจะเปลี่ยนเป็นแบบ intersect ซึ่งใช้ AND logic ทำให้ตัวกรองใหม่และตัวกรองเดิมต้องเป็นจริงพร้อมกันทั้งคู่

Context Transition และลำดับการ Evaluate

เช่นเดียวกับ CALCULATE ฟังก์ชัน CALCULATETABLE จะทำ context transition โดยอัตโนมัติเมื่อถูกเรียกใช้ภายใน row context (เช่น ใน calculated column หรือภายใน iterator function เช่น SUMX) กลไกนี้จะแปลง row context เป็น filter context ก่อนที่จะ evaluate table expression

ลำดับการ Evaluate ภายใน CALCULATETABLE (สำคัญมาก):

  1. Evaluate Filter Arguments: ประเมินค่าทุก filter expression ใน original context ก่อนการเปลี่ยนแปลงใดๆ (นี่เป็นพฤติกรรมที่สำคัญมากเมื่อซ้อน CALCULATETABLE)
  2. Context Transition: ถ้ามี row context จะถูกแปลงเป็น filter context โดยอัตโนมัติ
  3. Apply Filter Modifiers: ประเมินและดำเนินการฟังก์ชันปรับแต่ง filter เช่น REMOVEFILTERS, ALL, KEEPFILTERS
  4. Apply Filters: นำ filter ทั้งหมดมาใช้กับ filter context (ปกติจะ override filter เดิมที่ column เดียวกัน เว้นแต่ใช้ KEEPFILTERS)
  5. Evaluate Table Expression: สุดท้ายจึงประเมินค่า table expression ภายใต้ filter context ใหม่ที่เตรียมไว้แล้ว

ALLSELECTED Pattern สำหรับ Visual-Level Totals

Pattern ที่นิยมใช้มากและมีประโยชน์อย่างยิ่งคือการใช้ CALCULATETABLE ร่วมกับ ALLSELECTED เพื่อสร้าง visual-level totals ที่เคารพ filter จาก slicers และ filters ภายนอก visual แต่ไม่รับผลกระทบจาก row/column context ภายใน visual เอง (เช่น บริบทของแถวใน matrix หรือ table visual)

ALLSELECTED คืนค่า filter context ที่ “มองเห็นได้” ใน visual (visible context) โดยลบ filter ที่เกิดจาก row/column context ภายใน visual แต่ยังคง filter จาก slicers, page filters, และ report filters ไว้ ทำให้เหมาะสำหรับการคำนวณเปอร์เซ็นต์ของยอดรวม (percentage of total) ที่ต้องการให้เคารพ user selections แต่ไม่รับผลจาก row-level context

Use Cases และ Best Practices

เมื่อไหร่ควรใช้ CALCULATETABLE:

  • เตรียม intermediate table ที่ถูกกรองแล้วส่งต่อให้ iterator functions (SUMX, AVERAGEX, COUNTX)
  • สร้าง calculated table ที่มีการกรองแบบ dynamic ตาม filter context
  • ลบ filter บางส่วนออกจาก context ปัจจุบันด้วย REMOVEFILTERS หรือ ALL
  • เปิดใช้งาน inactive relationship ชั่วคราวด้วย USERELATIONSHIP (role-playing dimensions)
  • สร้าง visual-level totals ด้วย ALLSELECTED สำหรับ percentage calculations
  • ต้องการ performance ที่ดีกว่า FILTER สำหรับ simple Boolean conditions

Best Practices:

  • ใช้ VAR…RETURN: แบ่งการคำนวณที่ซับซ้อนออกเป็นขั้นตอนชัดเจน ทำให้โค้ดอ่านง่าย debug ง่าย และ performance ดีขึ้น (variable ถูกคำนวณเพียงครั้งเดียว)
  • ใช้ REMOVEFILTERS แทน ALL: ใน modern DAX แนะนำให้ใช้ REMOVEFILTERS เพราะอ่านง่ายและชัดเจนกว่า
  • ระวัง Override Behavior: จำไว้ว่า Boolean filter จะ override filter เดิมเสมอ ใช้ KEEPFILTERS ถ้าต้องการ AND logic
  • เลือก CALCULATETABLE เป็น First Choice: สำหรับ table filtering เลือกใช้ CALCULATETABLE ก่อนเสมอ และเปลี่ยนไปใช้ FILTER เฉพาะเมื่อต้องการ row context จริงๆ

Leave a Reply

Your email address will not be published. Required fields are marked *