Context Transition - Tính năng gây hiểu lầm khi dùng DAX

Context Transition – Tính năng gây hiểu lầm khi dùng DAX

Ngày đăng: 14/06/2023

Trong quá trình viết DAX và thể hiện chúng bằng các visual ở trong Power BI, đôi khi chúng ta đã tạo ra các hàm theo từng bước logic đúng nhưng kết quả trả về lại không ra giống như suy nghĩ ban đầu. Chúng ta hoang mang không biết sai do đâu, là do logic ban đầu sai hay do viết sai cú pháp hàm DAX hoặc thậm chí là do Power BI sai.  

Trong bài viết dưới đây, mình sẽ chia sẻ với các bạn về một tính năng chạy ngầm, dễ gây hoang mang và hiểu lầm trong Power BI mà có lẽ là khi viết DAX bạn đã vô tình kích hoạt nó. Tính năng này chính là Context Transition. 

Trước khi đi tới nội dung bài viết về Context Transition, sẽ thật là thiếu xót nếu chúng ta không đề cập tới một trong những khái niệm quan trọng nhất của Power BI đó là Row context vs Filter context. Bởi vì nếu không hiểu rõ ràng 2 loại context này thì khái niệm Context Transition sẽ rất mơ hồ đối với các bạn. Hãy tham khảo bài viết dưới đây để hiểu được Row Context và Filer Context trước khi tới bài viết của chính của chúng ta nhé: Row Context and Filter Context in DAX 

Context Transition là gì? 

Context Transition là quá trình chuyển đổi Row context thành Filter context tương đương. 

Quá trình chuyển đổi này sẽ xảy ra ngay khi hàm DAX chúng ta viết có đủ 2 điều kiện:  

  • Trong hàm có sự xuất hiện của Row context 
  • Trong hàm có xuất hiện của hàm Calculate hoặc Calculatetable. 

Định nghĩa có vẻ khá rõ ràng và rành mạch nhưng lý do vì sao Context Transition lại khiến nhiều người sử dụng hoang mang như vậy thì hãy cùng tìm hiểu ngay bây giờ nhé. 

Những ví dụ minh họa về cách thức hoạt động 

Chúng ta sẽ sẽ đến với một vài ví dụ minh họa để hiểu rõ hơn về cơ chế hoạt động của Context Transition:  

Minh họa bằng Calculated Columns cùng một bảng 

Chúng ta có mô hình dữ liệu đơn giản gồm 2 bảng là Sales vs Products.  

Tạo ra calculated columns bằng DAX để tính giá trị Total Unit Price của bảng Products.

Đầu tiên, chúng ta viết 1 Caculated column Grand Total Unit Price ở trong bảng Product:  

Context transition

Kết quả hiển thị được thể hiên như bảng trên: cột Grand Total Unit Price chỉ chứa duy nhất một giá trị cho tất cả các dòng trong bảng là tổng giá trị của của cột Unit Price.  

Nhắc lại chút về Row context, chúng ta biết rằng khi tạo ra một Calculated Columns chúng ta đang tạo ra một row context. Công thức SUM ở đây đã tính toàn bộ các giá trị hiển thị ở cột Unit Price – với row context mà không có sự xuất hiện của bộ lọc filter context nên nên hàm SUM sẽ hiển thị giá trị là toàn bộ cột Unit Price và giá trị tổng này được hiện ở tất cả các dòng của bảng hiển thị theo từng row context. 

Tiếp theo: Chúng ta tiếp tục tạo thêm 1 cột mới Grand Total CALCULATE giống như trên bằng hàm SUM nhưng có bổ sung hàm CALCULATE bằng cú pháp sau: 

Lúc này kết quả của cột mới đã hoàn toàn khác với giá trị Total ở ví dụ trên và bằng giá trị tương ứng ở cột Unit Price (Giá trị của từng dòng ở Grand Total Calculate = giá trị Unit Price cho từng dòng)  

Context transition khi dùng DAX

Tại sao lại như vậy? 

Công thức hàm Calculate là: CALCULATE(<biểu thức>[, <điều kiện lọc 1> [, <điều kiện lọc2> [, …]]]) 

Chúng ta biết rằng hàm Calculate có thể thay đổi ngữ cảnh của biểu thức theo các điều kiện lọc xác định. Tuy nhiên ở công thức Grand Total Calculate chúng ta chỉ sử dụng hàm Calculate cho biểu thức và không đưa ra bất kỳ một điều kiện lọc nào kèm theo.  

Đáng lý ra kết quả của 2 công thức phải bằng nhau chứ? Giá trị từng dòng của Grand Total Unit Price = Giá trị từng dòng của Grand Total Calculate 

Tại sao 2 hàm về logic có vẻ giống nhau mà kết quả lại khác nhau? 

Rất nhiều câu hỏi khiến chúng ta hoang mang ở đây?

Giải thích vấn đề: Chính là Context Transition đã gây ra sự khác biệt ở đây giữa 2 hàm trên. 

