تفاوت بین GROUPBY و SUMMARIZE
هر دو تابع GROUPBY و SUMMARIZE برای گروهبندی بر اساس ستونها مفید هستند. با این حال، از نظر عملکرد و قابلیتها تفاوتهایی دارند. دانستن جزئیات این تفاوتها به توسعهدهندگان کمک میکند تا تابع مناسب را برای سناریوی خاص خود انتخاب کنند.
DAX مجموعهای غنی از توابع را ارائه میدهد که برخی از آنها در عملکرد با یکدیگر همپوشانی دارند. در میان آنها، دو تابع GROUPBY و SUMMARIZE عملیات گروهبندی را انجام میدهند. البته این دو تنها توابع موجود نیستند: توابع SUMMARIZECOLUMNS و GROUPCROSSAPPLY نیز عملیات مشابهی انجام میدهند. با این حال، این مقاله فقط به بررسی SUMMARIZE و GROUPBY میپردازد، چرا که توابع دیگر قابلیتهای بسیار بیشتری دارند و مقایسه با آنها عادلانه نخواهد بود.
به طور خلاصه:
GROUPBY باید برای گروهبندی بر اساس ستونهای محلی (ستونهایی که به صورت آنی با استفاده از توابع DAX ایجاد میشوند) استفاده شود.
SUMMARIZE باید برای گروهبندی بر اساس ستونهای مدل یا ستونهای کوئری استفاده شود.
توجه داشته باشید که هر دو تابع میتوانند هر دو نوع ستون را گروهبندی کنند: هم ستونهای مدل و هم ستونهای محلی. اما استفاده از تابع نادرست میتواند منجر به افت شدید عملکرد شود.
اکنون بیایید نحوه عملکرد این توابع را بررسی کنیم تا اطلاعات فنی بیشتری در مورد بیانیه بالا ارائه دهیم.
معرفی SUMMARIZE
تابع SUMMARIZE دو عملیات انجام میدهد: گروهبندی و افزودن ستونهای محلی جدید. ما قبلاً در مقالهای طولانی و فنی با عنوان همه اسرار SUMMARIZE به صورت مفصل در مورد این تابع نوشتهایم. در آن مقاله، رفتار SUMMARIZE و اینکه چرا هرگز نباید از آن برای محاسبه ستونهای محلی جدید استفاده کرد، شرح داده شده است. به طور خاص، SUMMARIZE از خوشهبندی (clustering) استفاده میکند، که نوعی تکنیک گروهبندی است که – با وجود قدرتمند بودن – ممکن است منجر به نتایج غیرمنتظره و عملکرد ضعیف شود.
با این حال، برای هدف این مقایسه، ما از SUMMARIZE برای محاسبه ستونهای جدید استفاده خواهیم کرد تا رفتار خاص آن را توصیف کنیم.
وقتی SUMMARIZE در مثالهای ساده استفاده شود، بهخوبی عمل میکند و عملیات گروهبندی را به موتور ذخیرهسازی (storage engine) منتقل میکند. برای مثال، کد زیر به خوبی کار میکند و کوئریهایی مطابق انتظار را به موتور ذخیرهسازی ارسال میکند:

تابع SUMMARIZE جدول Sales را اسکن میکند، آن را بر اساس ستون Product[Brand] گروهبندی میکند و مقدار فروش را به تفکیک برند تولید مینماید.
کوئری مربوط به موتور ذخیرهسازی (Storage Engine) به شکل زیر است:

با این حال، این رفتار ساده بهراحتی از بین میرود به محض اینکه کد مربوط به Measure کمی پیچیدهتر شود. در واقع، همانطور که پیشتر اشاره کردیم، SUMMARIZE محاسبات خود را با استفاده از تکنیکی خاص به نام clustering (خوشهبندی) انجام میدهد، که در مقالهای که قبلاً به آن اشاره شد، شرح داده شده است.
به کد زیر نگاه کنید:

انتظار معقولی است که Sales All Brands مجموع کل فروش را تولید کند، چرا که CALCULATE تنها فیلتری را که در filter context وجود دارد، حذف میکند.
با این حال، این فرضیه تکنیک clustering را در نظر نمیگیرد.
به دلیل وجود clustering، فیلتری که توسط SUMMARIZE اعمال میشود، بر تمام ستونهای جدول Sales (که بهصورت expanded در نظر گرفته میشود) تأثیر میگذارد، و این موضوع منجر به این نتیجهی عجیب میشود.

همانطور که میبینید، Sales All Brands همان مقادیری را تکرار میکند که در Sales Amount دیده میشود.
توزیع متفاوت دادهها یا وجود ردیفهای تکراری ممکن است منجر به مقادیر متفاوتی شود.
علاوه بر این، به دلیل استفاده از تکنیک clustering، به محض اینکه measure مورد استفاده برای تجمیع (aggregation) ساده نباشد، SUMMARIZE مجبور میشود کل جدول را materialize (مادیسازی یا تولید فیزیکی) کند.
برای محاسبهی Sales All Brands، یکی از کوئریهایی که توسط VertiPaq اجرا میشود، به شکل زیر است:

