آموزش PARTITION BY در SQL Server | ۱۰ مثال کاربردی و بهینه‌سازی

آموزش جامع عبارت PARTITION BY در SQL Server

توسط admin | گروه SQL Server | 1405/04/29

نظرات 0

آموزش جامع عبارت PARTITION BY در SQL Server با مثال‌های عملی

مقدمه

PARTITION BY ورودی تابع پنجره‌ای را به گروه‌های منطقی مستقل تقسیم می‌کند، اما برخلاف GROUP BY ردیف‌های جزئی را حذف یا ادغام نمی‌کند. محاسبه برای هر پارتیشن از نو آغاز می‌شود و هر ردیف هم مقدار خودش و هم شاخص گروه را در اختیار دارد. در این راهنما رفتار واقعی عبارت PARTITION BY را از مثال پایه تا سناریوی سازمانی، حالت NULL، خطای رایج و بهینه‌سازی بررسی می‌کنیم.

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

تعریف، نحو و نوع خروجی

PARTITION BY ورودی تابع پنجره‌ای را به گروه‌های منطقی مستقل تقسیم می‌کند، اما برخلاف GROUP BY ردیف‌های جزئی را حذف یا ادغام نمی‌کند. محاسبه برای هر پارتیشن از نو آغاز می‌شود و هر ردیف هم مقدار خودش و هم شاخص گروه را در اختیار دارد.

نحو استاندارد

window_function() OVER (
    PARTITION BY expression [, expression ...]
    [ORDER BY sort_expression]
)

پارامترها و اجزای مهم

  • تابع پنجره‌ای: تابعی مانند SUM، AVG، ROW_NUMBER، RANK، LAG یا LEAD که نتیجه تحلیلی را تولید می‌کند.
  • PARTITION BY: مرز اختیاری گروه‌های مستقل را تعیین می‌کند و در هر گروه محاسبه از نو آغاز می‌شود.
  • ORDER BY: ترتیب منطقی ردیف‌ها را برای محاسبات وابسته به توالی مشخص می‌کند.
  • Window Frame: در توابع سازگار، محدوده ردیف‌های مؤثر نسبت به ردیف جاری را تعریف می‌کند.

نوع خروجی

PARTITION BY به‌تنهایی خروجی ندارد و محدوده محاسبه تابع پنجره‌ای را تعیین می‌کند. نوع ستون حاصل تابعی از window_function است و تمام ردیف‌های اصلی حفظ می‌شوند.

مدل ذهنی و ترتیب منطقی اجرا

برای تحلیل عبارت PARTITION BY ابتدا Dataset پس از FROM، JOIN، WHERE و GROUP BY را در نظر بگیرید. تابع پنجره‌ای روی این مجموعه منطقی محاسبه می‌شود و سپس SELECT خروجی را شکل می‌دهد. به همین دلیل Alias یا خروجی پنجره در WHERE همان سطح قابل استفاده نیست و برای فیلتر باید CTE یا زیرپرس‌وجو ایجاد شود.

سه سؤال پیش از نوشتن Query مطرح کنید: هر محاسبه برای کدام گروه مستقل است، ترتیب دقیق و قطعی ردیف‌ها چیست، و قاب از کجا تا کجا امتداد دارد؟ پاسخ صریح به این سه سؤال بیشتر خطاهای ظریف گزارش‌های تحلیلی را حذف می‌کند.

وجود مقدارهای تکراری و NULL را بخشی از طراحی بدانید. نمونه‌ای که فقط داده یکتا دارد ممکن است در محیط آزمایشی درست به نظر برسد ولی با اولین تساوی در تولید، رتبه یا مانده متفاوتی بسازد. تست کوچک باید عمداً این حالت‌ها را وارد کند.

مثال‌های عملی

مثال 1: شمارش اعضای هر واحد

COUNT با PARTITION BY تعداد کارکنان هر واحد را کنار جزئیات هر کارمند نمایش می‌دهد.

SELECT EmployeeID, DepartmentID,
       COUNT(*) OVER (PARTITION BY DepartmentID) AS DepartmentCount
FROM (VALUES (1,10),(2,10),(3,20)) E(EmployeeID,DepartmentID);
EmployeeIDDepartmentIDDepartmentCount
1102
2102
3201

