DAX nền tảng: measure, ngữ cảnh lọc và CALCULATE
Xem thửGiám đốc GDev Mart yêu cầu một thẻ số duy nhất: "doanh thu tháng này so với cùng kỳ năm ngoái, tính theo phần trăm". Một analyst kéo cột net_amount vào thẻ và nhận tổng toàn thời gian. Cô thêm slicer tháng, số nhảy đúng. Cô tạo một cột tính toán chia net_amount của mỗi dòng cho tổng doanh thu toàn bảng để ra tỷ lệ. Khi chưa lọc, các tỷ lệ cộng lại đúng 100%, nhưng chọn slicer tháng thì các dòng còn lại chỉ cộng ra vài phần trăm, vì mẫu số đã bị chốt lúc nạp. Cô thử thêm "cùng kỳ năm ngoái" và không biết bắt đầu từ đâu.
Hãy dự đoán: vì sao tỷ lệ tính bằng cột tính toán lại sai, và tính toán nào nên được làm "lúc xem" thay vì "lúc nạp dữ liệu"? Nêu một khác biệt giữa hai loại.
Sau bài này, bạn có thể
- Phân biệt cột tính toán với measure và chọn đúng loại cho tỷ lệ, tổng hợp và phân loại theo dòng.
- Viết measure với
SUM,DISTINCTCOUNTvàDIVIDE, và dự đoán kết quả củaCALCULATEkèmALL(dim_store)trong một ô có sẵn ngữ cảnh lọc. - Viết
Sales YTD,Sales LY,Sales YoY %vớiVARvà xử lý trường hợp năm trước không có dữ liệu.
Cần biết trước: bài "Power Query và mô hình ngữ nghĩa: quan hệ, lược đồ sao và lọc" (ngữ cảnh lọc) và bài "Mô hình nâng cao: dimension ngày, role-playing, độ chi tiết và hệ phân cấp" (bảng ngày được đánh dấu).
Mô hình tư duy 1: measure tính lúc xem, cột tính toán tính lúc nạp
DAX (Data Analysis Expressions) là ngôn ngữ công thức của Power BI. Có hai loại phần tử tính toán:
| Cột tính toán (calculated column) | Measure (số đo) | |
|---|---|---|
| Khi nào tính | Lúc nạp hoặc làm mới, cho từng dòng | Lúc xem, cho ngữ cảnh lọc hiện tại |
| Lưu trữ | Lưu trong mô hình, tốn bộ nhớ | Không lưu kết quả |
| Phản ứng với slicer | Không đổi theo bộ lọc | Đổi theo mọi bộ lọc |
| Dùng cho | Phân loại theo dòng (nhóm giá trị, cờ) | Tổng, tỷ lệ, so sánh, mọi thứ cần tổng hợp |
Tỷ lệ và tổng hợp luôn là measure. Cột tính toán chia cho tổng cả bảng sai khi dùng với slicer vì mẫu số được tính một lần lúc nạp, không biết slicer đang chọn gì. Còn nếu trong cột tính toán bạn gọi số đo [Total Sales] làm mẫu số, context transition làm mẫu số bằng doanh thu của chính dòng đó nên mọi dòng ra 100%. Một measure đúng:
Total Sales = SUM(fct_orders[net_amount])
Order Count = DISTINCTCOUNT(fct_orders[order_id])
Avg Order Value = DIVIDE([Total Sales], [Order Count])
SUM cộng cột trong ngữ cảnh lọc; DISTINCTCOUNT đếm giá trị khác nhau; DIVIDE chia an toàn, trả về trống (hoặc giá trị thay thế bạn chọn) khi mẫu số bằng 0 hoặc trống, trong khi dấu / cho kết quả vô cực hoặc NaN hiện lên báo cáo. Dùng DIVIDE khi mẫu số là biểu thức có thể bằng 0; nếu mẫu số là hằng số thì / là đủ.
Mô hình tư duy 2: ngữ cảnh lọc và CALCULATE
Mỗi ô trong báo cáo có một ngữ cảnh lọc: các bộ lọc từ slicer, hàng, cột và quan hệ. CALCULATE là hàm cốt lõi cho phép bạn thay đổi ngữ cảnh khi tính một biểu thức:
Sales Hanoi = CALCULATE([Total Sales], dim_store[city] = "Hanoi")
Biểu thức này tính Total Sales nhưng ghi đè bộ lọc city bằng "Hanoi". Một cách dùng rất thường gặp là tỷ trọng (tỷ lệ trên tổng):
Sales All Stores = CALCULATE([Total Sales], ALL(dim_store))
Store Share = DIVIDE([Total Sales], [Sales All Stores])
ALL(dim_store) gỡ bộ lọc cửa hàng, nên Sales All Stores luôn là tổng mọi cửa hàng; chia cho nó cho tỷ trọng của cửa hàng đang xét. Cộng tỷ trọng các cửa hàng được 100%.
Đang tải sơ đồ…
Cách đọc hình: cùng một ô có một ngữ cảnh lọc (cửa hàng và tháng). Total Sales tính theo ngữ cảnh đó, còn CALCULATE với ALL gỡ bộ lọc cửa hàng để lấy tổng mẫu số; thương của hai số là tỷ trọng. Điểm mấu chốt: hai giá trị được tính từ cùng một công thức nhưng với ngữ cảnh khác nhau.
Đang tải Animation…
Time intelligence: so sánh theo thời gian
Với bảng ngày chuẩn (đã đánh dấu là bảng ngày), DAX có các hàm thời gian:
Sales YTD = TOTALYTD([Total Sales], dim_date[date])
Sales LY = CALCULATE([Total Sales], SAMEPERIODLASTYEAR(dim_date[date]))
Sales YoY % = DIVIDE([Total Sales] - [Sales LY], [Sales LY])
TOTALYTD cộng dồn từ đầu năm; SAMEPERIODLASTYEAR dịch ngữ cảnh ngày về cùng kỳ năm trước; Sales YoY % là tăng trưởng so với cùng kỳ. Ba hàm này chỉ đúng khi dim_date liên tục (không thiếu ngày) và được đánh dấu là bảng ngày, như ở bài lab tuần 15.
Biến để dễ đọc và nhanh hơn
VAR lưu giá trị trung gian, giúp công thức rõ hơn và không tính lặp:
Sales YoY % =
VAR Current = [Total Sales]
VAR Prior = CALCULATE([Total Sales], SAMEPERIODLASTYEAR(dim_date[date]))
RETURN
DIVIDE(Current - Prior, Prior)
Nếu năm ngoái không có dữ liệu thì trả về trống thay vì hiển thị tăng trưởng vô nghĩa. Cách này cũng thể hiện nguyên tắc: xử lý trường hợp biên một cách tường minh.
Đang tải sơ đồ…
Cách đọc hình: bảng ngày liên tục là nền; từ đó các hàm thời gian suy ra cộng dồn năm và cùng kỳ năm trước, rồi tính tăng trưởng. Nếu bảng ngày thiếu ngày, các hàm này trả kết quả sai hoặc trống.
Những hiểu lầm thường gặp
- "Cột tính toán và measure như nhau." Cột tính theo dòng lúc nạp, không đổi theo slicer; mọi phép tổng hợp phải là measure.
- "Dùng
/cho nhanh." Chia cho 0 cho kết quả vô cực hoặc NaN hiện lên báo cáo; khi mẫu số là biểu thức có thể bằng 0, dùngDIVIDE(mặc định trả về trống, hoặc nêu giá trị thay thế rõ ràng). - "
ALLxóa mọi thứ."ALL(dim_store)chỉ gỡ bộ lọc của bảng cửa hàng; các bộ lọc khác như tháng vẫn còn. Đó là điều ta muốn trong tỷ trọng theo tháng. - "Hàm thời gian chạy trên mọi bảng." Chúng cần bảng ngày liên tục và đánh dấu; cột ngày trong bảng fact có thiếu ngày không đủ.
Tự kiểm tra (có đáp án)
Câu 1. Vì sao tỷ trọng cửa hàng nên là measure? Đáp án: vì nó phải tính lại theo từng ngữ cảnh lọc (tháng, cửa hàng đang chọn); cột tính toán cố định lúc nạp nên không đổi theo slicer và tổng không về 100%.
Câu 2. CALCULATE([Total Sales], ALL(dim_store)) trả về gì trong một ô của cửa hàng Hanoi 01, tháng 9?
Đáp án: tổng doanh thu mọi cửa hàng trong tháng 9; bộ lọc cửa hàng bị gỡ nhưng bộ lọc tháng được giữ.
Câu 3. Vì sao dùng VAR trong Sales YoY %?
Đáp án: để lưu giá trị trung gian, tránh tính lặp, công thức dễ đọc hơn, và kiểm tra trường hợp năm trước không có dữ liệu.
Nếu chỉ nhớ 3 điều
- Measure cho mọi thứ cần tổng hợp theo ngữ cảnh; cột tính toán chỉ cho phân loại theo dòng.
CALCULATEthay đổi ngữ cảnh lọc;ALLgỡ bộ lọc một bảng để lấy mẫu số,DIVIDEđể chia an toàn.- Hàm thời gian cần bảng ngày liên tục và được đánh dấu; dùng
VARvà xử lý trường hợp biên rõ ràng.
Nguồn tham khảo
- CALCULATE function (DAX), kiểm tra ngày 2026-10-28
- ALL function (DAX), kiểm tra ngày 2026-10-28
- DIVIDE function (DAX), kiểm tra ngày 2026-10-28
- Measures in Power BI Desktop, kiểm tra ngày 2026-10-28