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

آموزش جامع قاب پنجره‌ای RANGE در SQL Server

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

نظرات 0

آموزش جامع قاب پنجره‌ای RANGE در SQL Server با مثال‌های عملی

مقدمه

RANGE قاب را بر اساس مقدار منطقی کلید ORDER BY و مفهوم ردیف‌های همتا تعریف می‌کند. ردیف‌هایی که مقدار مرتب‌سازی یکسان دارند در مرز CURRENT ROW با هم دیده می‌شوند؛ بنابراین نتیجه تجمعی می‌تواند برای چند ردیف تکراری یکسان جهش کند. در این راهنما رفتار واقعی قاب پنجره‌ای RANGE را از مثال پایه تا سناریوی سازمانی، حالت NULL، خطای رایج و بهینه‌سازی بررسی می‌کنیم.

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

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

RANGE قاب را بر اساس مقدار منطقی کلید ORDER BY و مفهوم ردیف‌های همتا تعریف می‌کند. ردیف‌هایی که مقدار مرتب‌سازی یکسان دارند در مرز CURRENT ROW با هم دیده می‌شوند؛ بنابراین نتیجه تجمعی می‌تواند برای چند ردیف تکراری یکسان جهش کند.

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

aggregate_function(expression) OVER (
    [PARTITION BY partition_expression]
    ORDER BY sort_expression
    RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)

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

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

نوع خروجی

RANGE نوع خروجی تابع را تغییر نمی‌دهد، اما عضویت منطقی قاب را تغییر می‌دهد. در SQL Server پشتیبانی RANGE از Offset عددی محدود است و الگوهای UNBOUNDED PRECEDING و CURRENT ROW کاربرد اصلی را دارند.

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

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

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

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

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

مثال 1: تجمع همتاهای یکسان

دو ردیف با SortKey برابر در RANGE تا CURRENT ROW همتا هستند و مجموع یکسان 30 می‌گیرند.

SELECT ItemID,SortKey,Amount,SUM(Amount) OVER(ORDER BY SortKey RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RangeTotal
FROM (VALUES(1,1,10),(2,1,20),(3,2,5)) D(ItemID,SortKey,Amount);
ItemIDSortKeyAmountRangeTotal
111030
212030
32535

رفتار همتاها تفاوت اصلی RANGE با ROWS است. ترتیب فیزیکی دو ردیف کلید 1 در این محاسبه اهمیتی ندارد.

مثال 2: قاب RANGE صریح

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

SELECT Grade,Points,SUM(Points) OVER(ORDER BY Grade RANGE UNBOUNDED PRECEDING) AS TotalByGrade
FROM (VALUES(1,10),(1,15),(2,20)) G(Grade,Points);
GradePointsTotalByGrade
11025
11525
22045

شکل کوتاه RANGE UNBOUNDED PRECEDING انتهای CURRENT ROW دارد. برای خوانایی تیمی BETWEEN را می‌توان کامل نوشت.

مثال 3: مقایسه RANGE و ROWS

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

SELECT ItemID,SortKey,Amount,
 SUM(Amount) OVER(ORDER BY SortKey RANGE UNBOUNDED PRECEDING) AS RangeTotal,
 SUM(Amount) OVER(ORDER BY SortKey,ItemID ROWS UNBOUNDED PRECEDING) AS RowsTotal
FROM (VALUES(1,1,10),(2,1,20),(3,2,5)) D(ItemID,SortKey,Amount);
ItemIDRangeTotalRowsTotal
13010
23030
33535

ROWS با ItemID ترتیب قطعی هر ردیف را نشان می‌دهد؛ RANGE همتاهای SortKey را یک مرز منطقی می‌بیند.

مثال 4: فقط گروه همتای جاری

RANGE بین CURRENT ROW و CURRENT ROW مجموع تمام ردیف‌های با کلید برابر را می‌دهد.

SELECT Price,Qty,SUM(Qty) OVER(ORDER BY Price RANGE BETWEEN CURRENT ROW AND CURRENT ROW) AS QtyAtPrice
FROM (VALUES(10,2),(10,3),(20,1)) P(Price,Qty);
PriceQtyQtyAtPrice
1025
1035
2011

این الگو یک محاسبه همتایی است؛ اگر فقط ردیف جاری مدنظر باشد ROWS BETWEEN CURRENT ROW AND CURRENT ROW بنویسید.

مثال 5: تکرار تاریخ گزارش

فروش‌های یک تاریخ در مرز RANGE با هم به جمع تجمعی وارد می‌شوند.

SELECT SaleID,SaleDate,Amount,SUM(Amount) OVER(ORDER BY SaleDate RANGE UNBOUNDED PRECEDING) AS DailyCumulative
FROM (VALUES(1,CONVERT(date,'2026-01-01'),10),(2,'2026-01-01',20),(3,'2026-01-02',5)) S(SaleID,SaleDate,Amount);
SaleIDSaleDateDailyCumulative
12026-01-0130
22026-01-0130
32026-01-0235

اگر گزارش باید پایان هر روز را نشان دهد این رفتار مطلوب است؛ برای ثبت مانده پس از هر تراکنش ROWS و کلید رفع تساوی لازم است.

مثال 6: همتایی NULL

مقادیر NULL در ORDER BY صعودی در ابتدای ترتیب قرار می‌گیرند و به‌عنوان همتا یک قاب مشترک می‌سازند.

SELECT ItemID,SortKey,Amount,SUM(Amount) OVER(ORDER BY SortKey RANGE UNBOUNDED PRECEDING) AS rt
FROM (VALUES(1,NULL,10),(2,NULL,20),(3,1,5)) D(ItemID,SortKey,Amount);
ItemIDSortKeyAmountrt
1NULL1030
2NULL2030
31535

اگر NULL معنای «نامشخص» دارد، قرارگرفتن آن در ابتدای محاسبه را آگاهانه تأیید یا با CASE اصلاح کنید.

مثال 7: کلید اعشاری تکراری

برابری دقیق مقدار decimal گروه‌های همتا را می‌سازد.

SELECT ItemID,Price,Amount,SUM(Amount) OVER(ORDER BY Price RANGE UNBOUNDED PRECEDING) AS rt
FROM (VALUES(1,CAST(10.00 AS decimal(10,2)),5),(2,10.00,7),(3,10.01,3)) D(ItemID,Price,Amount);
ItemIDPricert
110.0012
210.0012
310.0115

برای float مفهوم برابری می‌تواند تحت اثر نمایش تقریبی باشد. کلیدهای مالی را با decimal دقیق مدل کنید.

مثال 8: جمع پایان روز فاکتورها

RANGE تمام فاکتورهای روز را هم‌زمان وارد مانده روزانه می‌کند.

SELECT InvoiceID,InvoiceDate,Total,SUM(Total) OVER(PARTITION BY CustomerID ORDER BY InvoiceDate RANGE UNBOUNDED PRECEDING) AS CustomerDailyTotal
FROM (VALUES(1,1,CONVERT(date,'2026-01-01'),40),(1,2,'2026-01-01',60),(1,3,'2026-01-02',25)) I(CustomerID,InvoiceID,InvoiceDate,Total);
InvoiceIDInvoiceDateCustomerDailyTotal
12026-01-01100
22026-01-01100
32026-01-02125

PARTITION BY مانده هر مشتری را جدا می‌کند و RANGE مرز روز را رعایت می‌کند؛ این ترکیب با تعریف گزارش پایان روز سازگار است.

مثال 9: اصلاح Offset پشتیبانی‌نشده

SQL Server برای RANGE مانند برخی DBMSها Offset عددی عمومی ارائه نمی‌کند؛ برای سه ردیف اخیر ROWS به‌کار می‌رود.

-- RANGE BETWEEN 2 PRECEDING AND CURRENT ROW در SQL Server انتخاب مناسبی نیست.
SELECT SeqNo,Amount,SUM(Amount) OVER(ORDER BY SeqNo ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS LastThreeRows
FROM (VALUES(1,10),(2,20),(3,30),(4,40)) D(SeqNo,Amount);
SeqNoAmountLastThreeRows
11010
22030
33060
44090

اگر هدف بازه زمانی مانند هفت روز است، ابتدا مرز زمانی را با طراحی Query مشخص کنید؛ ROWS صرفاً تعداد ردیف را می‌شمارد.

مثال 10: ارزیابی کارایی RANGE

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

CREATE TABLE #R(AccountID int,PostingDate date,EntryID int,Amount int);
INSERT #R VALUES(1,'2026-01-01',1,10),(1,'2026-01-01',2,20),(1,'2026-01-02',3,5);
CREATE INDEX IX_R_POC ON #R(AccountID,PostingDate,EntryID) INCLUDE(Amount);
SELECT EntryID,SUM(Amount) OVER(PARTITION BY AccountID ORDER BY PostingDate RANGE UNBOUNDED PRECEDING) rt FROM #R;
DROP TABLE #R;
EntryIDrt
130
230
335

EntryID برای پوشش و قطعیت فیزیکی ایندکس مفید است، ولی RANGE فقط PostingDate را معیار همتایی قرار داده است.

خطاهای رایج

  • فرض اینکه RANGE و ROWS همیشه نتیجه یکسان دارند
  • نادیده‌گرفتن ردیف‌های همتا در مقادیر تکراری
  • استفاده از Offset عددی RANGE که در SQL Server پشتیبانی کامل ندارد
  • اتکا به قاب پیش‌فرض بدون ثبت تصمیم کسب‌وکار

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

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

پردازش همتاها می‌تواند به Window Spool یا عملیات بیشتر نیاز داشته باشد. اگر منطق کسب‌وکار به همتاها وابسته نیست، ROWS صریح را آزمایش کنید و تفاوت I/O، زمان، Memory Grant و Spill را با داده واقعی بسنجید.

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

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

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

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

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

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

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

چگونه صحت نتیجه قاب پنجره‌ای RANGE را با یک تست کوچک بررسی کنیم؟

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

کاربرد تجاری قاب پنجره‌ای RANGE در گزارش مدیریتی چیست؟

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

آیا قاب پنجره‌ای RANGE برای سامانه‌های مالی و فروش مناسب است؟

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

تفاوت قاب پنجره‌ای RANGE با GROUP BY یا روش تجمیع سنتی چیست؟

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

برای پیاده‌سازی حرفه‌ای قاب پنجره‌ای RANGE چه خدماتی مفید است؟

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

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

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

چگونه کارایی Query دارای قاب پنجره‌ای RANGE را بهبود دهیم؟

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

بهترین روش نگهداری Queryهای مبتنی بر قاب پنجره‌ای RANGE چیست؟

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

قاب پنجره‌ای RANGE در کدام نسخه‌های SQL Server قابل استفاده است؟

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

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

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

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

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

جمع‌بندی

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

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

 

0 نظر

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

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

حرف 500 حداکثر

اطلاعات تماس

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