RANKX, DAX RANKX, RANKX DAX, Power BI Ranking, DAX Ranking, RANK DAX, TOPN DAX, رتبه بندی در Power BI, رتبه بندی در DAX, تابع RANKX در DAX, آموزش RANKX, RANKX در Power BI, رتبه بندی فروش, رتبه بندی محصولات, رتبه بندی فروشندگان, ALL در DAX, ALLEXCEPT در DAX, Filter Context در DAX, Skip در RANKX, Dense در RANKX, بهینه سازی RANKX, DAX Performance

یکی از نیازهای همیشگی گزارش‌های مدیریتی Power BI، رتبه‌بندی است. مدیر فروش می‌خواهد بداند بهترین فروشنده ماه چه کسی بوده، تیم بازاریابی می‌خواهد پرفروش‌ترین محصولات را شناسایی کند و مدیر منطقه‌ای می‌خواهد بداند شعبه او در مقایسه با سایر شعبه‌ها چه جایگاهی دارد. تمام این سناریوها در نهایت به یک نیاز مشترک ختم می‌شوند: تعیین رتبه یک آیتم در میان مجموعه‌ای از آیتم‌های مشابه، بر اساس یک معیار مشخص.

تابع RANKX دقیقاً برای پاسخ به همین نیاز طراحی شده است. RANKX در DAX به دلیل تعامل مستقیم با Filter Context و Context Transition، رفتاری متفاوت و بسیار پویاتر از رتبه‌بندی‌های معمول در محیط‌های جدولی دارد.

در این مقاله بررسی می‌کنیم RANKX دقیقاً چگونه کار می‌کند، پارامترهای مختلف آن چه نقشی دارند، چگونه باید با مقادیر تکراری و تساوی رتبه برخورد کرد، چگونه می‌توان رتبه‌بندی را در سطوح مختلف Visual محاسبه نمود، RANKX چه تفاوتی با تابع جدیدتر RANK در DAX دارد و چه نکاتی از منظر Performance در مدل‌های فروش با حجم بالا باید رعایت شود.

تعریف RANKX و ساختار پایه آن

RANKX یکی از توابع خانواده X در DAX است، به این معنا که مانند SUMX یا AVERAGEX، یک جدول را پیمایش می‌کند و برای هر ردیف آن، یک Expression را ارزیابی می‌کند. تفاوت اصلی RANKX با سایر X Functionها این است که خروجی آن یک مقدار Aggregate نیست، بلکه رتبه عنصر جاری در میان تمام عناصر جدول ورودی است.

ساختار کلی تابع به شکل زیر است.

RANKX (
    <Table>,
    <Expression>,
    <Value>,
    <Order>,
    <Ties>
)

<h5>معرفی پارامترها به ترتیب:

  1. جدولی است که رتبه‌بندی باید در میان اعضای آن انجام شود.
  • Expressionی است که برای هر ردیف آن جدول ارزیابی می‌شود و معیار رتبه‌بندی را می‌سازد.
  •  اختیاری است و مقدار مربوط به عنصر جاری را مشخص می‌کند؛ در صورت عدم ذکر، همان Expression پارامتر دوم در Context جاری ارزیابی می‌شود.
    •  جهت رتبه‌بندی را تعیین می‌کند، صعودی یا نزولی.
    • نحوه برخورد با مقادیر تکراری را کنترل می‌کند.

پارامتر Value در RANKX چه زمانی کاربرد دارد؟

پارامتر سوم RANKX اختیاری است و مشخص می‌کند دقیقاً چه مقداری باید در مجموعه مقادیر تولیدشده توسط Expression رتبه‌بندی شود. اگر این پارامتر حذف شود، RANKX مقدار Expression را در Context جاری به‌عنوان مقداری که باید رتبه‌بندی شود در نظر می‌گیرد. استفاده صریح از Value زمانی اهمیت بیشتری پیدا می‌کند که بخواهیم رتبه یک مقدار مشخص را در برابر مجموعه‌ای از مقادیر محاسبه‌شده بررسی کنیم. برای مثال، می‌توان رتبه یک مقدار هدف یا مقدار محاسبه‌شده در Context فعلی را در میان مقادیر Expression مربوط به تمام اعضای جدول ورودی تعیین کرد. اگر مقدار Value در مجموعه مقادیر تولیدشده توسط Expression وجود نداشته باشد، RANKX آن مقدار را به‌صورت موقت به مجموعه اضافه می‌کند (Value به‌صورت مفهومی به مجموعه مقادیر برای تعیین رتبه اضافه می‌شود) و سپس رتبه آن را محاسبه می‌کند. بنابراین Value این امکان را فراهم می‌کند که مقدار موردنظر برای رتبه‌بندی را از Expression مورد استفاده برای ساخت مجموعه مقادیر رتبه‌بندی جدا کنیم.

رتبه‌بندی محصولات بر اساس فروش

فرض کنید در یک مدل فروش سازمانی، جدول Product و Measure فروش کل را داریم.

Total Sales =
SUM ( Sales[Amount] )

برای رتبه‌بندی هر محصول بر اساس مجموع فروش آن در میان تمام محصولات، Measure زیر نوشته می‌شود.

Product Sales Rank =
RANKX (
ALL ( Product[ProductID] ),
[Total Sales]
)