این خروجی برای نمایش جزئیات و شاخص گروه در یک Grid مفید است و به Join با زیرپرس‌وجوی تجمیعی نیاز ندارد.

مثال 2: مجموع خرید مشتری

هر سفارش حفظ می‌شود و CustomerTotal برای مشتری مربوط تکرار می‌گردد.

SELECT CustomerID, OrderID, Amount,
       SUM(Amount) OVER (PARTITION BY CustomerID) AS CustomerTotal
FROM (VALUES (1,101,40),(1,102,60),(2,103,25)) O(CustomerID,OrderID,Amount);
CustomerIDOrderIDAmountCustomerTotal
110140100
110260100
21032525

برای محاسبه درصد، Amount را بر NULLIF(CustomerTotal,0) تقسیم کنید تا خطای تقسیم بر صفر رخ ندهد.

مثال 3: سهم هر ردیف از پارتیشن

مجموع پارتیشن در مخرج استفاده می‌شود تا درصد سهم هر فروش محاسبه شود.

SELECT RegionID, Amount,
       CAST(100.0 * Amount / NULLIF(SUM(Amount) OVER (PARTITION BY RegionID),0) AS decimal(6,2)) AS SharePct
FROM (VALUES (1,30),(1,70),(2,50)) S(RegionID,Amount);
RegionIDAmountSharePct
13030.00
17070.00
250100.00

استفاده از 100.0 محاسبه را اعشاری می‌کند. نوع decimal را متناسب با دقت مالی سامانه انتخاب کنید.

مثال 4: بالاترین فروش هر گروه

رتبه داخل هر گروه از نو شروع می‌شود و CTE رتبه اول هر منطقه را برمی‌گرداند.

WITH R AS (
 SELECT RegionID, SellerID, Amount,
        ROW_NUMBER() OVER (PARTITION BY RegionID ORDER BY Amount DESC, SellerID) AS rn
 FROM (VALUES (1,1,90),(1,2,80),(2,3,70)) S(RegionID,SellerID,Amount)
)
SELECT RegionID,SellerID,Amount FROM R WHERE rn=1;
RegionIDSellerIDAmount
1190
2370

برای نگه‌داشتن تمام نفرات هم‌رتبه می‌توان به‌جای ROW_NUMBER از RANK استفاده کرد و سیاست تساوی را مستند نمود.

مثال 5: پارتیشن چندستونی

ترکیب سال و منطقه یک پارتیشن مستقل برای هر دوره و محل می‌سازد.

SELECT SalesYear, RegionID, Amount,
       AVG(1.0*Amount) OVER (PARTITION BY SalesYear,RegionID) AS AvgAmount
FROM (VALUES (2025,1,10),(2025,1,20),(2026,1,40)) S(SalesYear,RegionID,Amount);
SalesYearRegionIDAmountAvgAmount
202511015.0
202512015.0
202614040.0

افزودن هر ستون Cardinality پارتیشن‌ها را افزایش می‌دهد. فقط ابعادی را وارد کنید که مرز واقعی کسب‌وکار هستند.

مثال 6: پارتیشن مقادیر NULL

تمام RegionIDهای NULL یک پارتیشن مشترک می‌سازند و جدا از مقادیر شناخته‌شده محاسبه می‌شوند.

SELECT ItemID,RegionID,
       COUNT(*) OVER (PARTITION BY RegionID) AS GroupCount
FROM (VALUES (1,NULL),(2,NULL),(3,1)) D(ItemID,RegionID);
ItemIDRegionIDGroupCount
1NULL2
2NULL2
311

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

مثال 7: پارتیشن بر اساس سال تاریخ

YEAR تاریخ سفارش مرز سالانه ایجاد می‌کند؛ برای گزارش کوچک خواناست اما ستون محاسبه‌شده می‌تواند ایندکس‌پذیری را بهتر کند.

SELECT OrderID,OrderDate,Amount,
       SUM(Amount) OVER (PARTITION BY YEAR(OrderDate)) AS YearTotal
FROM (VALUES (1,CONVERT(date,'2025-01-01'),10),(2,'2025-03-01',20),(3,'2026-01-01',30)) O(OrderID,OrderDate,Amount);
OrderIDOrderDateAmountYearTotal
12025-01-011030
22025-03-012030
32026-01-013030

