WINDOW ดึงช่วงแถวภายในพาร์ทิชัน คืนค่าเป็นตาราง เหมาะกับการคำนวณแบบช่วง เช่น moving average, running sum, หรือ lead/lag analysis
=WINDOW(<From>[, <FromType>], <To>[, <ToType>][, <Relation>][, <OrderBy>][, <Blanks>][, <PartitionBy>][, <MatchBy>][, <Reset>])
=WINDOW(<From>[, <FromType>], <To>[, <ToType>][, <Relation>][, <OrderBy>][, <Blanks>][, <PartitionBy>][, <MatchBy>][, <Reset>])
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
| From | integer | Yes | ตำแหน่งเริ่มของช่วง นิยมเป็นค่าลบ (แถวก่อนหน้า) เช่น -2 หมายถึง 2 แถวก่อนแถวปัจจุบัน | |
| FromType | enumeration | Optional | ชนิดการตีความ From: REL (relative, ค่าตั้งต้น) หรือ ABS (absolute) – REL = offset จากแถวปัจจุบัน, ABS = ตำแหน่งแน่นอนจากจุดเริ่มพาร์ทิชัน | |
| To | integer | Yes | ตำแหน่งสิ้นสุดของช่วง มักเป็น 0 (แถวปัจจุบัน) หรือค่าบวก (แถวข้างหน้า) | |
| ToType | enumeration | Optional | ชนิดการตีความ To: REL หรือ ABS (ค่าตั้งต้น REL) | |
| Relation | table | Optional | ตาราง/ความสัมพันธ์ที่จะดึงช่วงแถวมา ถ้าไม่ระบุจะใช้ relation ของ OrderBy/PartitionBy | |
| OrderBy | expression | Optional | นิพจน์กำหนดลำดับการเรียงสำหรับการสร้างช่วงแถว เช่น ORDERBY(‘DimDate'[Date]) | |
| Blanks | enumeration | Optional | วิธีจัดการค่าว่างในการเรียงลำดับ: SKIP (ข้ามค่าว่าง) หรือ KEEP (เก็บค่าว่าง) | |
| PartitionBy | expression | Optional | นิพจน์กำหนดการแบ่งพาร์ทิชัน เช่น PARTITIONBY(Sales[CustomerID]) – ช่วงแถวจะสร้างแยกในแต่ละพาร์ทิชัน | |
| MatchBy | expression | Optional | นิพจน์กำหนดการจับคู่แถว เมื่อหลายแถวมีค่าเดียวกันในลำดับ ช่วยกำหนดแถวไหนเป็นแถวปัจจุบัน | |
| Reset | boolean | Optional | ใช้ในการคำนวณภาพ (visual calculations) เท่านั้น กำหนดว่าจะ reset การคำนวณที่ลำดับชั้นหรือไม่ |
เช่น เอาแถวก่อนหน้าและถัดไปเพื่อทำการสรุปแบบช่วง
เช่น สรุปยอดในช่วงแถวล่าสุดตามลำดับที่กำหนด
Moving Avg 3Day = AVERAGEX( WINDOW( -2, REL, 0, REL, ORDERBY(Sales[OrderDate]) ), Sales[Amount] ) -- หรือ ใช้ VAR ให้ชัด: Moving Avg = VAR _window = WINDOW( -2,…Moving Avg 3Day =
AVERAGEX(
WINDOW(
-2, REL, 0, REL,
ORDERBY(Sales[OrderDate])
),
Sales[Amount]
)
-- หรือ ใช้ VAR ให้ชัด:
Moving Avg =
VAR _window = WINDOW(
-2, REL, -- เอา 2 แถวก่อน
0, REL, -- ถึง แถวปัจจุบัน (รวม 3 แถว)
ORDERBY(Sales[OrderDate])
)
RETURN AVERAGEX(_window, Sales[Amount])
ได้ค่าเฉลี่ยของ 3 วัน (วันก่อนหน้า 2 วัน + วันปัจจุบัน)
Running Sum Year = VAR _window = WINDOW( 1, ABS, -- เริ่มจาก แถวแรกของปี (position 1) 0, REL, -- ถึง แถวปัจจุบัน ALLSELECTED(Sales), ORDERBY(Sales[MonthNumber])…Running Sum Year =
VAR _window = WINDOW(
1, ABS, -- เริ่มจาก แถวแรกของปี (position 1)
0, REL, -- ถึง แถวปัจจุบัน
ALLSELECTED(Sales),
ORDERBY(Sales[MonthNumber]),
PARTITIONBY(Sales[Year])
)
RETURN SUMX(_window, Sales[Amount])
ได้ยอดรวมสะสมตั้งแต่จุดเริ่มต้นของแต่ละปีจนถึงเดือนปัจจุบัน
Customer Window Rows = VAR _window = WINDOW( -3, REL, -- 3 แถวก่อนหน้า 1, REL, -- 1 แถวข้างหน้า ORDERBY(Sales[OrderDate]), PARTITIONBY(Sales[CustomerID]) ) RETU…Customer Window Rows =
VAR _window = WINDOW(
-3, REL, -- 3 แถวก่อนหน้า
1, REL, -- 1 แถวข้างหน้า
ORDERBY(Sales[OrderDate]),
PARTITIONBY(Sales[CustomerID])
)
RETURN CONCATENATEX(
_window,
FORMAT(Sales[OrderDate], "dd/mm") & ": " & Sales[Amount],
" | "
)
ได้ลำดับคำสั่งของลูกค้า 3 วันก่อน + วันปัจจุบัน + วันข้างหน้า ต่อวันปัจจุบัน
Count Recent Orders = VAR _window = WINDOW( -7, REL, -- ย้อนหลัง 7 วัน 0, REL, -- ถึงวันปัจจุบัน ORDERBY(Sales[OrderDate]), KEEP -- Keep blanks ที่อาจเกิด ) RET…Count Recent Orders =
VAR _window = WINDOW(
-7, REL, -- ย้อนหลัง 7 วัน
0, REL, -- ถึงวันปัจจุบัน
ORDERBY(Sales[OrderDate]),
KEEP -- Keep blanks ที่อาจเกิด
)
RETURN COUNTROWS(_window)
ได้จำนวนคำสั่งในช่วง 7 วัน (ถ้า SKIP จะข้ามวันที่ไม่มีคำสั่ง)
WINDOW คืนค่าเป็นตาราง (ชุดแถว) ไม่ใช่ค่าเดียว จึงต้องนำไปสรุปต่อด้วย iterator เช่น SUMX, AVERAGEX, COUNTROWS, CONCATENATEX ถ้าต้อง scalar value นั่นคือส่วนหนึ่งของ DAX ที่สำคัญ – เข้าใจ row context vs filter context ผม
REL (relative) = offset จากแถวปัจจุบัน, -2 หมายถึง 2 แถวก่อน, 1 หมายถึง 1 แถวข้างหน้า, 0 = แถวปัจจุบัน
ABS (absolute) = ตำแหน่งแน่นอนจากจุดเริ่มต้นของพาร์ทิชัน, 1 = แถวแรก, 2 = แถวที่สอง, -1 = แถวสุดท้าย
กรณี partition มี 10 แถวและอยู่ที่แถวที่ 5:
WINDOW(-2, REL, 0, REL) = แถว 3 ถึง 5
WINDOW(1, ABS, 0, REL) = แถว 1 ถึง 5 (position 1 ไปถึง current row)
PARTITIONBY แบ่งพาร์ทิชัน (group) ก่อนสร้างช่วงแถว ช่วงแถวจะไม่ข้ามพาร์ทิชัน เช่น PARTITIONBY(Sales[CustomerID]) ช่วงแถวจะเริ่มใหม่สำหรับลูกค้าแต่ละคน ไม่ผสมลูกค้าคนที่แล้วกับคนใหม่
ที่จริง WINDOW จะตัด boundary โดยอัตโนมัติเมื่อชนพาร์ทิชัน มันจะส่งคืนตารางของแถวที่เหลือ ถ้าอยากรู้ตัวเลขจริงของแถวในช่วง ลองใช้ COUNTROWS() บนผลลัพธ์จาก WINDOW ดู
OrderBy กำหนดลำดับการเรียง ซึ่งเป็นหลักในการสร้างช่วงแถว ถ้าข้าม WINDOW จะ error หรือได้ผลลัพธ์ที่คาดไม่ได้ ลำดับที่ชัดเจนมีความสำคัญเท่า partition ผมแนะนำให้ระบุเสมอ
WINDOW ใช้ดึงช่วงของแถวภายในพาร์ทิชันตามลำดับที่กำหนด มันคืนค่าเป็นตาราง (ชุดแถว) ไม่ใช่ค่าเดียว ทำให้มันเหมาะสำหรับงานคำนวณแบบช่วงต่อเนื่อง เช่น rolling window, moving average, running total ตามลำดับเวลา หรือหาช่วงแถวรอบแถวปัจจุบัน
ที่เจ๋งของ WINDOW คือมันให้ control เต็มเหมี่ยมในการกำหนดช่วงแถว – ว่าจะเอาแถวก่อนหน้ากี่แถว แถวปัจจุบัน แถวหน้ากี่แถว และมันสมาร์ทพอในการ partition (แบ่งกลุ่ม) ตามเงื่อนไขที่ต้องการ ผมชอบนำ WINDOW มาใช้แทน EARLIER/OFFSET เมื่อต้องการ rolling calculation
ส่วนตัวผมแล้ว WINDOW ยังยากอยู่ เพราะ parameter พอสมควร และต้องเข้าใจเรื่อง relative vs absolute position ให้ดี แต่พอเข้าใจแล้ว มันจะฉลาดกว่า EARLIER มากในเรื่องการจัดการพาร์ทิชันและการเรียงลำดับ