Challenge này là 1 phần trong 100+ Challenges & Cases của khóa học PBI Mastery Thực Chiến mình viết cho các bạn trong lớp hiện tại làm thêm ngoài giờ học qua GG Meet, sẵn public cho các bạn khác tham khảo thêm. Bạn nào thích học thực chiến như thế này, thì có thể đăng ký khóa học PBI Mastery Thực Chiến nhé. Inbox mình Tại Đây.
Các challenge này, sẽ được giải thích lại chi tiết thêm 1 lần nữa trong các buổi học.
From: Data Analytics Lead
To: BI Analyst Team
Subject: [New Task] Xây dựng Report Sales Performance với Best Performing Store Card
Mình cần team xây dựng một Power BI Report hiển thị các chỉ số Sales Performance. Report cần có các thông tin sau:
Điều kiện Business cần đảm bảo trong report
- Điều kiện 1: Xử lý trường hợp không có data tháng trước
- Điều kiện 2: Best Store Card KHÔNG bị ảnh hưởng bởi Store Location Slicer
- Điều kiện 3: Ký hiệu mũi tên nhận biết Tăng / Giảm và đường viền Card đổi màu theo xu hướng
Hướng Dẫn Chi Tiết Từng Bước
Kiến Thức Chi Tiết Sẽ Hướng Dẫn Lại Trong Khóa Học PBI Mastery Challenges & Case Studies
Trong Bài Này Chỉ Hướng Dẫn Nhanh Một Số Theory Quan Trọng
Bước 01
Total Revenue = SUM(Sales[amount])
Bước 02
Total Quantity Sold = SUM(Sales[quantity])
Bước 03
Average Revenue per Transaction = DIVIDE([Total Revenue], COUNTROWS(Sales), 0)
Bước 04 (Phần Này Sẽ Demo Chi Tiết & Giải Thích Lại Trong Khóa Học)
- Mở Tabular Editor 2 và kết nối Power BI
- Tạo Calculation Group
- Đặt tên Calculation Group
- Tạo Calculation Items
THEORY Bạn Cần Biết Trong Bước 04
- Nếu không dùng Calculation Groups
- Khi dùng Calculation Groups
Chi tiết hơn mình sẽ hướng dẫn thêm trong khóa học.
Bước 05: Nhập DAX Cho Từng Item
ITEM 1: “Current”
SELECTEDMEASURE()
ITEM 2: “Previous Month”
VAR CurrentMonthStart = MIN('Date'[Date])
VAR PrevStart = DATE(YEAR(CurrentMonthStart), MONTH(CurrentMonthStart) - 1, 1)
VAR PrevEnd = EOMONTH(CurrentMonthStart, -1)
RETURN
CALCULATE(
SELECTEDMEASURE(),
'Date'[Date] >= PrevStart,
'Date'[Date] <= PrevEnd
)
ITEM 3: “MoM %”
VAR CurrentVal = SELECTEDMEASURE()
VAR PrevVal = CALCULATE(
SELECTEDMEASURE(),
'Time Intelligence'[Time Calc] = "Previous Month"
)
RETURN
IF(
ISBLANK(PrevVal) || PrevVal = 0,
BLANK(),
DIVIDE(CurrentVal - PrevVal, PrevVal)
)
Lưu thay đổi về Power BI
THEORY Bạn Cần Biết Trong Bước 05
Trong challenge này, user filter theo Month slicer (CẢ tháng) -> Nếu dùng DATEADD() thay thế -> Có thể THIẾU ngày nếu tháng không đều
Còn hàm PREVIOUSMONTH() thì sao? Có những Edge Case nào cần lưu ý? Phần này mình sẽ hướng dẫn thêm trong khóa học.
Bước 06
Prev Month Revenue = CALCULATE([Total Revenue], 'Time Intelligence'[Time Calc] = "Previous Month")
Prev Month Qty Sold = CALCULATE([Total Quantity Sold], 'Time Intelligence'[Time Calc] = "Previous Month")
Prev Month ARPT = CALCULATE([Average Revenue per Transaction], 'Time Intelligence'[Time Calc] = "Previous Month")
Revenue MoM % = CALCULATE([Total Revenue], 'Time Intelligence'[Time Calc] = "MoM %")
Qty MoM % = CALCULATE([Total Quantity Sold], 'Time Intelligence'[Time Calc] = "MoM %")
ARPT MoM % = CALCULATE([Average Revenue per Transaction], 'Time Intelligence'[Time Calc] = "MoM %")
Bước 07: Cài Đặt Dynamic Format
- Chọn measure
- Sau khi chọn measure, ribbon “Measure tools” sẽ xuất hiện -> Chọn Dynamic
- Nhập Format String vào FORMULA BAR: “▲” #,0.0%;”▼” -#,0.0%;-
THEORY Bạn Cần Biết Trong Bước 07
Bước 08: Best Performing Store (Logic Riêng)
- Card này có logic đặc biệt (Text comparison), không dùng chung Calculation Group `MoM %`.
- Calculation Group “MoM %” hoạt động với SỐ
- Best Performing Store cần so sánh TEXT (Tên cửa hàng)
- Yêu cầu nghiệp vụ:
- “Best Performing Store” phải LUÔN xem xét TẤT CẢ cửa hàng, KHÔNG bị ảnh hưởng bởi Store Location slicer.
- VD: Nếu user chọn slicer: Store = “Hell’s Kitchen”
- FILTER CONTEXT hiện tại: Store[store_location] = “Hell’s Kitchen” → Best Store LUÔN = “Hell’s Kitchen” SAI!
- VD: Nếu user chọn slicer: Store = “Hell’s Kitchen”
- “Best Performing Store” phải LUÔN xem xét TẤT CẢ cửa hàng, KHÔNG bị ảnh hưởng bởi Store Location slicer.
Best Performing Store =
CALCULATE(
-- Tính ra kết quả nhưng cho phép chỉnh điều kiện lọc (filter) trong lúc tính
FIRSTNONBLANK(
-- Lấy giá trị đầu tiên không bị trống từ kết quả (để chắc chắn trả về 1 cửa hàng)
TOPN(
-- Tạo bảng xếp hạng và lấy TOP theo tiêu chí
1,
-- Chỉ lấy đúng 1 dòng: cửa hàng đứng #1
VALUES('Store'[store_location]),
-- Danh sách các cửa hàng (store_location) đang có trong ngữ cảnh dữ liệu
[Total Revenue],
-- Tiêu chí để xếp hạng: Doanh thu (Total Revenue)
DESC
-- Sắp xếp giảm dần: doanh thu cao nhất lên đầu
),
1
-- Mẹo: đưa số 1 vào để FIRSTNONBLANK có cái kiểm tra không trống
),
REMOVEFILTERS('Store'[store_location])
-- Bỏ filter theo cửa hàng (nếu người xem đang chọn 1 store),
-- để luôn tìm ra cửa hàng doanh thu cao nhất trên TOÀN BỘ cửa hàng
)
Bước 09
Previous Month Best Store =
VAR CurrentMonthStart = MIN('Date'[Date])
-- Lấy “ngày nhỏ nhất” trong phạm vi ngày đang xem.
-- Thường hiểu như: đang đứng ở một tháng nào đó trên báo cáo, thì đây là “ngày đầu” của phạm vi đó.
VAR PrevStart = DATE(YEAR(CurrentMonthStart), MONTH(CurrentMonthStart) - 1, 1)
-- Tạo ngày bắt đầu của THÁNG TRƯỚC.
-- Ví dụ đang xem tháng 12/2025 => PrevStart = 01/11/2025.
VAR PrevEnd = EOMONTH(CurrentMonthStart, -1)
-- Lấy ngày cuối cùng của THÁNG TRƯỚC.
-- Ví dụ đang xem tháng 12/2025 => PrevEnd = 30/11/2025.
VAR HasData =
NOT ISBLANK(
CALCULATE(
[Total Revenue],
'Date'[Date] >= PrevStart,
'Date'[Date] <= PrevEnd,
REMOVEFILTERS('Store'[store_location])
)
)
-- Kiểm tra xem “tháng trước có doanh thu không”.
-- Ở đây cố tình bỏ filter theo cửa hàng (REMOVEFILTERS) để kiểm tra dữ liệu trên TOÀN BỘ cửa hàng,
-- tránh trường hợp đang lọc 1 store làm kết quả bị sai.
RETURN
IF(
HasData,
-- Nếu tháng trước có dữ liệu:
CALCULATE(
FIRSTNONBLANK(
TOPN(
1,
VALUES('Store'[store_location]),
[Total Revenue],
DESC
),
1
),
-- TOPN(1...) = lấy đúng 1 cửa hàng doanh thu cao nhất trong tháng trước.
-- FIRSTNONBLANK(...) = lấy ra tên cửa hàng đó (đảm bảo không trả về rỗng).
'Date'[Date] >= PrevStart,
'Date'[Date] <= PrevEnd,
-- Giới hạn dữ liệu chỉ trong khoảng thời gian THÁNG TRƯỚC.
REMOVEFILTERS('Store'[store_location])
-- Bỏ filter cửa hàng để “best store” là best trên toàn hệ thống,
-- không bị ảnh hưởng nếu report đang chọn sẵn 1 store.
),
-- Nếu tháng trước KHÔNG có dữ liệu:
BLANK()
-- Trả về trống để báo cáo không hiện linh tinh.
)
Bước 10
Best Performing Store ▲ =
VAR Curr = [Best Performing Store]
-- Curr = “cửa hàng top doanh thu của THÁNG HIỆN TẠI” (đang xem trên báo cáo)
VAR Prev = [Previous Month Best Store]
-- Prev = “cửa hàng top doanh thu của THÁNG TRƯỚC”
RETURN
SWITCH(
TRUE(),
-- Mẹo viết IF nhiều nhánh: SWITCH(TRUE()) nghĩa là đi kiểm tra từng điều kiện từ trên xuống,
-- điều kiện nào đúng trước thì trả về kết quả của điều kiện đó.
ISBLANK(Curr) || ISBLANK(Prev), BLANK(),
-- Nếu tháng hiện tại hoặc tháng trước không có “best store” (bị trống dữ liệu),
-- thì trả về trống luôn để khỏi hiện thông báo gây hiểu nhầm.
Curr = Prev, "No change",
-- Nếu best store tháng này giống best store tháng trước,
-- thì trả về chữ “No change” (không thay đổi).
"Changed from " & Prev & " to " & Curr
-- Trường hợp còn lại: best store đã đổi.
-- Ghép chữ để tạo câu: “Changed from {Prev} to {Curr}”
)
Bước 11: Data Visualization
Phần này khá đơn giản, các bạn có thể tự làm được. Mình sẽ hướng dẫn chi tiết trong khóa học.

Leave a Comment