برای جداول بزرگ، ذخیره سال به‌صورت ستون محاسبه‌شده پایدار و ایندکس‌شده ممکن است Sort و محاسبه تکراری را کاهش دهد.

مثال 8: گزارش جزئیات و میانگین واحد

حقوق هر کارمند همراه میانگین واحد در یک Dataset گزارش می‌شود.

SELECT DepartmentID,EmployeeID,Salary,
       AVG(CAST(Salary AS decimal(12,2))) OVER (PARTITION BY DepartmentID) AS DeptAvg
FROM (VALUES (10,1,80),(10,2,100),(20,3,120)) E(DepartmentID,EmployeeID,Salary);
DepartmentIDEmployeeIDSalaryDeptAvg
1018090.00
10210090.00
203120120.00

این الگو امکان Highlight کردن کارکنان بالاتر از میانگین را در لایه گزارش فراهم می‌کند و جزئیات را از بین نمی‌برد.

مثال 9: مقایسه با GROUP BY

دو Query هدف متفاوت دارند: GROUP BY یک ردیف برای هر گروه و PARTITION BY جزئیات به‌همراه مجموع را می‌دهد.

SELECT DepartmentID,EmployeeID,Salary,
       SUM(Salary) OVER (PARTITION BY DepartmentID) AS DeptTotal
FROM (VALUES (10,1,80),(10,2,100)) E(DepartmentID,EmployeeID,Salary);
-- خلاصه مستقل:
SELECT DepartmentID,SUM(Salary) AS DeptTotal
FROM (VALUES (10,1,80),(10,2,100)) E(DepartmentID,EmployeeID,Salary)
GROUP BY DepartmentID;
نوع خروجیتعداد ردیف
PARTITION BY2
GROUP BY1

انتخاب باید بر اساس شکل Dataset باشد. استفاده از پنجره برای گزارشی که فقط خلاصه می‌خواهد ممکن است داده اضافی تولید کند.

مثال 10: ایندکس POC برای پارتیشن

ایندکس با DepartmentID آغاز و سپس ترتیب Salary و EmployeeID را پوشش می‌دهد.

CREATE TABLE #E(DepartmentID int,EmployeeID int,Salary int);
INSERT #E VALUES(10,1,80),(10,2,100),(20,3,90);
CREATE INDEX IX_E_POC ON #E(DepartmentID,Salary DESC,EmployeeID);
SELECT EmployeeID,ROW_NUMBER() OVER(PARTITION BY DepartmentID ORDER BY Salary DESC,EmployeeID) AS rn FROM #E;
DROP TABLE #E;
EmployeeIDrn
21
12
31

کلیدهای فیلتر ثابت می‌توانند پیش از POC قرار گیرند. تصمیم نهایی را با Query Store و طرح واقعی پرتکرارترین بار کاری بگیرید.

خطاهای رایج

  • اشتباه‌گرفتن PARTITION BY با GROUP BY
  • قرار دادن ستون‌های اضافی و ساخت پارتیشن‌های بیش از حد ریز
  • نادیده‌گرفتن اینکه NULLها یک پارتیشن مشترک می‌سازند
  • عدم هماهنگی ترتیب ستون‌های ایندکس با کلید پارتیشن

برای عیب‌یابی ابتدا نتیجه را روی Dataset بسیار کوچک دستی محاسبه کنید. سپس Actual Execution Plan را جدا از صحت منطقی بررسی کنید؛ سریع‌بودن Query پاسخ نادرست را قابل قبول نمی‌کند و پاسخ درست با Sort و Spill بزرگ نیز برای تولید آماده نیست.

ملاحظات کارایی

یک ایندکس با کلیدهای پارتیشن در ابتدا و کلیدهای ترتیب پس از آن می‌تواند ورودی مرتب موردنیاز اپراتورهای پنجره‌ای را فراهم کند. تعداد بسیار زیاد پارتیشن‌ها، تخمین Cardinality ضعیف و Sort بزرگ را در Plan بررسی کنید.

آمارهای به‌روز، تخمین درست تعداد ردیف‌ها و Memory Grant کافی روی عملکرد اثر مستقیم دارند. قبل و بعد از تغییر ایندکس، SET STATISTICS IO, TIME ON و Actual Execution Plan را روی حجم نماینده ثبت کنید. از نتیجه یک اجرای گرم یا داده آزمایشی کوچک حکم قطعی نسازید.

ایندکس پیشنهادی باید با بار نوشتن، فضای دیسک و سایر Queryها سنجیده شود. یک ایندکس پوششی عریض ممکن است یک گزارش را سریع کند اما درج و به‌روزرسانی کل سامانه را گران‌تر سازد. Query Store برای مشاهده رفتار در طول زمان و تشخیص Regression مفید است.

بهترین روش‌ها

  • ترتیب و پارتیشن را مستقیماً از تعریف کسب‌وکار استخراج و در مستند فنی ثبت کنید.
  • برای کلیدهای تکراری سیاست روشن داشته باشید و در صورت نیاز کلید یکتای رفع تساوی اضافه کنید.
  • قاب پنجره را صریح بنویسید تا رفتار با تغییر داده یا توسعه Query مبهم نشود.
  • حالت‌های NULL، پارتیشن تک‌ردیفی، پارتیشن بزرگ و داده تکراری را در تست رگرسیون قرار دهید.
  • کارایی را با Plan واقعی و آمار IO و زمان روی حجم نماینده بسنجید، نه با حدس یا فقط Estimated Plan.
  • از Aliasهای توصیفی استفاده کنید تا معنای ستون محاسباتی برای گزارش و API روشن بماند.

سؤالات متداول

برای شروع یادگیری عبارت PARTITION BY چه پیش‌نیازی لازم است؟

آشنایی با SELECT، مرتب‌سازی، توابع تجمیعی و ترتیب منطقی اجرای Query کافی است. سپس عبارت PARTITION BY را روی چند ردیف کوچک با مقدار تکراری و NULL آزمایش کنید تا مرز پنجره فقط حفظ نشود، بلکه مشاهده شود.

چگونه صحت نتیجه عبارت PARTITION BY را با یک تست کوچک بررسی کنیم؟

یک مجموعه داده VALUES با سه تا پنج ردیف بسازید، نتیجه مورد انتظار را دستی حساب کنید و ستون حاصل از عبارت PARTITION BY را مقایسه نمایید. تست باید حالت عادی، تساوی، NULL و مرز پارتیشن را جداگانه پوشش دهد.

کاربرد تجاری عبارت PARTITION BY در گزارش مدیریتی چیست؟

عبارت PARTITION BY امکان ساخت KPI، رتبه‌بندی، روند، سهم از کل و مانده تجمعی را بدون حذف جزئیات فراهم می‌کند. این ویژگی Dataset گزارش را ساده‌تر می‌کند و تعداد رفت‌وبرگشت‌های لایه برنامه به دیتابیس را کاهش می‌دهد.

آیا عبارت PARTITION BY برای سامانه‌های مالی و فروش مناسب است؟

بله، به شرط آنکه ترتیب قطعی، نوع داده دقیق و مرز محاسبه مستند باشد. در پروژه مالی لازم است خروجی با داده مرجع تطبیق داده شود و برای حجم واقعی، Query Store و Actual Execution Plan نیز بررسی شوند.

تفاوت عبارت PARTITION BY با GROUP BY یا روش تجمیع سنتی چیست؟

GROUP BY ردیف‌ها را در سطح گروه خلاصه می‌کند، ولی عبارت PARTITION BY در چارچوب تابع پنجره‌ای معمولاً جزئیات را حفظ می‌کند. انتخاب درست به شکل خروجی نیازمند وابسته است و هیچ‌یک جایگزین مطلق دیگری نیست.

برای پیاده‌سازی حرفه‌ای عبارت PARTITION BY چه خدماتی مفید است؟

بازبینی مدل داده، طراحی شاخص‌ها، تهیه تست رگرسیون، تحلیل Execution Plan و مشاوره SQL Server بیشترین ارزش را دارند. در پروژه حساس بهتر است منطق عبارت PARTITION BY همراه تعریف KPI و نمونه مورد انتظار تحویل شود.

رایج‌ترین خطای توسعه‌دهندگان هنگام استفاده از عبارت PARTITION BY چیست؟

خطای پرتکرار، فرض‌کردن ترتیب یا قاب پیش‌فرض و بی‌توجهی به مقادیر مساوی است. نسخه صحیح باید کلید رفع تساوی، رفتار NULL و تفاوت ترتیب پنجره با ترتیب نهایی را صریح کند.

چگونه کارایی Query دارای عبارت PARTITION BY را بهبود دهیم؟

ابتدا تعداد ردیف ورودی را با فیلتر SARGable کاهش دهید، سپس ایندکس هماهنگ با پارتیشن و ترتیب را ارزیابی کنید. Sort، Window Spool، Memory Grant و Spill به tempdb در طرح واقعی باید اندازه‌گیری شوند.

بهترین روش نگهداری Queryهای مبتنی بر عبارت PARTITION BY چیست؟

نام‌گذاری روشن Aliasها، قاب صریح، کامنت درباره سیاست تساوی، تست داده مرزی و ثبت Baseline کارایی بهترین روش است. Query باید در Code Review همراه Execution Plan نمونه و انتظار کسب‌وکار بررسی شود.

عبارت PARTITION BY در کدام نسخه‌های SQL Server قابل استفاده است؟

قابلیت‌های اصلی Window Functions از SQL Server 2012 به بعد گسترده و پایدارند، هرچند برخی توابع یا بهبودهای Optimizer میان نسخه‌ها تفاوت دارند. Compatibility Level و مستندات نسخه مقصد را پیش از استقرار کنترل کنید.

سؤالات مصاحبه

  1. تفاوت نقش عبارت PARTITION BY در تعریف پنجره با مرتب‌سازی نهایی نتیجه چیست؟
  2. در چه شرایطی استفاده نادرست از عبارت PARTITION BY پاسخ تحلیلی را غیرقطعی می‌کند؟
  3. برای بررسی کارایی Query دارای عبارت PARTITION BY کدام بخش‌های Execution Plan را کنترل می‌کنید؟
  4. چگونه یک تست کوچک برای اثبات رفتار عبارت PARTITION BY در حضور مقادیر تکراری می‌نویسید؟
  5. چه ایندکسی می‌تواند Sort و خواندن داده در سناریوی عبارت PARTITION BY را کاهش دهد؟

چک‌لیست نهایی

  • صحت پارتیشن تأیید شده است.
  • ORDER BY قطعی و سیاست تساوی مشخص است.
  • قاب پیش‌فرض یا صریح آگاهانه انتخاب شده است.
  • نتیجه NULL و داده مرزی تست شده است.
  • Actual Execution Plan و Spill بررسی شده است.
  • ایندکس پیشنهادی با هزینه نوشتن سنجیده شده است.

جمع‌بندی

عبارت PARTITION BY زمانی قابل اعتماد است که مرز داده، ترتیب و رفتار ردیف‌های هم‌ارزش صریح باشند. مثال‌های این مقاله نشان دادند چگونه از پاسخ ساده به Query قابل استقرار برسیم و هم‌زمان صحت، خوانایی و کارایی را کنترل کنیم.

برای مقایسه این قابلیت با سایر اجزای پنجره‌ای، به مقاله مادر Window Clauses در SQL Server بازگردید و نمونه‌ها را روی ساختار جدول واقعی پروژه خود بازنویسی کنید.

 

0 نظر

نظر محترم شما در مورد مقاله های وب سایت برنامه نویسی و پایگاه داده

نظرات محترم شما در خدمات رسانی بهتر ما را یاری می نمایند. لطفا اگر مایل بودید یک نظر ما را مهمان فرمائید. آدرس ایمیل و وب سایت شما نمایش داده نخواهد شد.

حرف 500 حداکثر

اطلاعات تماس

  • آدرس:اصفهان-خیابان ام کلثوم غربی - بعد خیابان تخم چی - بیست متر بعد از پیتزا ننه شب - کوچه تعمیر گاه سمار زغالی - پلاک 354 - درب مشکی - طبقه هفتم
  • آدرس ایمیل:najafzade@gmail.com
  • وب سایت:http://www.a00b.com/
  • تلفن ثابت:(+98)9131253620
  • تلفن همراه:09131253620