لطفاً توجه داشته باشید که RowNumber بخشی از کوئری نیست، بنابراین سطح جزئیات (granularity) حافظه پنهان داده (datacache) دقیقاً معادل سطح جزئیات جدول Sales نیست، برخلاف حالتی که از GROUPBY استفاده میشود.
با این حال، از آنجایی که تمام ستونهای جدول بهعنوان ستونهای گروهبندی استفاده شدهاند، اندازهی این دادهها معمولاً بسیار قابلتوجه است.
همین کوئری، اگر با استفاده از ترکیب SUMMARIZE و ADDCOLUMNS نوشته شود، نتیجهای مطابق انتظار تولید میکند:

در اینجا نتیجه را مشاهده میکنید.

به لطف تکنیک clustering، تابع SUMMARIZE میتواند عملیات گروهبندی را بر اساس ستونهای محلی نیز انجام دهد.
کوئری زیر بهخوبی کار میکند، با وجود اینکه گروهبندی بر اساس یک ستون محلی انجام شده است:

نتیجه، مقدار فروش را به تفکیک اندازه تراکنش (transaction size) نمایش میدهد.

با این حال، به یاد داشته باشید که اگرچه کوئری از نظر نحوی و معنایی درست عمل میکند، نتیجه آن با استفاده از تکنیک clustering محاسبه میشود. خوشهبندی ممکن است در چندین سناریو نتایج غیرمنتظرهای تولید کند و عملکردی است که مشکلات بیشتری نسبت به راهحلها ایجاد میکند. علاوه بر این، در این حالت نیز محاسبه نیاز به مادیسازی کل جدول Sales دارد.
معرفی GROUPBY
تابع GROUPBY یک جدول را بر اساس یکی از ستونهای آن گروهبندی میکند. این ستون میتواند یک ستون مدل یا یک ستون محلی باشد. با این حال، رفتار آن بهطور قابلتوجهی متفاوت از SUMMARIZE است. GROUPBY حتی سعی نمیکند محاسبات را به موتور ذخیرهسازی منتقل کند: کل محاسبه در فرمولانجین (formula engine) پس از مادیسازی جدول انجام میشود.
GROUPBY همچنین میتواند ستونهای جدیدی به نتیجهاش اضافه کند. با این حال، به دلیل نحوهی رفتار آن، این ستونهای جدید باید بهعنوان تجمیعهای سادهای از ستونهای جدول گروهبندی شده با استفاده از تابع خاص CURRENTGROUP محاسبه شوند.
برای مثال، به کد زیر توجه کنید:

تابع GROUPBY جدول Sales را اسکن کرده و آن را بر اساس ستون Product[Brand] گروهبندی میکند. برای انجام عملیات گروهبندی، DAX ستونهای مورد نیاز جدول Sales را در یک datacache مادیسازی میکند و سپس این دادهها توسط فرمولانجین (formula engine) پردازش میشود.
در واقع، کوئری کد زیر را اجرا میکند:

از جدول Sales، DAX ستونهای Sales[Quantity]، Sales[Net Price] و Product[Brand] را بازیابی میکند. وجود ستون Sales[RowNumber] تضمین میکند که تمام ردیفها بازیابی شوند – در غیر این صورت، خود VertiPaq عملیات گروهبندی را انجام میدهد.
نتیجه، جدولی با همان تعداد ردیفها بهعنوان جدول Sales خواهد بود که در نتیجه میتواند بسیار بزرگ باشد. این جدول توسط فرمولانجین (formula engine) اسکن میشود، که آن را بر اساس ستون Product[Brand] به خوشههایی تقسیم میکند و سپس برای هر خوشه، مجموع حاصلضرب Sales[Quantity] در Sales[Net Price] را محاسبه میکند.
یکی از محدودیتهای اصلی GROUPBY این است که عبارت استفادهشده در هنگام تکرار CURRENTGROUP نمیتواند شامل context transition باشد. این محدودیت باعث میشود که استفاده از measureهای موجود بهعنوان بخشی از تکرار غیرممکن شود. همانطور که ممکن است متوجه شده باشید، در این مثال ما مجبور شدیم کد Sales Amount را بازنویسی کنیم.
با وجود اینکه ممکن است GROUPBY کند به نظر برسد، این تنها تابع DAX است که میتواند عملیات گروهبندی و محاسبات را بر روی جدولی بدون ارتباط (lineage) انجام دهد. به عنوان مثال، کوئری زیر یک جدول محلی را بر اساس یکی از ستونهای آن گروهبندی میکند و GROUPBY تنها تابعی است که قادر به انجام این عملیات است:

