آموزش ROW_NUMBER در SQL Server با مثال صفحه‌بندی و حذف تکراری‌ها

آموزش تابع ROW_NUMBER در SQL Server و شماره‌گذاری حرفه‌ای رکوردها

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

نظرات 0

آموزش تابع ROW_NUMBER در SQL Server و شماره‌گذاری حرفه‌ای رکوردها

مقدمه

ROW_NUMBER یکی از توابع رتبه‌بندی و پنجره‌ای SQL Server است. این تابع برای هر ردیف نتیجه یک شماره ترتیبی یکتا تولید می‌کند. شماره‌گذاری از عدد یک آغاز می‌شود و ترتیب آن بر اساس ORDER BY داخل عبارت OVER تعیین می‌گردد. ROW_NUMBER در صفحه‌بندی نتایج، انتخاب اولین یا آخرین رکورد هر گروه، حذف داده‌های تکراری، تهیه گزارش و ساخت شماره ردیف کاربرد زیادی دارد.

تفاوت مهم ROW_NUMBER با ستون Identity این است که Identity یک مقدار ذخیره‌شده در جدول است، اما ROW_NUMBER هنگام اجرای کوئری محاسبه می‌شود و بخشی از داده دائمی نیست. بنابراین با تغییر فیلتر یا ترتیب، شماره ردیف‌ها نیز تغییر می‌کنند.

تعریف و نحو ROW_NUMBER

ROW_NUMBER() OVER
(
    [ PARTITION BY partition_expression ]
    ORDER BY order_expression [ ASC | DESC ]
)

پارامترها

  • PARTITION BY اختیاری است و نتیجه را به گروه‌های مستقل تقسیم می‌کند.
  • ORDER BY اجباری است و ترتیب اختصاص شماره را مشخص می‌کند.
  • هر گروه در PARTITION BY شماره‌گذاری را دوباره از یک آغاز می‌کند.

نوع خروجی

نوع خروجی ROW_NUMBER برابر bigint است. این ویژگی باعث می‌شود شماره‌گذاری مجموعه‌های بسیار بزرگ نیز از محدوده int عبور نکند.

مثال پایه

DECLARE @Employees TABLE
(
    EmployeeID int,
    FullName nvarchar(100),
    Salary decimal(18,2)
);

INSERT INTO @Employees VALUES
(10, N'مریم احمدی', 42000000),
(11, N'علی کریمی', 51000000),
(12, N'رضا محمدی', 47000000);

SELECT
    ROW_NUMBER() OVER (ORDER BY Salary DESC) AS RowNo,
    EmployeeID,
    FullName,
    Salary
FROM @Employees;
RowNoEmployeeIDFullNameSalary
111علی کریمی51000000
212رضا محمدی47000000
310مریم احمدی42000000

استفاده از PARTITION BY

DECLARE @Sales TABLE
(
    SaleID int,
    SellerName nvarchar(100),
    SaleYear int,
    Amount decimal(18,2)
);

INSERT INTO @Sales VALUES
(1, N'علی', 1404, 10000000),
(2, N'مریم', 1404, 18000000),
(3, N'حسن', 1405, 15000000),
(4, N'زهرا', 1405, 21000000);

SELECT
    SaleYear,
    SellerName,
    Amount,
    ROW_NUMBER() OVER
    (
        PARTITION BY SaleYear
        ORDER BY Amount DESC
    ) AS RowInYear
FROM @Sales;

برای هر سال شماره‌گذاری از یک شروع می‌شود. این الگو برای انتخاب بهترین فروشنده هر سال یا آخرین سفارش هر مشتری بسیار مفید است.

انتخاب جدیدترین رکورد هر گروه

WITH RankedNews AS
(
    SELECT
        NewsID,
        NewsGroupID,
        NewsTitle,
        PublishedDate,
        ROW_NUMBER() OVER
        (
            PARTITION BY NewsGroupID
            ORDER BY PublishedDate DESC, NewsID DESC
        ) AS RowNo
    FROM dbo.tblNewsContent
)
SELECT NewsID, NewsGroupID, NewsTitle, PublishedDate
FROM RankedNews
WHERE RowNo = 1;

استفاده از NewsID به عنوان معیار دوم، نتیجه را در زمان برابر بودن PublishedDate قطعی می‌کند. اگر ORDER BY یکتا نباشد، ترتیب ردیف‌های هم‌ارزش ممکن است در اجراهای مختلف تغییر کند.

صفحه‌بندی با ROW_NUMBER

DECLARE @PageNumber int = 3;
DECLARE @PageSize int = 20;

WITH NumberedRows AS
(
    SELECT
        NewsID,
        NewsTitle,
        PublishedDate,
        ROW_NUMBER() OVER
        (
            ORDER BY PublishedDate DESC, NewsID DESC
        ) AS RowNo
    FROM dbo.tblNewsContent
    WHERE ArchiveState = 0
)
SELECT NewsID, NewsTitle, PublishedDate
FROM NumberedRows
WHERE RowNo BETWEEN
      ((@PageNumber - 1) * @PageSize) + 1
      AND (@PageNumber * @PageSize)
