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

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

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

نظرات 0

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

مقدمه

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

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

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

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

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

function_name(expression) OVER (
    [PARTITION BY partition_expression]
    [ORDER BY sort_expression]
    [ROWS | RANGE window_frame]
)

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

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

نوع خروجی

خود OVER نوع داده مستقلی برنمی‌گرداند؛ نوع خروجی را تابع سمت چپ آن مانند SUM، AVG، ROW_NUMBER یا LAG تعیین می‌کند. تعداد ردیف‌های نتیجه معمولاً با ورودی برابر می‌ماند.

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

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

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

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

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

مثال 1: شماره‌گذاری کارکنان

در ساده‌ترین کاربرد، ROW_NUMBER با OVER برای هر ردیف شماره‌ای بر اساس حقوق و شناسه می‌سازد. افزودن شناسه به ترتیب، نتیجه را در حقوق‌های مساوی قطعی می‌کند.

SELECT EmployeeID, Salary,
       ROW_NUMBER() OVER (ORDER BY Salary DESC, EmployeeID) AS RowNo
FROM (VALUES (1, 70000), (2, 85000), (3, 85000)) E(EmployeeID, Salary);
EmployeeIDSalaryRowNo
2850001
3850002
1700003

این الگو برای صفحه‌بندی و تولید شماره ردیف مناسب است؛ اما برای ترتیب ظاهری نتیجه باید در انتهای Query نیز ORDER BY نوشته شود.

مثال 2: جمع کل بدون حذف جزئیات

SUM با یک پنجره خالی کل فروش را کنار هر تراکنش نشان می‌دهد و برخلاف GROUP BY ردیف‌ها را ادغام نمی‌کند.

SELECT OrderID, Amount,
       SUM(Amount) OVER () AS GrandTotal
FROM (VALUES (101, 120), (102, 80), (103, 50)) S(OrderID, Amount);
OrderIDAmountGrandTotal
101120250
10280250
10350250

پنجره خالی یعنی تمام ردیف‌های خروجی جاری. فیلتر WHERE پیش از محاسبه پنجره اعمال می‌شود و دامنه GrandTotal را تغییر می‌دهد.

مثال 3: جمع هر مشتری

افزودن PARTITION BY باعث می‌شود مجموع برای هر مشتری جداگانه محاسبه شود و مبلغ هر سفارش نیز باقی بماند.

SELECT CustomerID, OrderID, Amount,
       SUM(Amount) OVER (PARTITION BY CustomerID) AS CustomerTotal
FROM (VALUES (1, 10, 40), (1, 11, 60), (2, 12, 30)) O(CustomerID, OrderID, Amount);
CustomerIDOrderIDAmountCustomerTotal
11040100
11160100
2123030

این روش برای محاسبه سهم هر ردیف از کل مشتری یا مقایسه سفارش با میانگین همان مشتری کاربرد مستقیم دارد.

مثال 4: فیلتر رتبه با CTE

توابع پنجره‌ای در مرحله WHERE همان SELECT قابل استفاده نیستند. ابتدا رتبه در CTE ساخته و سپس ردیف‌های برتر فیلتر می‌شوند.

WITH Ranked AS (
    SELECT ProductID, Price,
           ROW_NUMBER() OVER (ORDER BY Price DESC, ProductID) AS rn
    FROM (VALUES (1, 20), (2, 35), (3, 25)) P(ProductID, Price)
)
SELECT ProductID, Price
FROM Ranked
WHERE rn <= 2;
ProductIDPrice
235
325

CTE ترتیب منطقی پردازش را روشن می‌کند. استفاده مستقیم از ROW_NUMBER در WHERE خطاست زیرا WHERE پیش از محاسبه تابع پنجره‌ای اجرا می‌شود.

مثال 5: جمع تجمعی صریح

ترکیب ORDER BY و ROWS جمع را از ابتدای پارتیشن تا ردیف جاری محاسبه می‌کند و رفتار در مقادیر تکراری را شفاف نگه می‌دارد.