GROUPBY تابع مناسبی است زمانی که شما یک جدول کوچک با استفاده از توابع DAX دیگر تولید میکنید و سپس نیاز دارید بر اساس یکی از ستونها گروهبندی کرده و یک تجمیع ساده را بهصورت ردیف به ردیف انجام دهید.
انتخاب تابع مناسب
همانطور که دیدید، SUMMARIZE زمانی که نیاز دارید بر اساس ستونهای مدل گروهبندی کنید، بهخوبی عمل میکند. با وجود اینکه این تابع قابلیت گروهبندی بر اساس ستونهای محلی را نیز دارد، از تکنیک clustering استفاده میکند و نتایج آن اغلب غیرمنتظره است. از طرف دیگر، GROUPBY از clustering استفاده نمیکند. با این حال، محدودیت بسیار شدیدی دارد: همیشه جدولی را که باید گروهبندی کند، مادیسازی میکند. بنابراین، این تابع بهترین گزینه برای گروهبندی بر اساس ستونهای مدل نیست و در این مواقع، ترکیب توابع ADDCOLUMNS/SUMMARIZE معمولاً کد بهینهتری تولید میکند.
با این حال، زمانی که نیاز است یک جدول موقتی کوچک را بر اساس یک ستون محلی گروهبندی کنید، GROUPBY بهترین تابع است چون این کار را بدون وابستگی به clustering انجام میدهد.
یک توسعهدهنده DAX باتجربه تابع مناسب را برای انجام کار انتخاب میکند و معمولاً ترکیبی از توابع SUMMARIZE، ADDCOLUMNS و GROUPBY را برای دستیابی به بهترین عملکرد و نتایج درست استفاده میکند. بیایید این را با یک مثال توضیح دهیم. قبلاً کد زیر را نشان دادیم:

این کوئری از SUMMARIZE استفاده میکند، بنابراین از clustering بهره میبرد. این کوئری دو درخواست VertiPaq را اجرا میکند. اولین درخواست اساساً جدول Sales را مادیسازی میکند:

دومین درخواست موتور ذخیرهسازی (storage engine) از نتایج اولین درخواست برای ساخت یک فیلتر بزرگ بر روی Sales استفاده میکند:


با وجود اینکه این دو کوئری در مدل نمونه ما بسیار سریع عمل میکنند، این دو درخواست میتوانند در یک مثال واقعی با دهها میلیون ردیف در جدول Sales بسیار سنگین و کند باشند.
همین کوئری که با استفاده از GROUPBY بیان شده است، احتمالاً کارآمدتر خواهد بود:

سطح مادیسازی(تبدیل دادهها به یک فرم قابل استفاده و ذخیرهسازی) کوچکتر است، حتی اگر نتوانیم از measure پایه Sales Amount استفاده کنیم. تنها درخواست VertiPaq که اجرا میشود به شرح زیر است:

با این حال، سطح جزئیات این datacache مشابه جدول Sales است و این میتواند در مدلهای بزرگ مشکلساز باشد.
برای دستیابی به عملکرد بهتر، نیاز به ترکیب دو تابع داریم و باید دیدگاه خود را تغییر دهیم. ابتدا بر اساس Sales[Quantity] گروهبندی میکنیم و با استفاده از توابع ADDCOLUMNS و SUMMARIZE یک جدول بسیار کوچک تولید میکنیم. این جدول تنها 10 ردیف دارد. سپس ستون Transaction Size را اضافه میکنیم و در نهایت از GROUPBY برای گروهبندی جدول 10 ردیفی در دو خوشه Transaction Size استفاده میکنیم:

این کوئری DAX تنها دو درخواست موتور ذخیرهسازی (storage engine) را اجرا میکند. اولین درخواست مقدار فروش را بر اساس quantity گروهبندی میکند:

بیشتر محاسبات به موتور ذخیرهسازی منتقل شده است؛ سطح مادیسازی قابلتوجهی وجود ندارد و این آخرین کوئری DAX حتی در پایگاههای داده بزرگ بسیار سریع خواهد بود.
دانستن جزئیات توابع، نحوه پیادهسازی آنها و استفاده مورد انتظارشان، مهارتی مهم برای هر کسی است که جدی به DAX پرداخته است. در این مقاله، تفاوت بین GROUPBY و SUMMARIZE را پوشش دادیم. با این حال، DAX جزئیات پنهان بسیاری دارد که ارزش یادگیری دارند.
استفاده از تابع اشتباه ممکن است منجر به نتایج غیرمنتظره یا کوئریهای ناکارآمد شود. هر چه بیشتر درباره DAX یاد بگیرید، کد شما بهتر خواهد شد.
دیدگاهتان را بنویسید