مثالهای عملی مستقل و قابل اجرا
مثال 1: خواندن مقدار بعدی در یک توالی
در سادهترین حالت، LEAD مبلغ بعدی را طبق ترتیب قطعی تاریخ و شناسه برمیگرداند.
WITH Sales AS
(
SELECT *
FROM (VALUES
(1, N'فروش', CAST('2026-01-01' AS date), 100),
(2, N'فروش', CAST('2026-02-01' AS date), 140),
(3, N'فروش', CAST('2026-03-01' AS date), 120),
(4, N'پشتیبانی', CAST('2026-01-01' AS date), 80),
(5, N'پشتیبانی', CAST('2026-02-01' AS date), 110)
) AS V(Id, Department, SaleDate, Amount)
)
SELECT Id, SaleDate, Amount,
LEAD(Amount) OVER (ORDER BY SaleDate, Id) AS LeadAmount
FROM Sales
WHERE Department = N'فروش'
ORDER BY SaleDate, Id;
| تاریخ | مبلغ | مبلغ بعدی |
|---|
| 2026-01-01 | 100 | 140 |
| 2026-02-01 | 140 | 120 |
نکته کاربردی: وجود Id در ORDER BY نتیجه را حتی در تاریخهای تکراری پایدار میکند.
مثال 2: شروع دوباره محاسبه در هر واحد
با PARTITION BY زنجیره هر دپارتمان مستقل میشود و مقدار واحد دیگر وارد مقایسه نمیگردد.
WITH Sales AS
(
SELECT *
FROM (VALUES
(1, N'فروش', CAST('2026-01-01' AS date), 100),
(2, N'فروش', CAST('2026-02-01' AS date), 140),
(3, N'فروش', CAST('2026-03-01' AS date), 120),
(4, N'پشتیبانی', CAST('2026-01-01' AS date), 80),
(5, N'پشتیبانی', CAST('2026-02-01' AS date), 110)
) AS V(Id, Department, SaleDate, Amount)
)
SELECT Department, SaleDate, Amount,
LEAD(Amount) OVER (PARTITION BY Department ORDER BY SaleDate, Id) AS AdjacentAmount
FROM Sales
ORDER BY Department, SaleDate, Id;
| واحد | مبلغ | مجاور |
|---|
| فروش | 100 | 140 |
| فروش | 140 | 120 |
نکته کاربردی: پارتیشنبندی یک مرز منطقی است و تعداد ردیفهای خروجی را کم نمیکند.
مثال 3: استفاده از فاصله دو ردیفی و مقدار پیشفرض
پارامتر offset میتواند بیش از یک باشد و default نبود ردیف کافی را به مقدار کنترلشده تبدیل میکند.
WITH Sales AS
(
SELECT *
FROM (VALUES
(1, N'فروش', CAST('2026-01-01' AS date), 100),
(2, N'فروش', CAST('2026-02-01' AS date), 140),
(3, N'فروش', CAST('2026-03-01' AS date), 120),
(4, N'پشتیبانی', CAST('2026-01-01' AS date), 80),
(5, N'پشتیبانی', CAST('2026-02-01' AS date), 110)
) AS V(Id, Department, SaleDate, Amount)
)
SELECT SaleDate, Amount,
LEAD(Amount, 2, 0) OVER (ORDER BY SaleDate, Id) AS TwoRowsAway
FROM Sales
WHERE Department = N'فروش'
ORDER BY SaleDate, Id;
| ردیف | مبلغ | دو ردیف فاصله |
|---|
| 1 | 100 | 120 |
| 3 | 120 | 0 |
نکته کاربردی: نوع مقدار پیشفرض باید با عبارت اصلی سازگار باشد تا تبدیل ضمنی نامطلوب رخ ندهد.
مثال 4: محاسبه تغییر مبلغ
تفاضل مقدار جاری و مجاور، روند رشد یا افت را بدون اتصال جدول به خودش نشان میدهد.
WITH Sales AS
(
SELECT *
FROM (VALUES
(1, N'فروش', CAST('2026-01-01' AS date), 100),
(2, N'فروش', CAST('2026-02-01' AS date), 140),
(3, N'فروش', CAST('2026-03-01' AS date), 120),
(4, N'پشتیبانی', CAST('2026-01-01' AS date), 80),
(5, N'پشتیبانی', CAST('2026-02-01' AS date), 110)
) AS V(Id, Department, SaleDate, Amount)
)
SELECT SaleDate, Amount,
LEAD(Amount) OVER (ORDER BY SaleDate) - Amount AS AmountChange
FROM Sales
WHERE Department = N'فروش'
ORDER BY SaleDate, Id;
| تاریخ | مبلغ | تغییر |
|---|
| 2026-02-01 | 140 | -20 |
| 2026-03-01 | 120 | NULL |
نکته کاربردی: NULL در مرز پارتیشن اطلاعات مفیدی درباره نبود همسایه است و میتوان آن را آگاهانه نگه داشت.
مثال 5: فیلتر کردن نتیجه پنجره با CTE
تابع پنجرهای مستقیماً در WHERE همان SELECT مجاز نیست؛ ابتدا مقدار محاسبه و سپس فیلتر میشود.
WITH Sales AS
(
SELECT *
FROM (VALUES
(1, N'فروش', CAST('2026-01-01' AS date), 100),
(2, N'فروش', CAST('2026-02-01' AS date), 140),
(3, N'فروش', CAST('2026-03-01' AS date), 120),
(4, N'پشتیبانی', CAST('2026-01-01' AS date), 80),
(5, N'پشتیبانی', CAST('2026-02-01' AS date), 110)
) AS V(Id, Department, SaleDate, Amount)
)
, W AS
(
SELECT *, LEAD(Amount) OVER (ORDER BY SaleDate, Id) AS AdjacentAmount
FROM Sales
WHERE Department = N'فروش'
)
SELECT Id, SaleDate, Amount, AdjacentAmount
FROM W
WHERE AdjacentAmount IS NOT NULL
AND Amount < AdjacentAmount;
نکته کاربردی: این الگو هم از نظر ترتیب منطقی SQL صحیح است و هم خوانایی شرط را افزایش میدهد.
مثال 6: رفتار NULL و IGNORE NULLS
در SQL Server 2022 میتوان از نزدیکترین مقدار غیرNULL عبور نکرد و NULLها را هنگام جستوجوی همسایه نادیده گرفت.
WITH D AS
(SELECT * FROM (VALUES (1,10),(2,NULL),(3,30),(4,NULL)) V(Id,Value))
SELECT Id, Value,
LEAD(Value) RESPECT NULLS OVER (ORDER BY Id) AS RespectNulls,
LEAD(Value) IGNORE NULLS OVER (ORDER BY Id) AS IgnoreNulls
FROM D
ORDER BY Id;
| Id | مقدار | با نادیدهگیری NULL |
|---|
| 3 | 30 | NULL |
| 4 | NULL | NULL |
نکته کاربردی: برای IGNORE NULLS نصب بهروزرسانیهای تجمعی SQL Server 2022، بهویژه اصلاحات پس از CU4، بررسی شود.
مثال 7: قطعی کردن ترتیب در داده همزمان
وقتی چند رویداد زمان مساوی دارند، شماره رویداد بهعنوان tie-breaker نتیجه را قطعی میکند.
WITH Events AS
(SELECT * FROM (VALUES (101,CAST('2026-07-20T10:00:00' AS datetime2),N'ثبت'),(102,CAST('2026-07-20T10:00:00' AS datetime2),N'تأیید'),(103,CAST('2026-07-20T10:05:00' AS datetime2),N'ارسال')) V(EventId,EventTime,State))
SELECT EventId, State,
LEAD(State,1,N'مرز') OVER (ORDER BY EventTime, EventId) AS AdjacentState
FROM Events
ORDER BY EventTime, EventId;
| رویداد | وضعیت | وضعیت مجاور |
|---|
| 102 | تأیید | ارسال |
نکته کاربردی: ORDER BY فقط با زمان، در این داده قطعی نیست؛ EventId مشکل را برطرف میکند.
مثال 8: محاسبه فاصله رخدادها
در مانیتورینگ سامانه میتوان فاصله زمانی تا رخداد مجاور را برای کشف وقفه یا SLA محاسبه کرد.
WITH E AS
(SELECT * FROM (VALUES (1,CAST('2026-07-20T08:00:00' AS datetime2)),(2,CAST('2026-07-20T08:07:00' AS datetime2)),(3,CAST('2026-07-20T08:25:00' AS datetime2))) V(Id,EventDate))
SELECT Id, EventDate,
DATEDIFF(day, EventDate, LEAD(EventDate) OVER (ORDER BY EventDate)) AS GapDays
FROM E
ORDER BY EventDate;
| رخداد | زمان | فاصله روز |
|---|
| 2 | 08:07 | 0 |
| 3 | 08:25 | 0 |
نکته کاربردی: برای فاصله دقیقه کافی است datepart تابع DATEDIFF از day به minute تغییر کند.
مثال 9: اصلاح روش اشتباه Self Join
اتصال براساس Id-1 در داده حذفشده یا شناسه غیردنبالهای میشکند؛ پنجره براساس ترتیب واقعی درستتر است.
WITH D AS
(SELECT * FROM (VALUES (10,100),(30,150),(90,130)) V(Id,Amount))
SELECT Id, Amount,
LEAD(Amount) OVER (ORDER BY Id) AS CorrectAdjacent
FROM D
ORDER BY Id;
نکته کاربردی: تابع پنجرهای به پیوستگی مصنوعی شناسه وابسته نیست و منطق کسبوکار را مستقیم بیان میکند.
مثال 10: الگوی ایندکس برای ورودی بزرگ
این مثال موقت نشان میدهد چگونه کلید پارتیشن و ترتیب و ستون INCLUDE برای Query پنجرهای آماده میشوند.
CREATE TABLE #MonthlySales
(DepartmentId int NOT NULL, SaleDate date NOT NULL, Id bigint NOT NULL, Amount decimal(12,2) NOT NULL);
INSERT #MonthlySales VALUES (1,'2026-01-01',1,100),(1,'2026-02-01',2,140),(2,'2026-01-01',3,80);
CREATE INDEX IX_MonthlySales_Window ON #MonthlySales(DepartmentId,SaleDate,Id) INCLUDE(Amount);
SELECT DepartmentId, SaleDate, Amount,
LEAD(Amount) OVER (PARTITION BY DepartmentId ORDER BY SaleDate,Id) AS AdjacentAmount
FROM #MonthlySales;
DROP TABLE #MonthlySales;
| ایندکس | ترتیب کلید | ستون پوششی |
|---|
| IX_MonthlySales_Window | DepartmentId,SaleDate,Id | Amount |
نکته کاربردی: اثر واقعی ایندکس را با Actual Execution Plan، تعداد Sort و Logical Reads مقایسه کنید.