SELECT TxnID, Amount,
       SUM(Amount) OVER (
           ORDER BY TxnID
           ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS RunningTotal
FROM (VALUES (1, 10), (2, 15), (3, 7)) T(TxnID, Amount);
TxnIDAmountRunningTotal
11010
21525
3732

برای سیستم مالی کلید ترتیب باید قطعی و نماینده توالی واقعی باشد. تاریخ تکراری بدون شناسه رفع تساوی معمولاً کافی نیست.

مثال 6: رفتار NULL در شمارش

COUNT(Value) فقط مقادیر غیر NULL را می‌شمارد، در حالی که COUNT(*) تعداد تمام ردیف‌های پنجره را گزارش می‌کند.

SELECT ItemID, Value,
       COUNT(Value) OVER () AS NonNullCount,
       COUNT(*) OVER () AS RowCount
FROM (VALUES (1, 5), (2, NULL), (3, 9)) D(ItemID, Value);
ItemIDValueNonNullCountRowCount
1523
2NULL23
3923

انتخاب عبارت داخل تابع اهمیت دارد؛ OVER فقط دامنه را تعیین می‌کند و قواعد NULL متعلق به تابع COUNT است.

مثال 7: رفع تساوی در رتبه‌بندی

وقتی Score تکراری است، افزودن StudentID ترتیب را قطعی می‌کند تا اجرای دوباره شماره‌های متفاوت نسازد.

SELECT StudentID, Score,
       ROW_NUMBER() OVER (ORDER BY Score DESC, StudentID ASC) AS StableRank
FROM (VALUES (7, 90), (3, 90), (5, 80)) S(StudentID, Score);
StudentIDScoreStableRank
3901
7902
5803

اگر منطق به رتبه مشترک نیاز دارد از RANK یا DENSE_RANK استفاده کنید؛ افزودن کلید یکتا برای ROW_NUMBER صرفاً قطعیت را تضمین می‌کند.

مثال 8: گزارش سهم شعبه

مبلغ هر شعبه و کل سازمان در همان نتیجه محاسبه می‌شود تا درصد مشارکت بدون Query جداگانه به دست آید.

SELECT BranchID, Sales,
       SUM(Sales) OVER (PARTITION BY BranchID) AS BranchTotal,
       SUM(Sales) OVER () AS CompanyTotal
FROM (VALUES (1, 100), (1, 50), (2, 150)) B(BranchID, Sales);
BranchIDSalesBranchTotalCompanyTotal
1100150300
150150300
2150150300

چند پنجره در یک SELECT مجاز است. اگر مشخصات ترتیب و پارتیشن نزدیک باشند، Optimizer گاهی عملیات مشترک ایجاد می‌کند.

مثال 9: اصلاح روش اشتباه

استفاده مستقیم از تابع پنجره‌ای در WHERE مجاز نیست. نسخه صحیح ابتدا خروجی را در CTE نام‌گذاری می‌کند.

-- روش نادرست: WHERE ROW_NUMBER() OVER (...) = 1
WITH X AS (
    SELECT ProductID, Price,
           ROW_NUMBER() OVER (ORDER BY Price DESC, ProductID) AS rn
    FROM (VALUES (1, 10), (2, 30)) P(ProductID, Price)
)
SELECT ProductID, Price FROM X WHERE rn = 1;
ProductIDPrice
230

این اصلاح هم قابل اجراست و هم به Optimizer امکان می‌دهد طرح مناسبی بسازد. Alias تابع پنجره‌ای نیز در WHERE همان سطح قابل ارجاع نیست.

مثال 10: ایندکس مناسب پنجره

جدول موقت یک ایندکس مطابق CustomerID و OrderDate می‌گیرد تا محاسبه تجمعی ورودی مرتب و پوشش‌داده‌شده داشته باشد.

CREATE TABLE #Sales(CustomerID int, OrderDate date, OrderID int, Amount int);
INSERT #Sales VALUES (1,'2026-01-01',1,10),(1,'2026-01-02',2,20),(2,'2026-01-01',3,15);
CREATE INDEX IX_Sales_Window ON #Sales(CustomerID, OrderDate, OrderID) INCLUDE(Amount);
SELECT OrderID, SUM(Amount) OVER (PARTITION BY CustomerID ORDER BY OrderDate, OrderID ROWS UNBOUNDED PRECEDING) AS RunningTotal
FROM #Sales;
DROP TABLE #Sales;
OrderIDRunningTotal
110
230
315

ایندکس را صرفاً به دلیل یک Query اضافه نکنید؛ هزینه نوشتن و فضای ذخیره‌سازی را با حذف Sort و کاهش خواندن مقایسه کنید.

خطاهای رایج

  • فراموش‌کردن ORDER BY در محاسبات وابسته به ترتیب
  • استفاده از مرتب‌سازی غیرقطعی روی ستون‌های تکراری
  • مخلوط‌کردن ORDER BY داخل OVER با ORDER BY نهایی Query
  • فیلتر مستقیم تابع پنجره‌ای در WHERE به‌جای CTE یا زیرپرس‌وجو

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

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

برای OVER، ترتیب کلیدهای ایندکس باید تا حد امکان با PARTITION BY و سپس ORDER BY هماهنگ باشد. در Execution Plan به Sort، Window Aggregate، Sequence Project، Segment، حافظه درخواستی و Spill به tempdb توجه کنید.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

جمع‌بندی

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

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

 

0 نظر

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

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

حرف 500 حداکثر

اطلاعات تماس

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