تفاوت توابع DISTINCT و VALUES برای مدلسازی دادهها در Power BI
فهرست مطالب
وقتی برای اولین بار مدلسازی با زبان DAX را شروع میکنید، ممکن است به نظر برسد که توابع DISTINCT
و VALUES
قابل جایگزینی با یکدیگر هستند؛ چرا که هر دو، لیستی از مقادیر منحصربهفرد یک ستون را در زمینه فیلتر فعلی برمیگردانند. در یک مدل تمیز و ساده، این دو تابع تقریباً رفتار یکسانی دارند، بهطوریکه انتخاب یکی از آنها ممکن است تصادفی باشد — یا حتی بدتر، بدون توجه بین آنها جابجا شوید.
اما واقعیت این است که این دو تابع یکسان نیستند. تفاوت ظریف میان آنها در مدلهای واقعی و عملیاتی اهمیت حیاتی دارد؛ مخصوصاً در شرایطی که دادهها ممکن است دارای مشکلاتی مانند روابط نادرست یا دادههای ناقص باشند.
در این مقاله بررسی میکنیم:
رفتار فنی هر یک از این توابع چگونه است
چرا وجود تنها یک «ردیف خالی اضافی» (blank row) میتواند نتیجه را به کلی تغییر دهد
یک قانون کلی ساده که به شما میگوید کدام تابع را کجا استفاده کنید
بررسی موارد خاص: تکرار بر روی جدولها، چند ستونهها، و محاسبات آماری
در پایان مقاله، خواهید دانست که چرا معمولاً باید از VALUES
بهعنوان انتخاب پیشفرض استفاده کنید و چه زمانی استفاده از DISTINCT
ارجح است.
توجه: برای درک بهتر تفاوتها، در این مقاله از توابعی استفاده شده که بهینهسازی نشدهاند و صرفاً برای نمایش تفاوتهای رفتاری در موقعیتهای ساده طراحی شدهاند. در مدلهای واقعی، این تفاوتها ممکن است در قالبی بسیار پیچیدهتر ظاهر شوند؛ بنابراین، این مثالها را آموزشی در نظر بگیرید نه الگوی نهایی برای نوشتن محاسبات.
تفاوتهای فنی بین DISTINCT و VALUES در DAX
DISTINCT
و VALUES
دو تابع با امضای (signature) مشابه هستند. هر دو معمولاً یک پارامتر میپذیرند که معمولاً یک ارجاع به ستون (column reference) است، و جدول کوچکی از مقادیر منحصربهفرد را برمیگردانند. اما تفاوت اصلی در نحوه رفتار با مقادیر BLANK و تعامل با روابط مدل دادهای است.
DISTINCT ( )
VALUES ( )
با بررسی امضای توابع (Function Signature) DISTINCT
و VALUES
، متوجه میشویم که تابع VALUES
میتواند علاوه بر ستون، یک جدول کامل نیز بهعنوان ورودی بپذیرد، در حالی که DISTINCT
تنها میتواند یک عبارت جدول کلیتر (generic table expression) دریافت کند.
در این بخش از مقاله، تمرکز ما بر استفاده از این توابع با ارجاع ستونی (column reference) است.
شباهت ظاهری در رفتار
در نگاه اول، رفتار دو تابع مشابه به نظر میرسد؛ چرا که هر دو لیستی از مقادیر یکتا از یک ستون را در زمینه فیلتر فعلی (filter context) بازمیگردانند. اما تفاوت کلیدی در جایی بروز میکند که شرایط خاصی برقرار باشد.
تابع VALUES
ممکن است یک ردیف اضافهی BLANK نیز بازگرداند، البته فقط اگر تمام شرایط زیر برقرار باشند:
ستونی که در فرمول استفاده میشود متعلق به جدول سمت “یک” در یک رابطهی یکبهچند (regular relationship) باشد.
رابطهی معمول (regular relationship) نامعتبر (invalid) شده باشد.
زمینه فیلتر (filter context) مانع از نمایش ردیف BLANK اضافه نشده باشد.
مقدار BLANK برای آن ستون در دادههای فیلتر شده قبلی وجود نداشته باشد.
نکته فنی: ردیف BLANK در روابط نامعتبر
برای درک بهتر موارد بالا باید به نکتهای مهم توجه کنیم:
اگر از ستونی در سمت “یک” یک رابطهی یکبهچند مقدار دریافت کنیم، مقدار BLANK بهطور مستقیم نمیتواند در آن ستون وجود داشته باشد. این مقدار بهصورت رزرو شده برای ردیف ویژهای است که موتور DAX در صورت نامعتبر بودن رابطه، بهصورت خودکار ایجاد میکند.
در واقع، یک رابطهی معمول در مدل دادهای (یعنی یکبهچند یا یکبهیک) بین دو جدول برقرار است، بهگونهای که ستون سمت “یک” دارای مقادیر یکتا (primary key) است. اگر مقداری در جدول سمت “چند” وجود داشته باشد که معادل آن در جدول سمت “یک” نباشد، رابطه نامعتبر تلقی میشود و DAX بهصورت خودکار یک ردیف BLANK در جدول سمت “یک” ایجاد میکند. همهی ردیفهای نامطابق از سمت “چند” به این ردیف BLANK مرتبط میشوند.
در مقابل، روابط محدود (مانند many-to-many یا روابط دوطرفه (bidirectional)) چنین ردیفی اضافه نمیکنند؛ در نتیجه در این شرایط، DISTINCT
و VALUES
رفتاری یکسان دارند.
(DAX: تفاوت DISTINCT و VALUES هنگام استفاده در توابع تکرارکننده مثل SUMX و FILTER)
هنگامیکه از یک ستون در توابع تکرارشوندهای مانند SUMX
، AVERAGEX
، FILTER
و… استفاده میکنید، معمولاً انتظار دارید تمام مقادیر دادهای که در خروجی مدل مؤثر هستند، در تکرار لحاظ شوند.
اگر از تابع DISTINCT
استفاده کنید، ردیف BLANK نادیده گرفته میشود؛ در نتیجه تمام ردیفهای نامطابق از سمت دیگر رابطه نیز نادیده گرفته خواهند شد.
در مقابل، اگر از VALUES
استفاده کنید، این ردیف BLANK نیز در تکرار لحاظ میشود. به همین دلیل، ردیفهای نامطابق (unmatched rows) در سمت دیگر رابطه نیز در محاسبه شرکت میکنند.
مثال عملی – تعدیل درآمد بر اساس کشور
فرض کنید میخواهیم میزان درآمد (Revenue) کشورهای اروپایی را ۱٪ افزایش دهیم. میتوانیم از یک Measure مشابه زیر استفاده کنیم:
Sales Adjusted (incorrect) =
SUMX (
DISTINCT ( Customer[Continent] ),
[Sales Amount] * IF ( Customer[Continent] == "Europe", .99, 1 )
)
فرض کنید در مدل دادهای شما، جدول فروش (Sales) شامل تراکنشهایی با مقدار CustomerKey
است که در جدول مشتریان (Customer) وجود ندارد. حتی ممکن است برخی ردیفها دارای مقدار BLANK برای Sales[CustomerKey]
باشند.
در چنین شرایطی، اگر از تابعی استفاده کنید که ردیف BLANK را نادیده میگیرد (مانند DISTINCT
)، بخشی از دادهها در محاسبه لحاظ نمیشوند و منجر به خروجی غیر دقیق و ناقص خواهد شد.
در یک گزارش نمونه، دو Measure به صورت کنار هم نمایش داده شدهاند:
Sales Adjusted (Incorrect) – که از
DISTINCT(Customer[CustomerKey])
استفاده کرده است و ردیفهای نامعتبر را نادیده گرفته است.Sales Adjusted (Correct) – که از
VALUES(Customer[CustomerKey])
استفاده کرده و تمام ردیفها، از جمله BLANK را در نظر گرفته است.
نتیجهی این مقایسه بهوضوح نشان میدهد که استفادهی نادرست از DISTINCT
باعث میشود فروش مربوط به مشتریان نامشخص یا بدون شناسه (BLANK) در خروجی نهایی دیده نشود. در حالیکه در مدلهای عملیاتی، چنین دادههایی واقعاً وجود دارند و باید در محاسبات گنجانده شوند.
در یک گزارش ماتریسی که خروجی Measureها را بر اساس ستون Customer[Country]
دستهبندی میکند، اختلاف بین دو Measure (درست و نادرست) بهراحتی قابل تشخیص است؛ چرا که تفاوت بین مجموعها مستقیماً به مقدار BLANK اولیه برای کشور نسبت داده میشود.
اما وقتی همین Measureها را بر اساس ستونی غیرمرتبط مستقیم با مشتری دستهبندی کنیم — مثلاً Product[Brand]
— تحلیل اختلاف بسیار پیچیدهتر میشود.
در این حالت، تمام ردیفها در گزارش دارای مقادیر متفاوتی بین Measure صحیح و Measure اشتباه هستند، چون هر برند ممکن است شامل فروشهایی به مشتریانی باشد که در جدول Customer وجود ندارند یا مقدار CustomerKey
آنها BLANK است. این مشتریان ناشناخته در گزارش بهصورت مستقیم دیده نمیشوند، ولی در واقع وجود دارند و اثر خود را روی خروجی گذاشتهاند.
تعریف صحیح Measure: Sales Adjusted
برای درنظر گرفتن تمام تراکنشها، حتی آنهایی که مربوط به مشتریان ناشناخته هستند، باید از VALUES
استفاده کرد. در ادامه، تعریف صحیح Measure نمایش داده شده است:
Sales Adjusted =
SUMX (
VALUES ( Customer[Continent] ),
[Sales Amount] * IF ( Customer[Continent] == "Europe", .99, 1 )
)
هنگامی که بهجای DISTINCT
از VALUES
استفاده میکنید، ردیف BLANK اضافی نیز در نظر گرفته میشود؛ بهویژه در شرایطی که رابطه بین جداول (مثلاً بین Sales
و Customer
) نامعتبر باشد. این موضوع باعث میشود که تمام تراکنشها—even those with unmatched CustomerKeys—در محاسبات لحاظ شوند.
چه زمانی استفاده از DISTINCT مناسبتر است؟
در برخی محاسبات خاص، در نظر گرفتن ردیف BLANK میتواند نتیجه را گمراهکننده کند.
بهعنوان مثال:
در توابع آماری تکرارشونده مثل
MINX
،MAXX
،AVERAGEX
یا محاسبهی درصدهای توزیع (percentile functions)، اگر همهی مشتریان ناشناخته در یک گروه مشترک (مثلاً «Unknown») تجمیع شوند، این موضوع ممکن است منجر به انحراف آماری (bias) در نتایج شود.در چنین مواقعی، نادیده گرفتن کامل ردیفهای BLANK و مشتریان ناشناخته، انتخاب بهتری است.
با این حال، این موارد استثنا هستند، نه قاعده کلی.
قانون کلی برای انتخاب بین VALUES و DISTINCT
بهطور پیشفرض از
VALUES
استفاده کنید، مگر اینکه بتوانید بهروشنی توضیح دهید که چراDISTINCT
در فرمول خاص شما ضروری و منطقی است.
به بیان دیگر، VALUES
دقت مدلسازی را در اکثر سناریوها افزایش میدهد؛ در حالیکه DISTINCT
تنها در سناریوهای خاص آماری یا زمانی که BLANK نباید در نظر گرفته شود، مفید خواهد بود.
مثال: محاسبه اشتباه میانگین فروش بر اساس شهر مشتری
در ادامه، مثالی از یک محاسبهی اشتباه برای میانگین فروش بر اساس Customer[City]
آمده است که از VALUES
استفاده میکند و باعث انحراف آماری میشود:
City Average (incorrect) =
AVERAGEX (
VALUES ( Customer[City] ),
[Sales Amount]
)
وقتی گزارش ماتریسی را بر اساس یک ویژگی مشتری مانند Customer[Country]
تقسیمبندی میکنید، وجود یک ردیف BLANK بهراحتی نمایان میشود. این ردیف BLANK نمایندهی یک مشتری واحد است که تمام مشتریان «ناشناخته» (unknown customers) را در خود جمع کرده است.
نتیجه این است که مجموع کل در ماتریس بزرگتر و نادرست به نظر میرسد، چون این گروه ناشناخته یکجا در یک ردیف خاص جمع شدهاند.
اما در گزارشهای دیگر، مانند ماتریسی که دادهها را بر اساس Product[Brand]
تقسیمبندی میکند، مقدار نادرست به صورت پراکنده در هر سلول مشاهده میشود. علت این است که ناشناختهها به چندین گروه تقسیم شدهاند و اثر آن در همه ردیفها به چشم میآید، نه فقط در یک ردیف خاص.
در محاسبه میانگین فروش بر اساس شهر، برای جلوگیری از تأثیرگذاری ردیفهای BLANK (ناشناخته) که ممکن است باعث انحراف آماری شود، باید از تابع DISTINCT
به جای VALUES
استفاده کنیم.
فرمول صحیح به شکل زیر است:
City Average =
AVERAGEX (
DISTINCT ( Customer[City] ),
[Sales Amount]
)
نتیجهگیری
توابع VALUES
و DISTINCT
تنها در یک ردیف BLANK با یکدیگر تفاوت دارند، اما همین ردیف میتواند منجر به از دست رفتن درآمد، حذف مشتریان و بروز خطاهای محاسباتی پنهان شود؛ به ویژه زمانی که مدل شما با دادههای واقعی و پیچیده مواجه میشود.
بنابراین، اتخاذ VALUES
به عنوان تابع پیشفرض برای انجام تکرارها و محاسبات، و استفاده از DISTINCT
تنها در مواردی که دلایل موجه و مشخصی وجود دارد، باعث میشود:
مقاومت و پایداری بیشتری در Measures شما ایجاد شود
زمان صرف شده برای عیبیابی و رفع خطاهای ناگهانی در ماههای بعد به شدت کاهش یابد
دیدگاهتان را بنویسید