ALL(Product[ProductName]) فیلتر اعمال‌شده روی ستون ProductName را از جدول ورودی RANKX حذف می‌کند و مجموعه‌ای از نام محصولات را برای رتبه‌بندی در اختیار تابع قرار می‌دهد. سایر فیلترهای مدل، از جمله فیلترهای مربوط به تاریخ، مشتری یا سایر ستون‌های Product، بسته به مسیر فیلتر و Context جاری همچنان می‌توانند روی محاسبه [Total Sales] اثر بگذارند.

اگر این Measure در یک Visual جدولی که محصولات را در سطرها نمایش می‌دهد قرار گیرد، نتیجه‌ای شبیه جدول زیر خواهد بود.

ProductID Total Sales Product Sales Rank
لپ‌تاپ پرو 850,000,000 1
مانیتور فورکی 620,000,000 2
کیبورد مکانیکی 340,000,000 3
ماوس بی‌سیم 210,000,000 4

چرا استفاده از ALL در اینجا حیاتی است

نکته کلیدی که باید در نظر گرفت این است که اگر به‌جای ALL ( Product[ProductName] ) از VALUES ( Product[ProductName] ) استفاده شود، مشکل از VALUES ذاتاً نیست، بلکه به دامنه جدولی مربوط می‌شود که به‌عنوان ورودی RANKX در اختیار تابع قرار می‌گیرد. VALUES در Filter Context جاری، فقط مقادیر قابل مشاهده همان Context را برمی‌گرداند. بنابراین اگر در یک Visual جدولی، هر سطر با یک محصول مشخص فیلتر شده باشد و VALUES ( Product[ProductName] ) تنها همان محصول جاری را در جدول ورودی RANKX قرار دهد، مقدار رتبه در آن Context معمولاً ۱ خواهد شد، زیرا Expression عملاً فقط در برابر همان عضو مقایسه می‌شود.

بنابراین مسئله اصلی، انتخاب نادرست دامنه رتبه‌بندی است، نه اینکه VALUES ذاتاً تابع نامناسبی باشد. اگر هدف این باشد که رتبه هر محصول در میان تمام محصولات قابل مقایسه محاسبه شود، باید فیلتر محصول جاری از دامنه رتبه‌بندی حذف شود. در ساده‌ترین حالت می‌توان از ALL ( Product[ProductName] ) استفاده کرد.

نکته مهم دیگری که باید به آن توجه کرد این است که ALL روی یک ستون مشخص، با ALL روی کل جدول رفتار یکسانی ندارد. عبارت ALL ( Product[ProductName] ) تنها فیلتر همان ستون ProductName را حذف می‌کند و سایر فیلترهای احتمالی روی سایر ستون‌های جدول Product، مانند دسته‌بندی یا رنگ، همچنان فعال باقی می‌مانند. در مقابل، ALL ( Product ) تمام فیلترهای اعمال‌شده روی ستون‌های جدول Product را حذف می‌کند. بنابراین انتخاب بین این دو باید بر اساس این باشد که آیا واقعاً قصد داریم تمام فیلترهای جدول محصول حذف شوند یا فقط فیلتر ستونی که مبنای رتبه‌بندی است.

مسیر اجرای داخلی RANKX

برای درک عمیق‌تر رفتار RANKX، بررسی مسیر منطقی اجرای آن در موتور DAX کمک‌کننده است.

جدول ورودی (ALL Product)
        │
        ▼
پیمایش تمام محصولات
        │
        ▼
ارزیابی Expression برای هر محصول
        │
        ▼
تشکیل مجموعه‌ای از مقادیر محاسبه‌شده
        │
        ▼
مرتب‌سازی این مجموعه بر اساس Order
        │
        ▼
یافتن جایگاه مقدار عنصر جاری در این مجموعه مرتب‌شده
        │
        ▼
بازگرداندن رتبه به‌عنوان یک عدد صحیح

نکته مهم این است که RANKX برای محاسبه رتبه هر عنصر، عملاً باید Expression موردنظر را برای تمام اعضای جدول ورودی ارزیابی کند، نه فقط برای عنصر جاری. این ویژگی می‌تواند مستقیماً روی هزینه محاسباتی RANKX در جداول بزرگ تأثیر بگذارد، موضوعی که در بخش Performance این مقاله به‌طور مفصل بررسی می‌شود.

RANKX هنگام پیمایش جدول ورودی برای هر ردیف یک Row Context ایجاد می‌کند. اگر Expression پارامتر دوم شامل یک Measure یا CALCULATE باشد، این Row Context می‌تواند از طریق Context Transition به Filter Context تبدیل شود. همین رفتار امکان محاسبه مقدار Measure برای عضو جاری جدول را فراهم می‌کند و دقیقاً همان چیزی است که به RANKX اجازه می‌دهد Total Sales هر محصول را به‌صورت جداگانه و صحیح محاسبه کند.

پارامتر Order رتبه‌بندی صعودی و نزولی

پارامتر چهارم RANKX جهت رتبه‌بندی را کنترل می‌کند و می‌تواند مقادیر DESC یا ASC بگیرد. اگر این پارامتر مشخص نشود، مقدار پیش‌فرض DESC است، یعنی بزرگ‌ترین مقدار رتبه یک را می‌گیرد. این رفتار پیش‌فرض دقیقاً همان چیزی است که در بیشتر سناریوهای فروش موردنیاز است، جایی که محصول یا فروشنده با بیشترین فروش باید رتبه یک را داشته باشد.

Product Sales Rank Desc =
RANKX (
    ALL ( Product[ProductName] ),
    [Total Sales],
    ,
    DESC
)

اما در برخی سناریوها، مانند رتبه‌بندی بر اساس کمترین هزینه یا کمترین زمان تحویل، جهت صعودی مورد نیاز است.

Product Cost Rank Ascending =
RANKX (
    ALL ( Product[ProductName] ),
    [Total Cost],
    ,
    ASC
)

در این حالت، محصولی که کمترین هزینه را دارد رتبه یک را می‌گیرد. توجه به این پارامتر در Measureهایی که معیار رتبه‌بندی آن‌ها ماهیت معکوس دارد، مانند هزینه یا زمان، اهمیت زیادی دارد، زیرا استفاده نادرست از جهت پیش‌فرض DESC می‌تواند نتیجه‌ای کاملاً معکوس با انتظار کاربر تولید کند.

پارامتر Ties: مدیریت مقادیر تکراری در رتبه‌بندی

یکی از مهم‌ترین و در عین حال کمتر شناخته‌شده پارامترهای RANKX، پارامتر پنجم است که نحوه برخورد با مقادیر تکراری را کنترل می‌کند. این پارامتر می‌تواند دو مقدار بگیرد: Skip که رفتار پیش‌فرض است و Dense.

فرض کنید در جدول فروشندگان، دو نفر دقیقاً فروش یکسانی داشته باشند.

SalesPerson Total Sales
علی رضایی 500,000,000
مریم احمدی 500,000,000
نیما کریمی 320,000,000

با استفاده از حالت پیش‌فرض Skip:

Sales Rank Skip =
RANKX (
    ALL ( SalesPerson[SalesPersonName] ),
    [Total Sales]
)

نتیجه به شکل زیر خواهد بود.

SalesPerson Total Sales Sales Rank Skip
علی رضایی 500,000,000 1
مریم احمدی 500,000,000 1
نیما کریمی 320,000,000 3

هر دو فروشنده با فروش برابر، رتبه یک می‌گیرند، اما رتبه بعدی به‌جای دو، مستقیماً سه می‌شود. این رفتار از نظر نحوه جهش رتبه پس از تساوی، مشابه رفتار پیش‌فرض RANK در SQL Server است؛ رتبه بعدی متناسب با تعداد رکوردهای هم‌رتبه جهش پیدا می‌کند. با این حال این شباهت صرفاً در نتیجه نهایی است و به معنای یکسان بودن معماری اجرایی، Context و نحوه پردازش این دو تابع در دو موتور کاملاً متفاوت نیست.

با استفاده از حالت Dense:

Sales Rank Dense =
RANKX (
    ALL ( SalesPerson[SalesPersonName] ),
    [Total Sales],
    ,
    DESC,
    Dense
)

نتیجه به شکل زیر تغییر می‌کند.

SalesPerson Total Sales Sales Rank Dense
علی رضایی 500,000,000 1
مریم احمدی 500,000,000 1
نیما کریمی 320,000,000 2

در حالت Dense، رتبه بعدی بدون هیچ جهشی، بلافاصله پس از رتبه‌های تکراری ادامه می‌یابد، از نظر منطقی مشابه رفتار DENSE_RANK در SQL Server.

انتخاب میان این دو حالت باید بر اساس نیاز واقعی گزارش انجام شود. اگر هدف نمایش رتبه واقعی در میان تمام اعضا با احتساب اثر تساوی رتبه بر رتبه‌های بعدی است، Skip مناسب‌تر است. اگر هدف نمایش تعداد سطوح متمایز رتبه، بدون در نظر گرفتن جهش ناشی از تکرار، است، Dense انتخاب درست‌تری است.

نکته مهم درباره BLANK و دقت اعشاری در RANKX

در RANKX، اگر Expression یا Value به BLANK ارزیابی شود، برای محاسبات عددی BLANK به‌عنوان صفر در نظر گرفته می‌شود. بنابراین نباید به‌صورت کلی فرض کرد اعضای بدون مقدار همیشه در انتهای رتبه‌بندی قرار می‌گیرند، زیرا جایگاه آن‌ها به مقادیر سایر اعضا و جهت رتبه‌بندی نیز بستگی دارد.

نکته دیگری که در رتبه‌بندی مقادیر اعشاری باید مورد توجه قرار گیرد، دقت محاسبات ممیز شناور است. از آنجا که مقادیر Decimal در پس‌زمینه بر اساس استاندارد IEEE 754 ذخیره و مقایسه می‌شوند، دو مقداری که از نظر مفهومی باید کاملاً برابر باشند، در برخی شرایط ممکن است به دلیل خطای بسیار کوچک محاسباتی، نابرابر تشخیص داده شوند و همین موضوع می‌تواند باعث شود دو محصول با فروش ظاهراً یکسان، رتبه‌های متفاوت بگیرند. برای مقادیر حساس، استفاده از Fixed Decimal Number در صورت امکان گزینه مناسبی است و در صورت نیاز می‌توان Expression را با ROUND کنترل کرد.

Product Sales Rank Rounded =
RANKX (
    ALL ( Product[ProductName] ),
    ROUND ( [Total Sales], 2 )
)

این تکنیک به‌خصوص در گزارش‌های مالی که مقایسه دقیق مقادیر اعشاری اهمیت دارد، از بروز رتبه‌بندی‌های غیرمنتظره ناشی از خطای محاسباتی جلوگیری می‌کند.

رتبه‌بندی در سطوح مختلف: کل سازمان در برابر هر گروه

یکی از قدرتمندترین کاربردهای RANKX، امکان تغییر چارچوب رتبه‌بندی بر اساس نیاز تحلیلی است. تا اینجا مثال‌ها رتبه‌بندی در سطح کل مجموعه را نشان دادند، اما در بسیاری از سناریوهای سازمانی، نیاز واقعی رتبه‌بندی در داخل هر گروه، نه در سطح کل، است.

فرض کنید در یک شرکت فروش با چند شعبه، می‌خواهیم رتبه هر فروشنده را در میان فروشندگان همان شعبه، نه در میان تمام فروشندگان شرکت، محاسبه کنیم. یک الگوی رایج برای این هدف استفاده از ALLEXCEPT است.

Sales Rank Within Branch =
RANKX (
    ALLEXCEPT ( SalesPerson, SalesPerson[BranchID] ),
    [Total Sales]
)

در این Measure، به‌جای ALL که تمام فیلترها را حذف می‌کند، از ALLEXCEPT استفاده شده تا تنها فیلتر مربوط به شعبه حفظ شود و سایر فیلترهای جدول SalesPerson، از جمله فیلتر خود فروشنده، حذف گردد. نتیجه این است که هر فروشنده رتبه خودش را در میان همکاران هم‌شعبه‌اش می‌گیرد، نه در میان کل فروشندگان سازمان.

باید توجه داشت که ALLEXCEPT تمام فیلترهای جدول SalesPerson را به‌جز ستون یا ستون‌های مشخص‌شده حذف می‌کند. این رفتار همیشه بهترین انتخاب نیست، زیرا اگر در آینده فیلترهای دیگری روی همین جدول اضافه شوند، ممکن است ناخواسته توسط ALLEXCEPT حذف شوند. در بسیاری از مدل‌ها، اگر فقط لازم است فیلتر نام فروشنده حذف شود و سایر فیلترهای جدول باقی بمانند، حذف مستقیم همان ستون با ALL یا REMOVEFILTERS می‌تواند کنترل دقیق‌تری نسبت به ALLEXCEPT ایجاد کند.

Sales Rank Within Branch Alternative =
RANKX (
    ALL ( SalesPerson[SalesPersonID] ),
    [Total Sales]
)

در این نسخه جایگزین، تنها فیلتر ستون نام فروشنده حذف می‌شود. این Measure زمانی رتبه را داخل شعبه به‌درستی نگه می‌دارد که فیلتر شعبه از طریق مدل، Visual یا Slicer به‌طور طبیعی روی فروشندگان اعمال شده باشد؛ یعنی رتبه‌بندی در محدوده‌ای انجام می‌شود که فیلتر شعبه هنوز فعال است، در حالی که فقط فیلتر نام فروشنده کنار گذاشته شده است. انتخاب بین ALLEXCEPT و این رویکرد جایگزین باید بر اساس تعداد و پایداری فیلترهای جدول SalesPerson در مدل انجام شود.

رتبه‌بندی زمانی: مقایسه عملکرد در طول دوره

سناریوی رایج دیگر، رتبه‌بندی یک واحد فروش، مانند یک محصول یا فروشنده، بر اساس عملکرد ماهانه در طول یک سال است. در نوشتن این نوع Measure باید به‌دقت مشخص شود رتبه‌بندی دقیقاً در چه محدوده‌ای از ماه‌ها انجام می‌شود، زیرا این نوع رتبه‌بندی یکی از رایج‌ترین منابع خطا در Measureهای RANKX است.

اگر جدول Date شامل چند سال باشد، ستون MonthNumber به‌تنهایی هر ماه را به‌صورت یکتا مشخص نمی‌کند؛ برای مثال ژانویه ۲۰۲۵ و ژانویه ۲۰۲۶ هر دو مقدار MonthNumber برابر با یک دارند. اگر هدف رتبه‌بندی دوازده ماه یک سال مشخص در میان خودشان باشد، باید ترکیبی از ستون‌های شناسایی‌کننده ماه در ALL لحاظ شود تا از ادغام نادرست ماه‌های هم‌شماره در سال‌های مختلف جلوگیری شود.

Monthly Sales Rank =
RANKX (
    ALL ( 'Date'[MonthName], 'Date'[MonthNumber] ),
    [Total Sales],
    ,
    DESC,
    Dense
)

در این Measure، فیلتر ماه حذف می‌شود اما سایر فیلترهای فعال روی جدول Date، از جمله فیلتر سال، حفظ می‌شوند. بنابراین اگر کاربر یک سال مشخص را انتخاب کرده باشد، رتبه‌بندی در میان ماه‌های همان سال انجام خواهد شد. نکته مهم این است که MonthNumber به‌تنهایی برای مدل‌های چندساله یک شناسه یکتا برای ماه نیست و نباید تصور کرد ترکیب MonthName و MonthNumber می‌تواند ماه‌های سال‌های مختلف را از یکدیگر تفکیک کند. اگر هدف رتبه‌بندی ماه‌ها در چند سال به‌صورت هم‌زمان باشد، باید یک ستون ماه یکتا مانند YearMonth در مدل Date وجود داشته باشد و همان ستون مبنای رتبه‌بندی قرار گیرد.

اگر هدف حفظ انتخاب‌های کاربر در Slicer باشد، به‌طوری‌که رتبه‌بندی فقط در میان ماه‌های موجود در انتخاب فعلی کاربر انجام شود، نه تمام ماه‌های موجود در مدل، ALLSELECTED می‌تواند الگوی مناسب‌تری نسبت به ALL باشد.

Monthly Sales Rank Within Selection =
RANKX (
    ALLSELECTED ( 'Date'[MonthName], 'Date'[MonthNumber] ),
    [Total Sales],
    ,
    DESC,
    Dense
)

این تفاوت میان ALL و ALLSELECTED در رتبه‌بندی زمانی اهمیت زیادی دارد؛ ALL همیشه کل مجموعه موجود در مدل را در نظر می‌گیرد، صرف‌نظر از اینکه کاربر در Slicer چه بازه‌ای را انتخاب کرده، در حالی که ALLSELECTED رتبه‌بندی را به همان بازه انتخابی کاربر محدود می‌کند. انتخاب نادرست بین این دو یکی از رایج‌ترین جاهایی است که کاربران Power BI در نوشتن Measureهای رتبه‌بندی زمانی دچار اشتباه می‌شوند.

استفاده از RANKX برای فیلتر کردن N رکورد برتر

یکی از کاربردهای عملی مهم RANKX، ترکیب آن با FILTER برای استخراج N رکورد برتر یا پایین‌تر است.

Top 5 Products Sales =
VAR RankedProducts =
    FILTER (
        ADDCOLUMNS (
            ALL ( Product[ProductName] ),
            "ProductRank", RANKX ( ALL ( Product[ProductName] ), [Total Sales] )
        ),
        [ProductRank] <= 5
    )
RETURN
    SUMX ( RankedProducts, [Total Sales] )

در این Measure، ابتدا با ADDCOLUMNS یک جدول مجازی شامل رتبه هر محصول ساخته می‌شود، سپس با FILTER تنها محصولاتی که رتبه آن‌ها کمتر یا مساوی پنج است نگه داشته می‌شوند و در نهایت مجموع فروش این محصولات محاسبه می‌گردد.

نکته مهمی که باید در استفاده از این الگو در نظر گرفت این است که «پنج محصول برتر» لزوماً به معنای دقیقاً پنج رکورد نیست. اگر رتبه پنجم بین چند محصول مساوی باشد، تمام آن محصولات هم‌رتبه وارد نتیجه خواهند شد. برای مثال اگر توزیع رتبه‌ها به شکل یک، دو، سه، چهار، پنج، پنج، پنج باشد، شرط [ProductRank] <= 5 هفت محصول را انتخاب می‌کند، نه پنج محصول. این رفتار خاص این الگو نیست؛ خود تابع TOPN نیز در صورت وجود تساوی در رتبه N، می‌تواند بیش از N ردیف بازگرداند، رفتاری که در مستندات رسمی Microsoft نیز صراحتاً ذکر شده است.

بنابراین استفاده از RANKX در این الگو امکان اعمال منطق رتبه‌بندی مبتنی بر Skip یا Dense را فراهم می‌کند و کنترل روشنی بر نحوه محاسبه خود عدد رتبه می‌دهد، اما اگر هدف انتخاب دقیقاً و قطعاً N ردیف بدون هیچ رکورد اضافی باشد، صرفاً فیلتر کردن بر اساس رتبه کافی نیست و باید منطق انتخاب Top N، برای مثال با محدود کردن اضافی بر اساس یک معیار دوم برای شکستن تساوی، به‌صورت جداگانه طراحی شود.

نکته: این الگو برای آموزش نحوه ترکیب RANKX و FILTER مناسب است، اما در مدل‌های بزرگ باید Query Plan و Server Timings بررسی شود و در بسیاری از سناریوهای صرفاً Top N، استفاده مستقیم از TOPN می‌تواند انتخاب مناسب‌تری باشد.

RANKX یا RANK در DAX

در کنار RANKX، نسخه‌های جدیدتر DAX تابع مستقلی به نام RANK نیز معرفی کرده‌اند که امکاناتی برای رتبه‌بندی بر پایه یک رابطه مشخص، بند OrderBy و Partition ارائه می‌دهد و از نظر Syntax به توابع Window در زبان‌های Query نزدیک‌تر است. این تابع مسیر جدیدتری برای برخی سناریوهای رتبه‌بندی فراهم می‌کند که در آن‌ها نیازی به نوشتن صریح جدول ورودی و Expression مانند RANKX نیست.

با این حال، RANKX همچنان در انبوه بزرگی از مدل‌ها و Measureهای موجود در پروژه‌های Power BI مورد استفاده قرار می‌گیرد و برای سناریوهای کلاسیک رتبه‌بندی روی یک Table Expression دلخواه، ابزاری بسیار انعطاف‌پذیر و شناخته‌شده محسوب می‌شود. این مقاله تمرکز خود را بر RANKX حفظ می‌کند، زیرا شناخت عمیق آن برای درک و نگهداری مدل‌های موجود سازمانی ضروری است؛ با این حال توسعه‌دهندگانی که با نسخه‌های جدید DAX کار می‌کنند، باید از وجود RANK به‌عنوان یک مسیر جایگزین و تکمیلی نیز آگاه باشند و بررسی کنند کدام یک برای ساختار مدل و نسخه ابزار مورد استفاده آن‌ها مناسب‌تر است.

بررسی Performance تابع RANKX