Quay lại hàm DAX của ví dụ thứ hai: Việc viết hàm Calculate bao trùm lên công thức SUM, chúng ta đã đáp ứng 2 điều kiện cần để phát sinh Context Transition đó là có Row context và có Calculate trong công thức.

Khi 2 điều kiện được đáp ứng, Context Transition sẽ tự động chạy ngầm chuyển đổi row context thành một filter context tương đương.  

Quá trình này đã biến biểu thức chuyển đổi: từ không có filter context nào –tính toán toàn bộ giá trị trong bảng chuyển đổi thành tính giá trị theo một filter context mới – tính giá trị theo từng dòng dữ liệu. 

Power BI lúc đó sẽ tiếp nhận một new filter context mới bao trùm lên công thức SUM. Bây giờ công thức SUM sẽ không tính cho toàn bộ các giá trị cột trong cột Unit Price nữa. Cụ thể, tại mỗi dòng trong bảng trên (Row context), Power BI sẽ filter và trả ra đúng dữ liệu tại dòng đang đứng (Filter context), sau đó thực hiện phép SUM tại dữ liệu được trả ra đó. Kết quả là chúng ta nhận được giá trị SUM tương ứng trên từng dòng.  

Minh họa bằng Calculated Columns khác bảng theo relationship 

Tạo ra calculated columns ở trong bảng Products để tính giá trị Total Sales Amount từ bảng Internet Sales. Biết rằng 2 bảng có mối quan hệ one to many từ Products – Internet Sales (thông qua cột ProductKey).

Context transition khi viết DAX

Giống như việc xây dựng 2 cột ở trên, do 2 bảng có mối quan hệ one – many với nhau. Chúng ta hoàn toàn có thể xây dựng 2 Calculate Columns như dưới đây: 

Lưu ý khi dùng Context transition

2 kết quả của 2 cột vẫn thể hiện rõ sự khác biệt như ví dụ thứ nhất.  

Với cột Grand Total Sales Amount, chỉ có duy nhất row context được tạo ra.  

Còn cột Grand Total Sales Amount CALCULATE, với sự xuất hiện của Calculate trong hàm, đã tạo ra Context Transition từ row context thành filter context tương đương.  

Trong trường hợp ở đây, new filter context đã áp dụng thông qua relationships trong data model, và bắt đầu từ bảng ‘Product’ và nối sang bảng ‘Sales’ (quan hệ one – many). 

Nói cách khác, new filter context này sẽ tính giá trị Sales Amonut của bảng Internet Sales cho từng Products chứ không phải là giá trị total.  

Đến đây mọi người có thể thấy rõ ràng một phần cách Context Transition hoạt động như thế nào. 

Tuy nhiên những trường hợp trên, viêc phát hiện ra Context Transition khá dễ dàng.  

Trong thực tế, một trong những lý do mọi người cảm thấy khó hiểu với Context Transition là do sự khó nhận ra của nó. Với 2 điều kiện được viết ở bên trên, đáng lẽ điều này rất dễ để nhận ra, tuy nhiên đôi khi một trong hai yếu tố cần thiết cho Context Transition có thể bị ẩn đi và khó thấy hơn. Đến đây mình sẽ cùng các bạn giải thích thêm một chút: 

Đầu tiên, Context Transition được gọi bằng lệnh CALCULATE (hoặc CALCULATETABLE). Nếu hàm của chúng ta không có lệnh CALCULATE, thì không có Context Transition xảy ra. Tuy nhiên hãy nhớ rằng lệnh CALCULATE luôn xuất hiện khi bạn tạo một measures; do đó, một sự chuyển đổi ngữ cảnh có thể xảy ra khi bạn gọi một measures mà không có sự xuất hiện của lệnh CALCULATE được viết trong phép tính của bạn.  

Thứ hai, Context Transition yêu cầu sự hiện diện của row context. Nếu không có row context, thì không có Context Transition xảy ra, vì sẽ không có row context để chuyển đổi sang filter context. Row context có thể được tạo ra một cách bằng cách sử dụng các hàm iterator function (hàm có hậu tố X ở phía cuối), nhưng cũng có thể sẽ có khi bạn viết một calculated columns – nơi mặc định đã có một ngữ cảnh dòng. 

Chúng ta đến với một ví dụ khác mà các điều kiên phát sinh của Context Transition bị dấu đi: 

Minh họa bằng Measures 

Sử dụng Data model ở ví dụ thứ 2, viết DAX để tính toán giá trị Total Sales cho bảng Products: 

Đầu tiên chúng ta xây dựng một measures là:  

Sau đó ở bảng Products, tạo một cột tính mới trên với giá trị bằng measures Total Sales ở trên: 

Dừng lại ở đây, chúng ta thấy ở đây không có giá trị Calculate. Vậy thì kết quả trả ra phải là giá trị Total Sales của toàn bộ Sales Amount và hiển thị cho tất cả các dòng. Tuy nhiên, kết quả hiển thị được thể hiện như bảng dưới: 

