بدون ORDER BY، ترتيب الصفوف غير مضمون
عند تنفيذ استعلام SELECT بدون ORDER BY، تُرجع SQLite الصفوف بالترتيب الذي يناسبها داخليًا. صحيح أن النتيجة قد تبدو أحيانًا مطابقة لترتيب الإدخال في الجداول الصغيرة، وهذا ما يدفع كثيرين إلى الاعتماد عليه. لكن لا تقع في هذا الفخ. فبمجرد أن يُستخدم فهرس ما، أو يكبر حجم الجدول، أو تتغير خطة تنفيذ الاستعلام، قد ينقلب الترتيب دون أي تحذير.
إن كان ترتيب الصفوف يهمك فعلًا، فاذكر ذلك صراحةً في الاستعلام:
ORDER BY name يرتّب النتائج تصاعديًا بشكل افتراضي. تأتيك النتيجة هكذا: Ada، Boris، Chen، Rosa — مرتّبة أبجديًا في كل مرة، بغضّ النظر عن طريقة تخزين الجدول على القرص.
الترتيب التصاعدي والتنازلي: ASC و DESC
ASC تعني تصاعدي (من الأصغر إلى الأكبر، ومن الألف إلى الياء، ومن الأقدم إلى الأحدث). أما DESC فتعني تنازلي — أي العكس تمامًا. وبما أن ASC هو الوضع الافتراضي، فستجد أن أغلب المطورين يحذفونه من الاستعلام:
بهذا الشكل تظهر أحدث التسجيلات أولاً. التواريخ المخزّنة كنصوص بصيغة ISO 8601 (YYYY-MM-DD) تُرتَّب بشكل صحيح حتى لو عُومِلت كنصوص، وهذا أحد أهم الأسباب التي تجعل هذه الصيغة هي المفضّلة لتخزين التواريخ في SQLite، خاصةً أنه لا يوجد نوع بيانات مخصّص للتاريخ.
الترتيب حسب أكثر من عمود في SQLite
عندما تتساوى القيم في العمود الأول المستخدم للترتيب، يلجأ SQLite إلى العمود الثاني لفك التعادل. اكتب الأعمدة مفصولة بفواصل، وبالترتيب الذي يعكس أولويتها:
تُجمَّع الصفوف أولًا حسب الدولة (FR تأتي قبل US)، ثم تُرتَّب بعد ذلك أسماء العملاء داخل كل دولة. ويمكن تحديد اتجاه ترتيب مستقل لكل عمود:
ترتيب الدول تصاعديًا، ثم الأحدث أولًا داخل كل دولة. لاحظ أن ASC و DESC تطبَّق على العمود المجاور لها فقط، ولا تنسحب على بقية الأعمدة.
الترتيب باستخدام التعابير والأسماء المستعارة
تقبل جملة ORDER BY أي تعبير، لا أسماء الأعمدة فقط. وهذا مفيد جدًا عند التعامل مع القيم المحسوبة:
الاسم المستعار revenue من قائمة SELECT متاح للاستخدام داخل ORDER BY بلا مشاكل. وبإمكانك أيضًا إعادة كتابة التعبير كاملًا — ORDER BY price * quantity DESC — وستحصل على نفس النتيجة تمامًا.
كذلك يمكنك الترتيب بناءً على رقم العمود في قائمة الاختيار، لكنها عادة يُفضّل تجنّبها:
SELECT name, price FROM products ORDER BY 2 DESC;
2 يعني العمود الثاني في قائمة SELECT. هذه الطريقة تشتغل، لكن لو جاء أحدهم لاحقًا وأعاد ترتيب الأعمدة، فإن منطق الترتيب سيتغيّر بصمت دون أن ينتبه أحد. الأفضل دائمًا أن ترتّب باستخدام اسم العمود أو اسمه المستعار (alias).
أين تظهر قيم NULL عند الترتيب؟
قيمة NULL تعني "غير معروف"، ولذلك على SQLite أن يقرر أين يضع هذه القيم المجهولة ضمن النتائج المرتّبة. القاعدة الافتراضية: قيم NULL تظهر في البداية مع ASC، وفي النهاية مع DESC.
يظهر كلٌّ من Ada وChen في الأعلى قبل أي تاريخ فعلي، وهذا نادرًا ما يكون المطلوب عند الترتيب من الأحدث إلى الأقدم. لتجاوز هذا السلوك، استخدم NULLS LAST:
الآن تظهر التواريخ الفعلية أولاً، وتُرحَّل قيم NULL إلى الأسفل. أما NULLS FIRST فيفعل العكس تماماً. وكلتا الصيغتين جزء من معيار SQL القياسي، وتعملان في SQLite ابتداءً من الإصدار 3.30 فما فوق.
ترتيب النصوص بدون حساسية لحالة الأحرف باستخدام COLLATE NOCASE
المقارنة الافتراضية للنصوص في SQLite ثنائية بحتة، أي أنها تعتمد على ترتيب نقاط Unicode. هذا يعني أن الأحرف الكبيرة تأتي قبل الصغيرة، فتجد مثلاً أن 'Zoe' يسبق 'apple':
النتيجة هي Boris, Zoe, ada, apple — تظهر الأحرف الكبيرة أولاً ثم الصغيرة. وللترتيب دون التمييز بين حالة الأحرف، استخدم ترتيب المقارنة NOCASE:
الآن ستحصل على ada, apple, Boris, Zoe. لاحظ أن NOCASE يتعامل فقط مع أحرف ASCII من A–Z و a–z كأحرف متكافئة، فهو لا يُطبّع الأحرف ذات العلامات (accents) ولا الحروف خارج نطاق ASCII. إذا كنت تحتاج إلى ترتيب نصوص بلغات متعددة بشكل صحيح، فستحتاج إلى تعريف ترتيب مخصص (collation) على مستوى التطبيق نفسه. لكن لأغلب حالات النصوص الإنجليزية، يفي NOCASE بالغرض.
ترتيب عشوائي للنتائج باستخدام ORDER BY RANDOM
أحياناً تحتاج إلى استرجاع الصفوف بترتيب عشوائي، مثل اختيار عنصر مميّز يومياً أو أخذ عيّنة عشوائية من البيانات لأغراض الاختبار. توفّر SQLite الدالة random() التي تُرجع عدداً صحيحاً عشوائياً، ويمكنك ببساطة الترتيب وفقاً لها:
كل صف يحصل على قيمة عشوائية جديدة، ثم يتم الفرز بناءً عليها. هذا مناسب للجداول الصغيرة. أما مع الجداول الكبيرة، فإن ORDER BY random() بطيء — لأنه مضطر لحساب قيمة عشوائية لكل صف ثم فرز النتيجة كاملة. ولو كنت تريد سحب صف واحد عشوائي من جدول ضخم، فهناك طرق أذكى وأسرع (مثل اختيار rowid عشوائي).
أخطاء شائعة عند ترتيب النتائج في SQLite
في حاجات تتكرر كثيرًا وتوقع المبتدئين:
- نسيان
ORDER BYوالافتراض أن الترتيب ثابت. بدونه، الترتيب غير معرَّف. حتى لو بدا لك مستقرًا، فهو ليس كذلك فعليًا. - فرز أرقام مخزَّنة كنص. القيمة
'10'تأتي قبل'2'في الترتيب الأبجدي. إذا كان العمود يجب أن يُرتَّب كأرقام، فاحفظه بنوع رقمي (أو استخدم التحويل:ORDER BY CAST(value AS INTEGER)). - خلط ASC و DESC بين الأعمدة. كل عمود له اتجاهه المستقل. الجملة
ORDER BY a, b DESCترتّبaتصاعديًا وbتنازليًا، وليس كليهما تنازليًا. - فرز نتيجة ضخمة لأخذ أول عدد قليل من الصفوف فقط. اربط
ORDER BYمعLIMITوضع فهرسًا على عمود الفرز — وهذا هو موضوع الصفحة التالية.
التالي: LIMIT و OFFSET
الفرز يخبر SQLite كيف يرتب الصفوف، أما LIMIT و OFFSET فيحددان كم صفًا تريد ومن أين يبدأ. وهما معًا أساس التصفّح (Pagination) واستعلامات "أفضل N نتيجة" — وهذا ما سنتناوله بعد قليل.
الأسئلة الشائعة
كيف أرتّب نتائج الاستعلام في SQLite؟
أضف جملة ORDER BY في نهاية استعلام SELECT متبوعةً باسم العمود المراد الترتيب حسبه. مثلاً SELECT * FROM users ORDER BY name; يرتّب النتائج تصاعدياً، ولو أردته تنازلياً أضف DESC هكذا: ORDER BY name DESC. انتبه: بدون ORDER BY لا يوجد ضمان لترتيب الصفوف، وحتى لو ظهرت بترتيب ثابت في كل مرة، لا تعتمد على ذلك أبداً.
كيف أرتّب حسب أكثر من عمود في SQLite؟
اذكر الأعمدة مفصولة بفواصل، مثل ORDER BY country, name. سيقوم SQLite بالترتيب حسب العمود الأول، ثم يستخدم العمود الثاني لفك التعادل عند تطابق القيم. ويمكنك تحديد اتجاه مختلف لكل عمود، كما في: ORDER BY country ASC, signup_date DESC.
كيف أعمل ترتيباً لا يفرّق بين الحروف الكبيرة والصغيرة في SQLite؟
استخدم COLLATE NOCASE ضمن جملة ORDER BY، مثل: ORDER BY name COLLATE NOCASE. افتراضياً يستخدم SQLite ترتيباً ثنائياً (binary) للنصوص، لذا تأتي Zoe قبل apple لأن الحروف الكبيرة لها قيم ASCII أصغر. أما NOCASE فيتعامل مع الحرف الكبير والصغير على أنهما متساويان.
أين تظهر قيم NULL في النتائج المرتبة في SQLite؟
افتراضياً تظهر قيم NULL في البداية عند الترتيب التصاعدي، وفي النهاية عند الترتيب التنازلي. ويمكنك تجاوز هذا السلوك باستخدام NULLS FIRST أو NULLS LAST؛ فمثلاً ORDER BY signup_date DESC NULLS LAST يبقي التواريخ الفعلية في الأعلى ويدفع القيم الفارغة إلى الأسفل.