از منظر معماری موتور DAX، RANKX برای محاسبه رتبه هر عنصر، باید Expression موردنظر را برای تمام اعضای جدول ورودی ارزیابی کند. این بدان معناست که اگر جدول ورودی شامل هزاران عضو باشد و Expression شامل یک CALCULATE پرهزینه باشد، هزینه محاسباتی می‌تواند به شکل قابل توجهی افزایش یابد. هزینه واقعی RANKX در هر سناریوی مشخص به عوامل متعددی مانند تعداد اعضای جدول ورودی، پیچیدگی Expression، Cardinality ستون‌های درگیر، ساختار مدل داده و نوع Visual مصرف‌کننده Measure بستگی دارد، بنابراین نمی‌توان به‌طور قطعی درباره هزینه اجرایی آن بدون در نظر گرفتن این عوامل نظر داد.

تأثیر اندازه جدول ورودی بر هزینه اجرا

پرهزینه در حجم بالا:
Customer Rank =
RANKX (
    ALL ( Customer[CustomerID] ),
    [Total Sales]
)

در Visualهایی که تعداد زیادی عضو را هم‌زمان نمایش می‌دهند، دامنه بزرگ RANKX می‌تواند هزینه محاسباتی قابل توجهی ایجاد کند. میزان تکرار محاسبات و امکان استفاده مجدد از نتایج نیز به Query Shape و بهینه‌سازی‌های داخلی موتور VertiPaq و Formula Engine وابسته است. بنابراین نباید هزینه اجرای RANKX را صرفاً بر اساس تعداد ردیف‌های Visual تخمین زد و بهتر است رفتار واقعی Measure با Server Timings بررسی شود.

این الگو مشتریانی را که مقدار [Total Sales] آن‌ها صفر یا کمتر است از مجموعه رتبه‌بندی حذف می‌کند و در برخی سناریوها می‌تواند دامنه‌ای را که RANKX روی آن رتبه‌بندی انجام می‌دهد کاهش دهد. با این حال، خود FILTER برای ارزیابی شرط [Total Sales] > 0 نیازمند محاسبه این Measure برای اعضای جدول است، بنابراین نمی‌توان صرفاً با مشاهده این کد نتیجه گرفت که Performance حتماً بهتر خواهد شد. اثر واقعی این تغییر به اندازه جدول، پیچیدگی Measure، تعداد مشتریان و سایر شرایط مدل بستگی دارد و باید با ابزارهایی مانند DAX Studio و Server Timings روی داده واقعی اندازه‌گیری شود.

یک رویکرد احتمالی برای محدود کردن دامنه رتبه‌بندی:
Active Customer Rank =
RANKX (
    FILTER (
        ALL ( Customer[CustomerID] ),
        [Total Sales] > 0
    ),
    [Total Sales]
)

این تغییر مشتریانی که هیچ فروشی نداشته‌اند را از فرآیند رتبه‌بندی حذف می‌کند و می‌تواند در برخی مدل‌ها حجم محاسبات را به‌طور محسوس کاهش دهد، البته باید توجه داشت که خود FILTER نیز هزینه اجرایی دارد و این بهینه‌سازی باید با اندازه‌گیری واقعی Performance تأیید شود.

پرهزینه بودن Expression پیچیده داخل RANKX

هرچه Expression پارامتر دوم RANKX پیچیده‌تر باشد، هزینه کلی افزایش می‌یابد، زیرا این هزینه به ازای تمام اعضای جدول ورودی تکرار می‌شود.

بسیار پرهزینه:
Complex Rank =
RANKX (
    ALL ( Product[ProductName] ),
    CALCULATE (
        SUMX (
            FILTER (
                Sales,
                Sales[Discount] > 0
            ),
            Sales[Amount] * ( 1 - Sales[DiscountRate] )
        )
    )
)

در این مثال، برای هر محصول ابتدا یک FILTER روی جدول Sales اجرا می‌شود و سپس با استفاده از SUMX، محاسبه موردنظر روی نتیجه آن انجام می‌گیرد. تکرار این زنجیره محاسباتی برای تمام محصولات می‌تواند در مدل‌هایی که جدول Fact حجم بالایی دارد، هزینه پردازشی قابل توجهی ایجاد کند. برای کنترل بهتر این هزینه، می‌توان منطق محاسبه پایه را در یک Measure مستقل قرار داد و سپس همان Measure را در RANKX فراخوانی کرد. این تفکیک علاوه بر خوانایی بیشتر کد، امکان بررسی و بهینه‌سازی مستقل Measure پایه و ارزیابی دقیق‌تر Performance با ابزارهایی مانند DAX Studio را فراهم می‌کند.

تحلیل رفتار RANKX با DAX Studio

برای بررسی دقیق هزینه اجرایی یک Measure مبتنی بر RANKX، بهترین روش استفاده از DAX Studio و مشاهده Server Timings است. در بسیاری از سناریوهای RANKX، به‌خصوص زمانی که Expression پیچیده باشد، می‌توان انتظار داشت بخش مهمی از هزینه محاسبات در Formula Engine ایجاد شود، زیرا RANKX ماهیتاً یک عملیات مقایسه‌ای و ردیف‌محور است که به‌سختی قابل Pushdown کامل به Storage Engine است. با این حال، رفتار واقعی هر Measure مشخص باید با Server Timings در DAX Studio بررسی شود، نه صرفاً بر اساس یک قاعده کلی درباره RANKX. در پروژه‌های سازمانی با گزارش‌های رتبه‌بندی پرکاربرد روی جداول بزرگ، این تحلیل باید پیش از استقرار نهایی Measure انجام شود.

مقایسه RANKX با سایر رویکردهای رتبه‌بندی

در برخی سناریوهای ساده، ممکن است بتوان به‌جای RANKX از ترکیب TOPN و COUNTROWS برای شبیه‌سازی رتبه استفاده کرد، اما این رویکرد معمولاً پیچیده‌تر و کمتر خوانا از استفاده مستقیم RANKX است و کنترل دقیقی روی رفتار تساوی رتبه ارائه نمی‌دهد. RANKX به دلیل داشتن پارامترهای اختصاصی برای جهت رتبه‌بندی و مدیریت تساوی، یکی از استانداردترین و شفاف‌ترین ابزارها برای این هدف در DAX محسوب می‌شود، در کنار تابع جدیدتر RANK که در بخش قبل معرفی شد.

نکته‌ای که باید در نظر گرفت این است که RANKX، برخلاف توابع Ranking در SQL Server مانند RANK و DENSE_RANK که در سطح یک Query ثابت اجرا می‌شوند، رفتاری کاملاً پویا دارد و رتبه هر عنصر بسته به فیلترهای فعال در گزارش، به‌طور آنی بازمحاسبه می‌شود. این ویژگی قدرت اصلی RANKX در Power BI است، اما همان دلیلی است که هزینه اجرایی آن نمی‌تواند به‌سادگی با توابع Ranking در یک محیط Query ثابت مقایسه شود.

اشتباهات رایج در استفاده از RANKX

فراموش کردن حذف فیلتر با ALL یا ALLEXCEPT

رایج‌ترین اشتباه، فراموش کردن حذف فیلتر جدولی است که رتبه‌بندی باید در چارچوب کامل آن انجام شود. بدون ALL یا ALLEXCEPT، در بسیاری از Visualها هر ردیف تنها یک عضو در چارچوب رتبه‌بندی خواهد داشت و نتیجه برای اکثر سطرها رتبه یک خواهد بود.

نادیده گرفتن پارامتر Order در معیارهای معکوس

در Measureهایی که معیار رتبه‌بندی ماهیت معکوس دارد، مانند هزینه یا زمان تحویل، استفاده از جهت پیش‌فرض DESC بدون توجه به منطق کسب‌وکار می‌تواند نتیجه‌ای کاملاً معکوس با انتظار کاربر نهایی تولید کند.

رتبه‌بندی نادرست بین بازه‌های زمانی هم‌شماره

همان‌طور که در بخش رتبه‌بندی زمانی بررسی شد، استفاده از یک ستون تنها مانند MonthNumber بدون در نظر گرفتن سال، در مدل‌های چندساله می‌تواند باعث ادغام نادرست ماه‌های هم‌شماره از سال‌های مختلف در فرآیند رتبه‌بندی شود.

استفاده از Expression پیچیده و پرهزینه در پارامتر دوم بدون بررسی Performance

از آنجا که Expression پارامتر دوم برای تمام اعضای جدول ورودی ارزیابی می‌شود، هر پیچیدگی اضافه در این Expression به همان نسبت در کل جدول تکرار می‌شود. این هزینه در جداول کوچک محسوس نیست، اما در مدل‌های سازمانی حجیم می‌تواند به‌سرعت به یک مشکل Performance تبدیل شود.

عدم توجه به تفاوت Skip و Dense در گزارش‌های حساس به رتبه

در گزارش‌هایی که رتبه دقیق هر عنصر برای تصمیم‌گیری مدیریتی اهمیت دارد، مانند تخصیص پاداش بر اساس رتبه فروش، انتخاب نادرست بین Skip و Dense می‌تواند منجر به نتیجه‌ای شود که از نظر کسب‌وکار عادلانه یا منطبق با سیاست سازمان نیست.

Best Practiceهای استفاده از RANKX در پروژه‌های سازمانی

پیش از نوشتن هر Measure مبتنی بر RANKX، باید دقیقاً مشخص شود رتبه‌بندی باید در چه چارچوبی انجام شود: کل مجموعه، یک گروه مشخص یا یک بازه زمانی. این تصمیم مستقیماً انتخاب بین ALL، ALLEXCEPT و ALLSELECTED را تعیین می‌کند.

جهت رتبه‌بندی باید متناسب با ماهیت معیار موردنظر به‌صورت صریح تعیین شود، حتی اگر مقدار پیش‌فرض DESC با نیاز فعلی همخوانی داشته باشد، زیرا ذکر صریح پارامتر خوانایی کد را برای تیم توسعه بعدی افزایش می‌دهد.

انتخاب بین Skip و Dense باید بر اساس نیاز واقعی گزارش و سیاست‌های مدیریتی مرتبط با آن رتبه‌بندی انجام شود، نه صرفاً بر اساس رفتار پیش‌فرض تابع.

در رتبه‌بندی مقادیر اعشاری حساس، استفاده از ROUND برای گرد کردن Expression پیش از مقایسه، از رتبه‌بندی نادرست ناشی از خطای دقت ممیز شناور جلوگیری می‌کند.

Expression پارامتر دوم RANKX باید تا حد امکان ساده نگه داشته شود. اگر محاسبه پایه پیچیده است، بهتر است ابتدا در یک Measure مستقل تعریف و سپس در RANKX فراخوانی شود.

پیش از استقرار Measureهای رتبه‌بندی روی جداول بزرگ در محیط عملیاتی، Performance آن‌ها باید با DAX Studio روی داده واقعی سازمان اندازه‌گیری شود، به‌خصوص اگر قرار است این Measure در Visualهایی با تعداد زیاد ردیف هم‌زمان نمایش داده شود.