Tại sao suy nghĩ của chúng ta khác với kết quả thực tế vậy? Không có CALCULATE mà vẫn tạo ra Context Transition sao? 

Ở đây, chúng ta phải nhắc lại chút về cách hiển thị của hàm CALCULATE. CALCULATE là một hàm rất đặc biệt và chiếm vị trí cực kỳ quan trọng trong DAX và một trong những điểm đặc biệt của nó là nó xuất hiện mỗi khi measure được tạo ra, cho dù trong hàm DAX có viết CALCULATE hay không?   

Ví dụ trên đã được giải thích: Khi chúng ta tạo ra cột mới Sales = hàm measure [Total Sales]. Chúng ta đã có sự xuất hiện của cả row context (khi tạo ra new calculated columns) và filter context (khi gọi hàm measures ra) và Context Transition đã diễn ra.  

Ở đây, chúng mình sẽ phân tích kỹ hơn điều thực sự diễn ra ở đây: 

Khi viết calculate column là

Bên trong Power BI đã tiến hành thực hiện Context Transition và biến đổi công thức đó thành nội dung tương đương như bên dưới:

  • Một row context theo dòng được tạo ra bởi calculated columns Sales đã được chuyển hóa thành một filter context tương đương với giá trị lọc chính là giá trị của từng cột ở từng dòng (dòng hiện tại và trả về kết quả Sales theo từng dòng.   

Ngoài các trường hợp ở trên, chúng mình muốn ví dụ cũng khiến cho Context Transition trở nên khó hiểu đối với mọi người.  

Minh họa Measures thông qua giá trị cộng dồn 

Từ data model với 2 bảng Sales vs bảng Date (liên kết với nhau thông qua mối quan hệ one – many). Tính giá trị cộng dồn của Sales qua thời gian.  

Bài giải: Đối với các bài toán tính giá trị cộng dồn qua thời gian, chúng ta cần phải xác định giá trị cộng dồn tới thời điểm nào. Ở đây chính là Max Date chúng ta viết một measure như sau  

Chúng ta lần lượt viết 2 hàm Sales 1 và Sales 2 với sự kết hợp của CALCULATE, FILTER và ALL để tính giá trị Sales cộng dồn thời gian:   

Trong đó: 

Và  

Có vẻ là 2 cách viết này đều giống nhau và không có sự khác biệt ? Chúng ta đâu thay đổi điều gì, chỉ là việc viết trước hay viết sau? Kết quả được trả ra ở bảng dưới 

Như bạn đã thấy, Sales 1 trả về giá trị tổng cộng doanh số trong mỗi ô, trong khi Sales 2 tạo ra một giá trị cộng dồn của Total Sales theo chuỗi thời gian. Sự khác biệt giữa hai phương pháp này chỉ là Sales 2 sử dụng MAX trực tiếp bên trong FILTER, trong khi Sales 1 gọi biểu thức tính toán tính Max Date.  

Trong cả hai trường hợp, khi tính toán ngày lớn nhất, chúng ta đều tính toán nó bên trong một vòng lặp qua tất cả các Date (hàm ALL(Date)). 

Tuy nhiên là vì Sales 1 đã gọi measure là MaxDate, chúng ta biết rằng CALCULATE sẽ được sử dụng ở đây và có row context tạo ra từ hàm Dax Filter .  

Khi Sales 1 thực hiện như vậy Context Transition xảy ra, biểu thức tính toán được thực hiện trong một new filter context đang lọc tất cả các ngày được lặp, luôn trả về ngày đang được lặp ở hiện tạid. Vì mỗi ngày đều sẽ nhỏ hơn hoặc bằng chính nó nên hàm FILTER lúc này sẽ trả về toàn bộ bảng Date: nó hiển thị giá trị tổng cộng trong mỗi ô.  

Trong trường hợp của Sales 2, MAX được thực thi mà không có CALCULATE xung quanh nó. Không có Context Transition diễn ra. Vì vậy, nó tính toán ngày lớn nhất theo ngữ cảnh lọc bên ngoài, được tạo ra bởi outer filter thông qua visual theo Year vs Month. Vì vậy, nó tính toán ngày cuối cùng hiển thị trong visual và cho kết quả chính xác giá trị cộng dồn. 

Kết luận

Trong bài viết này, chúng mình chỉ mới tiếp xúc với khái niệm Context Transition. Context Transition chính nó không phải là một chủ đề phức tạp. Điều làm cho việc nắm vững Context Transition trở nên phức tạp là nó thường khó nhận biết do sự tự động của CALCULATE trong biểu thức tính toán. Ở đây, chúng mình muốn nhấn mạnh việc học DAX đòi hỏi bạn phải chậm lại, học cách cơ bản một cách chính xác và từ từ thực hành thật nhiều để rút ra được bài học.

Hy vọng bài viết này sẽ giúp các bạn hiểu rõ hơn về Context Transition. Hãy lưu lại bài viết này để tra cứu khi cần nhé.  

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *