استفاده از VALUES در توابع تکرارگر (Iterator) در DAX
در یک مقالهی قبلی با عنوان تفاوت توابع DISTINCT و VALUES برای مدلسازی دادهها در Power BI توضیح دادیم که چگونه باید تابع مناسب را برای تکرار روی مقادیر یکتای یک ستون در کانتکست فیلتر فعلی انتخاب کنید. توصیه میکنیم قبل از شروع این مقاله، آن مطلب را مطالعه کنید؛ زیرا همان مشکل زیربنایی را توضیح میدهد. در اینجا، موضوع بحث این است که آیا باید از VALUES در یک تکرارگر (iterator) استفاده کنیم یا خیر. این انتخاب بستگی به پاسخ این سؤال دارد:
آیا میخواهید ردیف blank (خالی) که بهدلیل یک ارتباط نامعتبر ایجاد میشود را هنگام تکرار روی ردیفهای یک مرجع جدول، شامل کنید یا حذف نمایید؟
مقالهی اشارهشده، توابع DISTINCT و VALUES را در حالتی تحلیل میکند که آرگومان آنها یک ارجاع ستون (column reference) باشد. با این حال، هر دوی DISTINCT و VALUES میتوانند یک جدول (table) را بهعنوان آرگومان دریافت کنند. با این وجود، تفاوت مهمی میان آنها وجود دارد که در معنا و کاربردشان بازتاب دارد:
DISTINCT میتواند هر عبارت جدول (table expression) را بپذیرد و ردیفهای تکراری را حذف کند.
VALUES تنها یک مرجع جدول (table reference) را میپذیرد و نه هر عبارت جدول.
هدف اصلی استفاده از VALUES با آرگومان مرجع جدول این است که یک ردیف اضافهی blank (در صورت وجود) را که بهدلیل یک ارتباط نامعتبر ایجاد شده، بازگرداند. علاوه بر این، اگر جدولی که مرجع آن داده شده فاقد محدودیت یکتایی (unique constraint) برای ستونها باشد، VALUES ردیفهای تکراری را حذف نمیکند. بنابراین، میتوانیم DISTINCT را در زمینهی مشکل ردیف blank نادیده بگیریم.
زمانی که از یک مرجع جدول استفاده میکنیم، دو حالت ممکن است رخ دهد:
مرجع جدول، ردیف blank ناشی از ارتباط نامعتبر را شامل نمیشود.
استفاده از
VALUES ( table )
باعث میشود ردیف blank ناشی از ارتباط نامعتبر نیز بازگردانده شود.
در صورتی که ارتباط معتبر باشد، یا زمانی که جدول در سمت «one» یک رابطهی یکبهچند قرار دارد و محدودیت یکتا ندارد، نتایج VALUES ( table )
و خود جدول یکسان خواهند بود. بنابراین میتوانیم فرض کنیم که همین مشکل (وجود ردیف blank) هنگام تکرار روی مرجع جدول نیز ممکن است وجود داشته باشد.
بهعنوان مثال، در نظر بگیرید که میخواهیم درآمدها را برای کشورهای اروپایی با یک درصد تعدیل کنیم:
Net Cashback (incorrect) =
SUMX (
Customer,
[Sales Amount] * (1 - Customer[% Cashback])
)
«آنچه میبینیم همان مشکلی است که در مقالهی قبلی دربارهی DISTINCT و VALUES توضیح دادیم؛ جایی که در مِژر Sales Adjusted (incorrect) با استفاده از SUMX روی خروجی DISTINCT (با آرگومان ارجاعِ ستون) تکرار کردیم. اینجا هم همان مشکل را داریم، چون تکرار روی یک table reference عملاً باعث نادیده گرفتن ردیفِ blank اضافی در صورت وجودِ یک invalid relationship میشود.»
«در این مورد هم راهحل استفاده از VALUES است؛ اما این بار آن را دورِ table reference بهکار میبریم، نه بهعنوان جایگزین DISTINCT؛ زیرا فرمول اولیه (نادرست) اصلاً DISTINCT نداشت و فقط از مرجع جدول Customer استفاده کرده بود:»
Net Cashback =
SUMX (
VALUES ( Customer ),
[Sales Amount] * (1 - Customer[% Cashback])
)
«حالتی که باید از table reference استفاده کنید و نباید از VALUES استفاده شود، مشابه همان چیزی است که برای DISTINCT توضیح دادیم، یعنی در زمان استفاده از تکرارگرهایی مانند AVERAGEX، MINX و MAXX. برای مثال، مِژری که بیشترین مقدار فروش را برای هر مشتری بازمیگرداند، نباید از VALUES استفاده کند؛ همانطور که در نسخهی “نادرست” انجام دادهایم:»
Max Customer =
MAXX (
Customer,
[Sales Amount]
)
Max Customer (incorrect) =
MAXX (
VALUES ( Customer ),
[Sales Amount]
)
در واقع، نسخهای که با VALUES نوشته شده نتیجهای نادرست تولید میکند؛ جایی که مقدار کل و مقدار مربوط به یک کشور خالی، برابر است با مجموع تمام مشتریان “ناشناس”. وقتی در ویژوال هیچ ویژگی (attribute) مربوط به مشتری وجود نداشته باشد، وضعیت بدتر هم میشود؛ همانطور که در ماتریس بر اساس برند نشان داده شده است. مقادیر در این حالت بزرگنمایی میشوند اما در نگاه اول بهراحتی بهعنوان مقادیر نادرست قابل تشخیص نیستند. ممکن است برندی (مثل Northwind Traders) در هر دو مژر مقدار یکسانی را نشان دهد، اما اغلب اوقات، مژر نادرست عدد بزرگتری نسبت به مژر صحیح نمایش میدهد.
بنابراین، نباید بدون فکر از VALUES در هر تکرارگر (iterator) استفاده کنید؛ انتخاب بین استفاده از یک table reference یا VALUES با همان مرجع جدول بستگی به نیاز محاسباتی دارد. با این حال، در حالی که استفاده از VALUES بهعنوان پیشفرض (برای دلایلی که توضیح داده شد، بهویژه هنگام تکرار روی مقادیر یکتای یک ستون) توصیه میشود، باید بپذیریم که بیشتر کدهای موجود حتی در مواقعی که طبق بهترین شیوهها باید از VALUES استفاده کنند، این کار را نمیکنند. با این وجود، هیچ پیامد منفی برای این کار وجود ندارد، زیرا زمانی که حداقل یکی از شرایط زیر برقرار باشد، نتیجه یکسان خواهد بود:
عبارت فقط مقادیر را با استفاده از ارجاع به ستونها (column references) بازیابی کند و هیچ ارجاعی به مژرها نداشته باشد.
مثالها:
بدون تفاوت:
SUMX ( table, table[column1] * table[column2] )
نتیجه متفاوت:
SUMX ( table, table[column1] * [measure3] )
عبارت شامل توابعی که باعث context transition میشوند (مانند CALCULATE و RELATEDTABLE) نباشد.
مثالها:
بدون تفاوت:
SUMX ( table, IF ( LEFT ( table[column1], 1 ) = "A", table[column2], table[column3] ) )
نتیجه متفاوت:
SUMX ( table1, table1[column1] * CALCULATE ( SUM ( table2[column2] ) ) )
جدولی که روی آن تکرار میکنیم، هیچ ستونی در سمت one یک رابطهی عادی (regular relationship) نداشته باشد.
مثالها:
بدون تفاوت: جدول Sales هیچ ستونی در سمت one از یک رابطهی many-to-one ندارد.
نتیجه متفاوت: جدول Customer شامل CustomerKey است که در سمت one از رابطهی یکبهچند بین Customer–Sales قرار دارد.
به همین دلیل، لازم نیست اگر نبود VALUES را در یک iterator مثل SUMX مشاهده کردید، حتماً کد موجود را بازنویسی کنید، چرا که معمولاً یکی از شرایطی برقرار است که باعث میشود استفاده از VALUES ضرورتی نداشته باشد. با این حال، هیچ هزینه یا پیامد اضافی برای استفاده از VALUES در زمانی که نیازی به آن نیست وجود ندارد، مادامی که همچنان معنای صحیح محاسبه را ارائه دهد.
دیدگاهتان را بنویسید