مثالهای عملی
مثال 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);
| ItemID | SortKey | Amount | RangeTotal |
|---|
| 1 | 1 | 10 | 30 |
| 2 | 1 | 20 | 30 |
| 3 | 2 | 5 | 35 |
رفتار همتاها تفاوت اصلی 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);
| Grade | Points | TotalByGrade |
|---|
| 1 | 10 | 25 |
| 1 | 15 | 25 |
| 2 | 20 | 45 |
شکل کوتاه 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);
| ItemID | RangeTotal | RowsTotal |
|---|
| 1 | 30 | 10 |
| 2 | 30 | 30 |
| 3 | 35 | 35 |
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);
| Price | Qty | QtyAtPrice |
|---|
| 10 | 2 | 5 |
| 10 | 3 | 5 |
| 20 | 1 | 1 |
این الگو یک محاسبه همتایی است؛ اگر فقط ردیف جاری مدنظر باشد 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);
| SaleID | SaleDate | DailyCumulative |
|---|
| 1 | 2026-01-01 | 30 |
| 2 | 2026-01-01 | 30 |
| 3 | 2026-01-02 | 35 |
اگر گزارش باید پایان هر روز را نشان دهد این رفتار مطلوب است؛ برای ثبت مانده پس از هر تراکنش 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);
| ItemID | SortKey | Amount | rt |
|---|
| 1 | NULL | 10 | 30 |
| 2 | NULL | 20 | 30 |
| 3 | 1 | 5 | 35 |
اگر 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);
| ItemID | Price | rt |
|---|
| 1 | 10.00 | 12 |
| 2 | 10.00 | 12 |
| 3 | 10.01 | 15 |
برای 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);
| InvoiceID | InvoiceDate | CustomerDailyTotal |
|---|
| 1 | 2026-01-01 | 100 |
| 2 | 2026-01-01 | 100 |
| 3 | 2026-01-02 | 125 |
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);
| SeqNo | Amount | LastThreeRows |
|---|
| 1 | 10 | 10 |
| 2 | 20 | 30 |
| 3 | 30 | 60 |
| 4 | 40 | 90 |
اگر هدف بازه زمانی مانند هفت روز است، ابتدا مرز زمانی را با طراحی 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;
EntryID برای پوشش و قطعیت فیزیکی ایندکس مفید است، ولی RANGE فقط PostingDate را معیار همتایی قرار داده است.