ORDER BY RowNo;

در نسخه‌های جدید SQL Server استفاده از OFFSET و FETCH نیز رایج است، اما ROW_NUMBER زمانی مفید است که علاوه بر صفحه‌بندی به شماره ردیف یا منطق پیچیده‌تر نیاز دارید.

حذف داده‌های تکراری با حفظ یک ردیف

WITH DuplicateRows AS
(
    SELECT
        NewsTagID,
        NewsID,
        TagDescription,
        ROW_NUMBER() OVER
        (
            PARTITION BY NewsID, TagDescription
            ORDER BY NewsTagID
        ) AS RowNo
    FROM dbo.tblTags
)
DELETE FROM DuplicateRows
WHERE RowNo > 1;

قبل از اجرای DELETE، همان CTE را با SELECT بررسی کنید و از پایگاه داده نسخه پشتیبان داشته باشید. در جدول نمونه شما کلید اصلی ترکیبی از NewsID و TagDescription است و اصولاً اجازه درج تکراری نمی‌دهد، اما این الگو برای جدول‌های بدون محدودیت یکتا کاربردی است.

مقایسه ROW_NUMBER، RANK و DENSE_RANK

تابعرفتار در مقدارهای مساویپرش شماره
ROW_NUMBERبه هر ردیف شماره متفاوت می‌دهدندارد
RANKرتبه برابر می‌دهددارد
DENSE_RANKرتبه برابر می‌دهدندارد
SELECT
    FullName,
    Salary,
    ROW_NUMBER() OVER (ORDER BY Salary DESC) AS RowNumberValue,
    RANK() OVER (ORDER BY Salary DESC) AS RankValue,
    DENSE_RANK() OVER (ORDER BY Salary DESC) AS DenseRankValue
FROM @Employees;

خطاها و اشتباهات رایج

  • استفاده از ROW_NUMBER بدون ORDER BY که از نظر نحو مجاز نیست.
  • تصور اینکه ORDER BY بیرونی جای ORDER BY داخل OVER را می‌گیرد.
  • استفاده از ترتیب غیر یکتا و انتظار نتیجه کاملاً ثابت.
  • حذف داده تکراری بدون بررسی اولیه ردیف‌های RowNo بزرگ‌تر از یک.
  • فیلتر کردن مستقیم روی نام مستعار RowNo در همان سطح SELECT؛ معمولاً باید CTE یا زیرکوئری استفاده شود.

نکات کارایی

توابع پنجره‌ای اغلب به مرتب‌سازی نیاز دارند. وجود ایندکسی که ستون‌های PARTITION BY و ORDER BY را پوشش دهد می‌تواند هزینه Sort را کاهش دهد. برای مثال، در انتخاب آخرین خبر هر گروه، ایندکس روی NewsGroupID و PublishedDate و NewsID می‌تواند مفید باشد. با این حال طراحی ایندکس باید بر اساس حجم جدول، انتخاب‌پذیری و سایر عملیات نوشتن انجام شود. طرح اجرا را بررسی کنید و به Sort، Window Spool، Memory Grant و Spill به tempdb توجه داشته باشید.

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

  1. در ORDER BY یک ستون یکتا به عنوان معیار نهایی اضافه کنید.
  2. فیلترهای اصلی را پیش از اجرای تابع پنجره‌ای اعمال کنید.
  3. برای صفحه‌بندی روی مجموعه بسیار بزرگ، روش Keyset Pagination را نیز بررسی کنید.
  4. پیش از DELETE مبتنی بر ROW_NUMBER، نتیجه را با SELECT کنترل کنید.
  5. ایندکس را بر اساس PARTITION BY و ORDER BY طراحی و آزمایش کنید.

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

آیا ROW_NUMBER مقدار دائمی ایجاد می‌کند؟

خیر. شماره در زمان اجرای کوئری ساخته می‌شود و در جدول ذخیره نمی‌گردد.

آیا دو ردیف می‌توانند ROW_NUMBER برابر داشته باشند؟

در یک مجموعه یا پارتیشن خیر. هر ردیف شماره یکتا می‌گیرد.

چه زمانی RANK بهتر است؟

زمانی که ردیف‌های هم‌امتیاز باید رتبه یکسان بگیرند، مانند رتبه‌بندی مسابقه.

چک‌لیست و جمع‌بندی

  • ORDER BY قطعی و ترجیحاً یکتا است.
  • PARTITION BY دقیقاً مطابق گروه‌بندی کسب‌وکار انتخاب شده است.
  • فیلترها پیش از محاسبه اعمال شده‌اند.
  • ایندکس و طرح اجرا بررسی شده‌اند.
  • عملیات حذف ابتدا با SELECT آزمایش شده است.

ROW_NUMBER ابزاری قدرتمند برای شماره‌گذاری و تحلیل ترتیبی داده‌ها است. با ترکیب صحیح OVER، PARTITION BY و ORDER BY می‌توان بسیاری از مسائل پیچیده گزارش‌گیری، صفحه‌بندی و پاک‌سازی داده را با کوئری خوانا و قابل نگهداری حل کرد.

 

0 نظر

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

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

حرف 500 حداکثر