نتیجه‌گیری

RANKX یکی از ابزارهای اصلی DAX برای پیاده‌سازی رتبه‌بندی پویا در گزارش‌های Power BI است که برخلاف رتبه‌بندی ایستا در ابزارهایی مانند Excel، می‌تواند به‌طور کامل با Filter Context گزارش هماهنگ شود و رتبه هر عنصر را متناسب با فیلترهای فعال، به‌صورت آنی بازمحاسبه کند. تسلط بر پارامترهای این تابع، از جمله انتخاب صحیح جدول ورودی با ALL، ALLEXCEPT یا ALLSELECTED، تعیین جهت مناسب با Order، مدیریت آگاهانه تساوی رتبه با Skip یا Dense و توجه به رفتار BLANK و دقت اعشاری، برای طراحی گزارش‌های رتبه‌بندی دقیق و قابل اعتماد ضروری است.

از منظر Performance، باید توجه داشت که هزینه اجرایی RANKX به عوامل متعددی از جمله اندازه جدول ورودی و پیچیدگی Expression بستگی دارد و در برخی سناریوها می‌تواند از نظر محاسباتی پرهزینه باشد. استفاده از آن روی جداول بزرگ یا با Expressionهای پیچیده باید همراه با اندازه‌گیری دقیق Performance در DAX Studio و در صورت نیاز محدودسازی جدول ورودی انجام شود. همچنین آشنایی با تابع جدیدتر RANK در DAX، به‌عنوان یک مسیر جایگزین در برخی سناریوها، به‌روز نگه‌داشتن مهارت طراحی Measureهای رتبه‌بندی را تضمین می‌کند.

سوالات متداول (FAQ)

1. چرا Measure مبتنی بر RANKX من همیشه رتبه یک برمی‌گرداند؟
این مشکل معمولاً به این دلیل رخ می‌دهد که جدول ورودی RANKX فیلتر فعلی روی همان بعد رتبه‌بندی، مانند محصول یا مشتری، را حفظ کرده است، برای مثال با استفاده از VALUES به‌جای ALL. با استفاده از ALL یا ALLEXCEPT روی ستون موردنظر، این فیلتر حذف و چارچوب کامل رتبه‌بندی در اختیار تابع قرار می‌گیرد.

2. تفاوت پارامتر Skip و Dense در RANKX چیست؟
در حالت Skip که پیش‌فرض است، رتبه بعدی پس از یک گروه تکراری متناسب با تعداد اعضای آن گروه جهش می‌کند. در حالت Dense، رتبه بعدی بدون جهش و بلافاصله پس از رتبه‌های تکراری ادامه می‌یابد.

3. چگونه می‌توان رتبه هر فروشنده را فقط در میان همکاران شعبه خودش محاسبه کرد؟
با استفاده از ALLEXCEPT روی جدول SalesPerson با حفظ ستون شعبه، یا به‌طور جایگزین با استفاده از ALL مستقیماً روی ستون نام فروشنده، در صورتی که فیلتر شعبه از طریق Visual یا مدل به‌درستی روی داده اعمال شده باشد.

4. آیا RANKX همیشه کند است؟
خیر، هزینه اجرایی RANKX به عواملی مانند اندازه جدول ورودی، پیچیدگی Expression و ساختار مدل داده بستگی دارد. در جداول کوچک تا متوسط معمولاً مشکلی ایجاد نمی‌کند، اما در جداول بزرگ با Expressionهای پیچیده می‌تواند از نظر محاسباتی پرهزینه شود و بهتر است با DAX Studio بررسی شود.

5. چه زمانی باید به‌جای RANKX از TOPN یا RANK استفاده کرد؟
اگر هدف صرفاً استخراج N رکورد برتر بدون نیاز به نمایش عدد رتبه است، TOPN مسیر مستقیم‌تری است، هرچند باید به رفتار آن در برابر تساوی رتبه در مرز N توجه شود. تابع جدیدتر RANK نیز برای سناریوهایی که نیاز به رتبه‌بندی بر پایه Partition و OrderBy مشابه توابع Window دارند، گزینه‌ای قابل بررسی است. RANKX همچنان برای رتبه‌بندی پویا روی یک Table Expression دلخواه، با کنترل دقیق روی جدول ورودی و رفتار تساوی، انتخاب رایج و منعطفی است.

پیشنهاد مطالعه

آیا رتبه‌بندی در Power BI برای شما به یک چالش تبدیل شده است؟

طراحی یک Measure با RANKX فقط به نوشتن چند خط DAX محدود نمی‌شود. انتخاب صحیح Filter Context، تعیین دامنه رتبه‌بندی، مدیریت تساوی‌ها و بررسی Performance می‌تواند مستقیماً روی دقت گزارش و سرعت اجرای آن تأثیر بگذارد.

اگر در مدل Power BI خود با رتبه‌بندی محصولات، مشتریان، فروشندگان یا شاخص‌های عملکردی مواجه هستید و نتیجه رتبه‌بندی با انتظار شما مطابقت ندارد، تیم توسعه فناوری اطلاعات لاندا می‌تواند در طراحی و بهینه‌سازی مدل‌های Power BI، Measureهای DAX و Performance گزارش‌های سازمانی به شما کمک کند.

برای دریافت مشاوره تخصصی، ارزیابی Performance یا برگزاری دوره‌های آموزشی عملی DAX، با کارشناسان لاندا تماس  بگیرید.

No comment

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *