تحديد عدد النتائج LIMIT و OFFSET

بعد أن تعلمنا كيفية ترتيب النتائج باستخدام ORDER BY، نحتاج الآن إلى تعلم كيفية التحكم في عدد النتائج المعروضة. في هذا الدرس الشامل من سلسلة تعلم لغة SQL باللغة العربية، سنتعلم استخدام LIMIT و OFFSET لتحديد عدد الصفوف المسترجعة وتخطي صفوف معينة. هذه المهارة أساسية لبناء أنظمة الصفحات (Pagination) في تطبيقات الويب، وتحسين أداء الاستعلامات، وعرض البيانات بشكل منظم.

1. ما هو LIMIT في SQL؟

LIMIT هو جملة تُستخدم لتحديد الحد الأقصى لعدد الصفوف (Rows) التي يجب إرجاعها من الاستعلام. بدلاً من جلب جميع النتائج (التي قد تكون آلاف أو ملايين الصفوف)، يمكنك تحديد عدد معين فقط.

البنية الأساسية
SELECT column1, column2
FROM table_name
LIMIT number;
لماذا نستخدم LIMIT؟
  • تحسين الأداء: جلب 10 صفوف أسرع بكثير من جلب 10,000 صف
  • تجربة المستخدم: عرض البيانات على صفحات بدلاً من قائمة طويلة جداً
  • توفير الموارد: تقليل استهلاك الذاكرة والشبكة
  • الاختبار: عرض عينة من البيانات للتحقق من الاستعلام

2. أمثلة أساسية على LIMIT

لنفترض أن لدينا جدول المنتجات التالي:

جدول products (يحتوي على 100 منتج)
+----+------------------+--------+-------+
| id | name             | price  | stock |
+----+------------------+--------+-------+
| 1  | لابتوب Dell      | 3500   | 15    |
| 2  | ماوس لاسلكي      | 50     | 100   |
| 3  | لوحة مفاتيح      | 120    | 50    |
| 4  | شاشة Samsung     | 800    | 25    |
| 5  | سماعات           | 150    | 60    |
| 6  | كاميرا ويب       | 200    | 40    |
| 7  | هارد خارجي       | 350    | 30    |
| 8  | طابعة HP         | 600    | 20    |
...  (92 منتج آخر)
+----+------------------+--------+-------+
مثال 1: جلب أول 5 منتجات فقط
LIMIT 5
SELECT * FROM products
LIMIT 5;
النتيجة (5 صفوف فقط)
+----+------------------+--------+-------+
| id | name             | price  | stock |
+----+------------------+--------+-------+
| 1  | لابتوب Dell      | 3500   | 15    |
| 2  | ماوس لاسلكي      | 50     | 100   |
| 3  | لوحة مفاتيح      | 120    | 50    |
| 4  | شاشة Samsung     | 800    | 25    |
| 5  | سماعات           | 150    | 60    |
+----+------------------+--------+-------+
5 rows in set

-- لاحظ: تم جلب 5 صفوف فقط من أصل 100
مثال 2: أغلى 3 منتجات
دمج ORDER BY مع LIMIT
SELECT name, price FROM products
ORDER BY price DESC
LIMIT 3;
النتيجة
+------------------+--------+
| name             | price  |
+------------------+--------+
| لابتوب Dell      | 3500   | ← الأغلى
| شاشة Samsung     | 800    |
| طابعة HP         | 600    | ← الثالث
+------------------+--------+
3 rows in set
مثال 3: أقل 10 منتجات مخزوناً
تنبيه نفاد المخزون
SELECT name, stock FROM products
ORDER BY stock ASC
LIMIT 10;

شرح: هذا الاستعلام مفيد جداً في أنظمة المخازن لمعرفة المنتجات التي توشك على النفاد.

3. ما هو OFFSET في SQL؟

OFFSET يُستخدم لتخطي (Skip) عدد معين من الصفوف قبل البدء في جلب النتائج. يُستخدم عادة مع LIMIT لتنفيذ نظام الصفحات (Pagination).

البنية الأساسية
SELECT column1, column2
FROM table_name
LIMIT number OFFSET skip_number;

-- أو بصيغة مختصرة:
SELECT column1, column2
FROM table_name
LIMIT skip_number, number;
ملاحظة مهمة: الصيغة المختصرة LIMIT skip, number تعمل في MySQL و MariaDB، لكن الصيغة القياسية LIMIT number OFFSET skip تعمل في معظم أنظمة SQL.

4. أمثلة على OFFSET

مثال 1: تخطي أول 5 صفوف
OFFSET 5
-- جلب 5 منتجات، بعد تخطي أول 5
SELECT * FROM products
LIMIT 5 OFFSET 5;
النتيجة (الصفوف 6-10)
+----+------------------+--------+-------+
| id | name             | price  | stock |
+----+------------------+--------+-------+
| 6  | كاميرا ويب       | 200    | 40    | ← بدأ من الصف 6
| 7  | هارد خارجي       | 350    | 30    |
| 8  | طابعة HP         | 600    | 20    |
| 9  | راوتر            | 180    | 35    |
| 10 | كابل HDMI        | 30     | 200   | ← انتهى عند الصف 10
+----+------------------+--------+-------+
5 rows in set

-- تم تخطي الصفوف 1-5، وجلب الصفوف 6-10
مثال 2: الصيغة المختصرة (MySQL)
LIMIT offset, count
-- هذان الاستعلامان متطابقان في MySQL:

-- الصيغة القياسية (تعمل في كل الأنظمة)
SELECT * FROM products LIMIT 5 OFFSET 10;

-- الصيغة المختصرة (MySQL فقط)
SELECT * FROM products LIMIT 10, 5;
--                            ↑   ↑
--                         OFFSET LIMIT
نصيحة: استخدم الصيغة القياسية LIMIT number OFFSET skip لأنها أوضح وتعمل في معظم أنظمة SQL.

5. بناء نظام الصفحات (Pagination)

الاستخدام الأكثر شيوعاً لـ LIMIT و OFFSET هو بناء نظام الصفحات في تطبيقات الويب. بدلاً من عرض 10,000 منتج في صفحة واحدة، نعرض 20 منتج في كل صفحة.

الصيغة الرياضية للصفحات
معادلة الصفحات
-- المتغيرات:
-- page_number = رقم الصفحة (1, 2, 3, ...)
-- items_per_page = عدد العناصر في كل صفحة (مثلاً 20)

-- المعادلة:
OFFSET = (page_number - 1) × items_per_page
LIMIT = items_per_page
مثال عملي: نظام صفحات للمنتجات

لنفترض أننا نريد عرض 10 منتجات في كل صفحة:

الصفحة 1 (المنتجات 1-10)
-- الصفحة 1: OFFSET = (1-1) × 10 = 0
SELECT * FROM products
ORDER BY id
LIMIT 10 OFFSET 0;

-- أو ببساطة:
SELECT * FROM products
ORDER BY id
LIMIT 10;
الصفحة 2 (المنتجات 11-20)
-- الصفحة 2: OFFSET = (2-1) × 10 = 10
SELECT * FROM products
ORDER BY id
LIMIT 10 OFFSET 10;
الصفحة 3 (المنتجات 21-30)
-- الصفحة 3: OFFSET = (3-1) × 10 = 20
SELECT * FROM products
ORDER BY id
LIMIT 10 OFFSET 20;
مثال: كود PHP لنظام الصفحات
تطبيق عملي في PHP
<?php
// إعدادات الصفحات
$items_per_page = 20;
$current_page = isset($_GET['page']) ? (int)$_GET['page'] : 1;

// حساب OFFSET
$offset = ($current_page - 1) * $items_per_page;

// بناء الاستعلام
$sql = "SELECT * FROM products 
        ORDER BY id 
        LIMIT $items_per_page OFFSET $offset";

// تنفيذ الاستعلام
$result = mysqli_query($conn, $sql);

// عرض النتائج
while ($row = mysqli_fetch_assoc($result)) {
    echo $row['name'] . " - " . $row['price'] . "<br>";
}
?>

6. الاختلافات بين أنظمة SQL

ليست كل أنظمة قواعد البيانات تستخدم نفس الصيغة لـ LIMIT. إليك الاختلافات الرئيسية:

نظام SQL الصيغة مثال
MySQL / MariaDB LIMIT n OFFSET m LIMIT 10 OFFSET 5
PostgreSQL LIMIT n OFFSET m LIMIT 10 OFFSET 5
SQLite LIMIT n OFFSET m LIMIT 10 OFFSET 5
SQL Server OFFSET m ROWS FETCH NEXT n ROWS ONLY OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY
Oracle OFFSET m ROWS FETCH NEXT n ROWS ONLY OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY
مثال: SQL Server
صيغة SQL Server
-- SQL Server يتطلب ORDER BY مع OFFSET
SELECT * FROM products
ORDER BY id
OFFSET 10 ROWS
FETCH NEXT 5 ROWS ONLY;
مثال: Oracle (الإصدارات القديمة)
Oracle القديم (قبل 12c)
-- Oracle القديم يستخدم ROWNUM
SELECT * FROM (
    SELECT p.*, ROWNUM rnum FROM (
        SELECT * FROM products ORDER BY id
    ) p WHERE ROWNUM <= 15
) WHERE rnum > 10;

-- Oracle 12c والأحدث:
SELECT * FROM products
ORDER BY id
OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY;

7. أمثلة عملية متقدمة

مثال 1: أحدث 10 طلبات
لوحة التحكم - آخر الطلبات
SELECT 
    order_id,
    customer_name,
    order_date,
    total_amount
FROM orders
ORDER BY order_date DESC
LIMIT 10;
مثال 2: أفضل 5 موظفين في المبيعات
تقرير الأداء
SELECT 
    employee_name,
    COUNT(*) AS total_sales,
    SUM(amount) AS total_revenue
FROM sales
WHERE YEAR(sale_date) = 2024
GROUP BY employee_name
ORDER BY total_revenue DESC
LIMIT 5;
مثال 3: المقالات الأكثر قراءة (مع الصفحات)
نظام المدونة
-- الصفحة 1: المقالات 1-10
SELECT 
    title,
    author,
    views,
    published_date
FROM articles
WHERE status = 'published'
ORDER BY views DESC
LIMIT 10 OFFSET 0;

-- الصفحة 2: المقالات 11-20
SELECT 
    title,
    author,
    views,
    published_date
FROM articles
WHERE status = 'published'
ORDER BY views DESC
LIMIT 10 OFFSET 10;
مثال 4: عينة عشوائية من البيانات
اختيار عشوائي
-- MySQL: جلب 5 منتجات عشوائية
SELECT * FROM products
ORDER BY RAND()
LIMIT 5;

-- PostgreSQL: جلب 5 منتجات عشوائية
SELECT * FROM products
ORDER BY RANDOM()
LIMIT 5;
مثال 5: تخطي النتائج المكررة
الصف الثاني والثالث من كل مجموعة
-- جلب الموظفين من الترتيب 2 إلى 4 في كل قسم
SELECT 
    department,
    employee_name,
    salary
FROM (
    SELECT 
        department,
        employee_name,
        salary,
        ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rank
    FROM employees
) ranked
WHERE rank BETWEEN 2 AND 4;

8. حساب عدد الصفحات الإجمالي

لبناء نظام صفحات كامل، نحتاج لمعرفة العدد الإجمالي للصفحات. هذا يتطلب معرفة إجمالي عدد الصفوف.

حساب عدد الصفحات
-- 1. احسب إجمالي عدد المنتجات
SELECT COUNT(*) AS total_products FROM products;
-- النتيجة: 100

-- 2. احسب عدد الصفحات
-- إذا كان لدينا 100 منتج، و 10 منتجات في كل صفحة:
-- عدد الصفحات = CEILING(100 / 10) = 10 صفحات
مثال كامل في PHP
نظام صفحات كامل
<?php
// الإعدادات
$items_per_page = 20;
$current_page = isset($_GET['page']) ? (int)$_GET['page'] : 1;

// 1. احسب إجمالي عدد المنتجات
$count_sql = "SELECT COUNT(*) AS total FROM products";
$count_result = mysqli_query($conn, $count_sql);
$total_items = mysqli_fetch_assoc($count_result)['total'];

// 2. احسب عدد الصفحات
$total_pages = ceil($total_items / $items_per_page);

// 3. احسب OFFSET
$offset = ($current_page - 1) * $items_per_page;

// 4. جلب البيانات
$sql = "SELECT * FROM products 
        ORDER BY id 
        LIMIT $items_per_page OFFSET $offset";
$result = mysqli_query($conn, $sql);

// 5. عرض النتائج
while ($row = mysqli_fetch_assoc($result)) {
    echo $row['name'] . "<br>";
}

// 6. عرض روابط الصفحات
echo "<div class='pagination'>";
for ($i = 1; $i <= $total_pages; $i++) {
    $active = ($i == $current_page) ? 'active' : '';
    echo "<a href='?page=$i' class='$active'>$i</a> ";
}
echo "</div>";
?>

9. الأداء والتحسينات (Performance)

مشكلة OFFSET الكبير

عندما يكون OFFSET كبيراً جداً (مثلاً 100,000)، يصبح الاستعلام بطيئاً لأن قاعدة البيانات يجب أن تقرأ وتتخطى جميع الصفوف قبل الوصول للنتائج المطلوبة.

مشكلة الأداء
-- بطيء جداً: يجب قراءة 100,000 صف ثم تخطيها
SELECT * FROM products
ORDER BY id
LIMIT 10 OFFSET 100000;
الحل: استخدام WHERE بدلاً من OFFSET
تحسين الأداء
-- بدلاً من OFFSET، استخدم WHERE مع آخر ID
-- افترض أن آخر منتج في الصفحة السابقة كان id = 100000

-- سريع: يستخدم الفهرس (Index) مباشرة
SELECT * FROM products
WHERE id > 100000
ORDER BY id
LIMIT 10;
مثال: Cursor-based Pagination
صفحات بناءً على المؤشر
-- الصفحة الأولى
SELECT * FROM products
ORDER BY created_at DESC, id DESC
LIMIT 20;

-- الصفحة التالية (بناءً على آخر created_at و id)
SELECT * FROM products
WHERE (created_at, id) < ('2024-01-15 10:30:00', 12345)
ORDER BY created_at DESC, id DESC
LIMIT 20;
نصيحة للأداء: استخدم Cursor-based Pagination للجداول الكبيرة جداً، و Offset-based Pagination للجداول الصغيرة والمتوسطة.

10. نصائح وأفضل الممارسات

1. استخدم دائماً ORDER BY مع LIMIT
أفضل ممارسة
-- ❌ سيء: النتائج غير متوقعة
SELECT * FROM products LIMIT 10;

-- ✅ جيد: النتائج مرتبة ومتوقعة
SELECT * FROM products ORDER BY id LIMIT 10;
2. تحقق من صحة رقم الصفحة
التحقق من المدخلات
<?php
// تحقق من صحة رقم الصفحة
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;

// تأكد أن الصفحة >= 1
if ($page < 1) {
    $page = 1;
}

// تأكد أن الصفحة <= عدد الصفحات الإجمالي
if ($page > $total_pages) {
    $page = $total_pages;
}
?>
3. استخدم الفهارس (Indexes)

تأكد من وجود فهرس على الأعمدة المستخدمة في ORDER BY لتحسين الأداء.

إنشاء فهرس
-- إنشاء فهرس على عمود created_at
CREATE INDEX idx_created_at ON products(created_at);

-- الآن هذا الاستعلام سيكون أسرع بكثير
SELECT * FROM products
ORDER BY created_at DESC
LIMIT 20;
4. احفظ النتائج في الذاكرة المؤقتة (Cache)

للصفحات التي يتم الوصول إليها بكثرة، احفظ النتائج في Cache لتقليل الضغط على قاعدة البيانات.

5. اعرض عدد النتائج الإجمالي
تجربة مستخدم أفضل
-- اعرض للمستخدم: "عرض 1-20 من أصل 1,543 منتج"
SELECT COUNT(*) AS total FROM products;
-- ثم اعرض النتائج مع LIMIT
ملخص الدرس
  • LIMIT: يحدد عدد الصفوف المسترجعة من الاستعلام
  • OFFSET: يتخطى عدداً معيناً من الصفوف قبل البدء في الجلب
  • الصيغة: LIMIT n OFFSET m (قياسية) أو LIMIT m, n (MySQL)
  • Pagination: OFFSET = (page - 1) × items_per_page
  • الأداء: استخدم WHERE بدلاً من OFFSET الكبير للجداول الضخمة
  • Best Practice: استخدم دائماً ORDER BY مع LIMIT لنتائج متوقعة
  • الاختلافات: SQL Server و Oracle يستخدمان صيغة مختلفة

الخطوة التالية: البحث بالأنماط باستخدام LIKE في SQL

أكمل رحلتك التعليمية وانتقل إلى الدرس التالي لتعلم البحث بالأنماط باستخدام LIKE في وتطوير مهاراتك في قواعد البيانات.

الانتقال إلى الدرس التالي
المحرر الذكي

اكتب الكود وشاهد النتيجة فوراً

جرب الآن مجاناً
قناة ديف عربي

تابع أحدث الدروس والتحديثات مباشرة على واتساب

انضم الآن