أساسيات SQL: SELECT وWHERE وORDER BY
لغة SQL (Structured Query Language) هي اللغة القياسية للاستعلام عن البيانات في قواعد البيانات العلائقية. وجملة SELECT تقرأ الصفوف دون أن تغيّر شيئاً، فهي القراءة (Read) في CRUD. وتسمّي FROM الجدول الذي تُقرأ منه الصفوف، وتؤدي ثلاث عبارات معظم العمل: SELECT تحدد الأعمدة، وWHERE تُبقي الصفوف التي تحقق شرطاً، وORDER BY ترتّب النتيجة.
مثال ذلك: SELECT order_id, status FROM orders WHERE customer_id = 207 ORDER BY created_at DESC; تعرض طلبات العميل 207 من الأحدث إلى الأقدم. ويستخدم المختبِرون استعلامات كهذه ليروا ما خزّنه التطبيق فعلاً، وليستخرجوا النتائج المتوقعة.
لماذا يهم
تعرض الشاشة والواجهة البرمجية (API) البيانات بعد أن يرشّحها التطبيق وينسّقها، أما قاعدة البيانات فتُظهر ما خُزّن فعلاً. فإذا فشل اختبار، أو نجح لسبب خاطئ، بيّن استعلام قصير هل العيب في البيانات أم في الشيفرة التي تعرضها، ومنح المطور دليلاً يستطيع إعادة تشغيله.
وجملة SELECT هي أيضاً موضع الأخطاء الصامتة لدى المبتدئين: فالشرط المكتوب = NULL، أو الأقواس الناقصة، أو قائمة تُقرأ دون ORDER BY، لا تُظهر أي رسالة خطأ. بل تعيد صفوفاً خاطئة، والمختبِر الذي يثق بها يحكم بنجاح ميزة معطوبة أو يبلّغ عن عيب غير موجود.
كيف تُبنى جملة SELECT
لغة SQL تصريحية (Declarative): تصف النتيجة، وتقرر قاعدة البيانات كيف تُنتجها. ويعرّف اللغةَ المعيارُ ISO/IEC 9075، ويطبّق كل محرك، مثل PostgreSQL وMySQL وSQLite وSQL Server، لهجةً (Dialect) منها؛ وتعمل الأساسيات هنا فيها جميعاً ما لم تذكر ملاحظة غير ذلك.
تتكون الجملة من عبارات (Clauses) تبدأ بكلمات مفتاحية. ولا تميّز الكلمات المفتاحية حالة الأحرف، لكن الأحرف الكبيرة تجعل الاستعلام أسهل قراءة. ويوضع النص بين علامتي اقتباس مفردتين ('PAID')؛ أما علامات الاقتباس المزدوجة فتعني في SQL القياسية وفي PostgreSQL اسم عمود أو جدول. ويبدأ التعليق بـ--، وتنتهي الجملة بـ;. والنتيجة نفسها جدول، والنتيجة التي لا تضم أي صف إجابةٌ صحيحة لا خطأ.
تُقيَّم العبارات منطقياً بترتيب يختلف عن ترتيب كتابتها: FROM، ثم WHERE، ثم قائمة SELECT، ثم ORDER BY، ثم LIMIT. لذا فالاسم المستعار (Alias) الذي تنشئه بـAS في قائمة SELECT يعمل في ORDER BY لا في WHERE، لأن WHERE تُقيَّم قبل أن يوجد الاسم. ويرفضه PostgreSQL وMySQL هناك، أما SQLite فيقبله، لذا قد يفشل على محرك المشروع استعلامٌ نجح أثناء التدرّب على SQLite.
الطلبات المدفوعة للعميل 207، من الأحدث إلى الأقدم
-- Paid orders of customer 207, newest first الجزء 3:SELECT order_id, total_amount AS total, created_at الجزء 1:FROM orders الجزء 2:WHERE customer_id = 207 AND status = 'PAID' الجزء 4:ORDER BY created_at DESC, order_id DESC الجزء 5:LIMIT 5;
- 1–2أي صفوف
- 3أي أعمدة
- 4–5بأي ترتيب، وكم صفاً
- 1
FROM: الجدولتُقيَّم أولاً: تسمّي الجدول الذي تعمل كل العبارات اللاحقة على صفوفه.الثانية كتابةً - 2
WHERE: الصفوفتُبقي الصفوف التي يكون الشرط كله فيها صحيحاً فقط؛ وتُسقط الصفوف التي يكون فيها خاطئاً أو مجهولاً بسببNULL.الثالثة كتابةً - 3قائمة
SELECT: الأعمدةتختار الأعمدة وأسماءها. والاسم المستعارtotalيوجد من هنا فصاعداً، لذا يمكن أن تستخدمهORDER BY؛ أما SQL القياسية فلا تسمح به فيWHERE.الأولى كتابةً - 4
ORDER BY: الترتيبالأحدث أولاً؛ وorder_id DESCتفصل بين الطلبات التي أُنشئت في اللحظة نفسها. ومن دون هذه العبارة لا يكون الترتيب محدداً.الرابعة كتابةً - 5
LIMIT: كم صفاًتُبقي أول 5 صفوف من النتيجة المرتبة. ويكتب SQL ServerTOP (5)، والصيغة القياسية هيFETCH FIRST 5 ROWS ONLY.الخامسة كتابةً
تبيّن الأرقام الترتيب المنطقي الذي يحدد النتيجة، لا الترتيب الذي يقرأ به المحرك البيانات فعلياً.
النسخة النصية
-- Paid orders of customer 207, newest first
SELECT order_id, total_amount AS total, created_at
FROM orders
WHERE customer_id = 207
AND status = 'PAID'
ORDER BY created_at DESC, order_id DESC
LIMIT 5;اختيار الأعمدة بـSELECT والصفوف بـWHERE
سمِّ الأعمدة التي تفحصها، كما في SELECT order_id, status، واترك SELECT * للنظرة السريعة، حتى تكون الفحوص المحفوظة مقروءة في تقرير الخطأ. وتعيد AS تسمية عمود في النتيجة، وSELECT DISTINCT status FROM orders تعرض كل قيمة مرة واحدة: وهي طريقة سريعة لاكتشاف قيم لم يتوقعها أحد، مثل 'CANCELED' بجوار 'CANCELLED'.
تُقيَّم WHERE لكل صف، ولا تعود إلا الصفوف التي يكون الشرط فيها صحيحاً. وتُبنى من هذه الأجزاء:
- المقارنات:
=و<>(وتقبل معظم المحركات!=أيضاً) و<و<=و>و>=. - المنطق: تُقيَّم
ANDقبلOR، لذا تعيدstatus = 'PAID' OR status = 'SHIPPED' AND total_amount > 500كل الطلبات المدفوعة ومعها الطلبات المشحونة التي تزيد على 500. استخدم الأقواس لتوضّح ما تقصده. - القوائم والنطاقات:
status IN ('PAID', 'SHIPPED')؛ وBETWEEN 100 AND 300تشمل الطرفين. وللطوابع الزمنية استخدم نطاقاً نصف مفتوح يشمل بدايته ولا يشمل نهايته:created_at >= '2026-09-01' AND created_at < '2026-10-01'. - الأنماط:
LIKE '%@example.com'، حيث يطابق%أي سلسلة من الأحرف ولو كانت فارغة، ويطابق_حرفاً واحداً بالضبط. ويعتمد تمييز حالة الأحرف على المحرك وعلى قواعد المقارنة (Collation) للعمود، أي قواعد مقارنة النصوص وترتيبها: يميّزها PostgreSQL في مقارنة النصوص، ولا تميّزها قواعد المقارنة الافتراضية في MySQL، أما في SQLite فيتجاهل المعاملLIKEحالة الأحرف اللاتينية الأساسية (ASCII) بينما يميّزها المعامل=.
ترتيب النتيجة بـORDER BY
من دون ORDER BY لا يكون ترتيب الصفوف محدداً: فقد يتغير حين تكبر البيانات أو يُضاف فهرس (Index)، أي بنية تستخدمها قاعدة البيانات لإيجاد الصفوف بسرعة أكبر، أو يُرقّى المحرك. لذا يجب أن يرتّب صراحةً كل فحص يقرأ «الصف الأول» أو يقارن قائمة بما على الشاشة.
لا تستخدم ORDER BY created_at DESC, order_id DESC العمود الثاني إلا عند تساوي الأول، والترتيب التصاعدي ASC هو الافتراضي. واختم بعمود فريد مثل المفتاح الأساسي (Primary Key): فمن دون هذا الفاصل عند التعادل (Tie-breaker) قد تتبادل الصفوف المتساوية مواقعها بين تشغيل وآخر، ومع تقسيم الصفحات قد يظهر صف في صفحتين أو لا يظهر في أي منها.
- تُبقي
LIMIT 10أول عشرة صفوف بعد الترتيب. ويكتب SQL ServerTOP (10)، أما الصيغة القياسيةFETCH FIRST 10 ROWS ONLYفيقبلها PostgreSQL ولا يقبلها MySQL ولا SQLite. - يُرتَّب النص حسب قواعد المقارنة للعمود، والأرقام المخزّنة نصاً تُرتَّب كنص:
'10'تأتي قبل'9'. وقائمة أسعار مرتبة هكذا على الشاشة سبب لفحص نوع بيانات العمود.
القيمة NULL: القيمة الغائبة
تدل NULL على قيمة مفقودة أو مجهولة؛ فهي ليست صفراً، ولا نصاً فارغاً ''، ولا النص 'NULL'. والمقارنة مع NULL ليست صحيحة ولا خاطئة بل مجهولة (Unknown)، وWHERE لا تُبقي إلا الصفوف التي شرطها صحيح، فتختفي من النتائج دون أي تحذير الصفوف التي تكون فيها قيمة العمود المقارَن NULL.
وتنتقل القيمة المجهولة عبر AND وOR أيضاً. فـAND لا تكون صحيحة إلا إذا صحّ طرفاها، لذا فإن true AND unknown مجهولة ويسقط الصف؛ أما OR فيكفيها طرف صحيح واحد، لذا فإن unknown OR true صحيحة ويبقى الصف.
WHERE email = NULLلا تعيد أي صف أبداً. استخدمIS NULLوIS NOT NULL.- تُسقط
WHERE city <> 'Riyadh'أيضاً الصفوف التي مدينتهاNULL، وكذلك تفعلNOT (city = 'Riyadh'). فإن كانت هذه الصفوف جزءاً من النتيجة المطلوبة فأضفOR city IS NULL. - العمليات الحسابية مع
NULLتُنتجNULL؛ وتعيدCOALESCE(delivery_fee, 0)أول وسيط فيها ليسNULL. - النص الفارغ ليس
NULLفي PostgreSQL وMySQL وSQLite (أما Oracle Database فيخزّن''على أنهNULL)، لذا يبحث فحص الحقل الإلزامي عن الحالتين. - في الترتيب التصاعدي يضع PostgreSQL قيم
NULLفي الآخر، ويضعها MySQL وSQLite في الأول؛ وفي الترتيب التنازلي يفعل كل منها العكس. ويقبل PostgreSQL وSQLiteNULLS FIRSTأوNULLS LASTلتحديد موضعها صراحةً.
-- Customers without a usable email address
SELECT customer_id, full_name
FROM customers
WHERE email IS NULL OR email = '';
-- Show a default where the value is missing
SELECT order_id, COALESCE(coupon_code, 'none') AS coupon
FROM orders
WHERE customer_id = 207;مثال تطبيقي: فحص صفحة «طلباتي»
المتطلب: «تعرض صفحة طلباتي طلبات العميل ما عدا الملغاة، من الأحدث إلى الأقدم؛ والطلبات التي وُضعت في الوقت نفسه تظهر حسب رقم الطلب، الأعلى أولاً؛ وتعرض الصفحة 10 طلبات على الأكثر». يكتب المختبِر أولاً الاستعلام الذي يُنتج القائمة المتوقعة من بيانات الاختبار، فيأتي مرجع الاختبار (Test Oracle)، أي مصدر النتيجة المتوقعة، من المتطلب لا من الشاشة.
للعميل 207 خمسة طلبات، والطلب 1003 يخص عميلاً آخر. وكل سطر في الاستعلام يفحص جزءاً من المتطلب:
customer_id = 207: إن ظهر الطلب 1003 فقد رأى عميل بيانات عميل آخر، وهذا عيب في الخصوصية.status <> 'CANCELLED': الطلب 1002 غير متوقع. والعمودstatusهناNOT NULL؛ ولو قبلNULLلأخفى هذا الشرط أيضاً الطلبات التي لا حالة لها.ORDER BY created_at DESC, order_id DESC: للطلبين 1004 و1005 قيمةcreated_atنفسها، فيضع الفاصل عند التعادل 1005 أولاً. والصفحة التي تعرض 1004 أولاً، أو تبدّل موقعيهما عند إعادة التحميل، تخالف المتطلب.LIMIT 10: حجم الصفحة حدّ. اختبر ببيانات فيها 10 طلبات غير ملغاة بالضبط، ثم 11 طلباً، كما في تحليل القيم الحدّية: يجب أن يظهر الطلب الحادي عشر في الصفحة الثانية، وألا يظهر أي طلب مرتين. وتتخطىLIMIT 10 OFFSET 10أول عشرة صفوف فتُعطي الصفحة الثانية المتوقعة.
-- Test data: orders
-- order_id customer_id status total_amount created_at
-- 1001 207 PAID 120.00 2026-09-03 10:15
-- 1002 207 CANCELLED 80.00 2026-09-05 18:40
-- 1003 311 PAID 455.50 2026-09-06 09:05
-- 1004 207 SHIPPED 455.50 2026-09-12 21:30
-- 1005 207 PAID 60.00 2026-09-12 21:30
-- 1006 207 PAID 35.25 2026-09-20 08:00
SELECT order_id, status, total_amount, created_at
FROM orders
WHERE customer_id = 207
AND status <> 'CANCELLED'
ORDER BY created_at DESC, order_id DESC
LIMIT 10;
-- Expected, top to bottom: 1006, 1005, 1004, 1001أخطاء شائعة
- كتابة
= NULLأو<> NULL: لا يعيد الاستعلام أي صف، فينجح الفحص الذي يتوقع نتيجة خالية لسبب خاطئ. - نسيان أن
<>وNOTتُسقطان أيضاً الصفوف التي قيمة العمود فيهاNULL. - مقارنة النتيجة بالشاشة دون
ORDER BYأو دون فاصل عند التعادل، ثم الإبلاغ عن عيب ترتيب هو في الحقيقة ترتيب غير محدد. - الجمع بين
ANDوORدون أقواس، فيعيد المرشّح صفوفاً يستبعدها المتطلب. - استخدام
BETWEEN '2026-09-01' AND '2026-09-30'على عمود طابع زمني: فهو يُسقط كل صف بعد الساعة 00:00 من يوم 30 سبتمبر. - الثقة بنتيجة فارغة دون استعلام ضابط (Control): فالقيمة المكتوبة خطأً، مثل
'CANCELED'بدلاً من'CANCELLED'، لا تعيد أي صف أيضاً. استعلم أولاً عن صف تعرف أنه موجود.
من منظور ضمان الجودة
يستخدم المختبِرون SELECT للتحضير للاختبارات (اختيار البيانات وحساب النتائج المتوقعة)، ولتأكيد ما خزّنه إجراء ما، كما في درس عمليات CRUD، وللتمييز بين مشكلات البيانات ومشكلات العرض عند التحقيق في إخفاق. وكل ذلك قراءة: فاستخدم حساباً للقراءة فقط في بيئة الاختبار، ولا تتصفح أبداً البيانات الشخصية في بيئة الإنتاج لمجرد التسهيل.
تعامل مع الاستعلام على أنه دليل اختبار: ضع الجملة والبيئة والصفوف التي أعادتها في حالة الاختبار أو تقرير الخطأ، ليعيد المطور تشغيلها. واسأل المطورين عن الأعمدة التي تقبل NULL، واسأل مالك المنتج عما يجب أن يراه المستخدم حين تغيب القيمة؛ فتلك الإجابة هي النتيجة المتوقعة لحالات NULL لديك.
تمرين عملي
راجع استعلاماً لحملة بريد إلكتروني
يريد فريق التسويق مراسلة كل عميل خارج الرياض لديه بريد إلكتروني، بدءاً بأصحاب أعلى نقاط الولاء. باستخدام الصفوف الستة، توقّع ما يعيده استعلام المطور أدناه، واعثر على عيوبه، واكتب استعلاماً مصححاً مع نتيجته.
بعض العيوب في الحقيقة أسئلة عن المتطلب: اكتبها لمالك المنتج.
-- Test data: customers
-- customer_id full_name city email loyalty_points
-- 1 Layla Hassan Riyadh layla@example.com 120
-- 2 Omar Saleh Jeddah NULL 300
-- 3 Priya Nair Dammam '' NULL
-- 4 Khalid Ali NULL khalid@example.com 450
-- 5 Sara Ahmed Jeddah sara@example.com 75
-- 6 Rahul Mehta Dammam rahul@example.com NULL
-- The developer's query
SELECT customer_id, full_name, email
FROM customers
WHERE city <> 'Riyadh' AND email <> NULL
ORDER BY loyalty_points;- قيّم كل جزء من شرط WHERE لكل صف: صحيح أو خاطئ أو مجهول.
- اكتب الصفوف التي يعيدها الاستعلام.
- اكتب العيوب وأسئلة المتطلب التي تثيرها.
- اكتب استعلاماً مصححاً وصفوفه بترتيبها.
إظهار الإجابة النموذجية
الشرط city <> 'Riyadh' خاطئ للصف 1، وصحيح للصفوف 2 و3 و5 و6، ومجهول للصف 4؛ أما email <> NULL فمجهول لكل الصفوف.
ولأن AND لا تكون صحيحة إلا إذا صحّ طرفاها، لا يستوفي أيُّ صف الشرطَ: فلا يعيد الاستعلام أي صف، ولا شيء ينبّهك إلى ذلك.
العيوب: يجب أن يصبح email <> NULL هو email IS NOT NULL؛ والنص الفارغ لدى العميل 3 ليس NULL، فاستبعد email = '' أيضاً؛ ويجب أن يكون الترتيب DESC؛ ونقاط العميل 6 التي قيمتها NULL تأتي أولاً أو أخيراً حسب المحرك؛ ولا يوجد فاصل عند تساوي النقاط.
أسئلة لمالك المنتج: هل يُعدّ العميل الذي لا مدينة له (العميل 4) خارج الرياض؟ وهل تُحسب نقاط الولاء المفقودة صفراً؟
إن كانت الإجابتان «نعم»، فإن SELECT customer_id, full_name, email FROM customers WHERE (city <> 'Riyadh' OR city IS NULL) AND email IS NOT NULL AND email <> '' ORDER BY COALESCE(loyalty_points, 0) DESC, customer_id; يعيد العملاء 4 (450 نقطة) ثم 5 (75) ثم 6 (بلا نقاط). وإن استُبعد العملاء الذين لا مدينة لهم، فالنتيجة 5 ثم 6.
أسئلة شائعة
هل أحتاج إلى تعلّم SQL الخاصة بقاعدة بيانات معينة؟
ابدأ بالمحرك الذي يستخدمه مشروعك. فـSELECT وWHERE وORDER BY وIS NULL تُكتب بالطريقة نفسها في PostgreSQL وMySQL وSQLite وSQL Server وOracle Database، وأول الفروق التي ستصادفها: تحديد عدد الصفوف، ودوال التاريخ، وتمييز حالة الأحرف في مقارنة النصوص، وموضع NULL في الترتيب. راجعها في توثيق محركك حين ينتقل استعلام من مشروع إلى آخر.
هل تشغيل SELECT على قاعدة بيانات مشتركة آمن؟
لا تغيّر SELECT البيانات، لكنها ليست بلا كلفة: فاستعلام على جدول كبير دون شرط WHERE انتقائي قد يبطئ قاعدة البيانات على الجميع، وقد تحتوي الصفوف على بيانات شخصية. استخدم حساباً للقراءة فقط، ورشّح بالمفتاح الأساسي أو بنطاق تواريخ، وأضف LIMIT أثناء الاستكشاف، والتزم بقواعد فريقك للوصول إلى الإنتاج.
كيف أتدرّب دون خادم قاعدة بيانات؟
تحفظ SQLite قاعدة البيانات كاملة في ملف واحد ولا تحتاج إلى خادم، بل تكفي واجهة سطر الأوامر الخاصة بها sqlite3، لذا فهي خيار شائع للتدريب. ولّد جدولاً وصفوفاً نموذجية في صورة سكربت SQL، وحمّلها، ثم أعد تشغيل استعلامات هذا الدرس متوقعاً نتيجة كل منها قبل تشغيله. وتذكّر المواضع التي تتساهل فيها SQLite أكثر من معظم المحركات، مثل قبول الاسم المستعار في WHERE.
المراجع
- ISO/IEC 9075-1:2023 — Information technology — Database languages SQL — Part 1: Framework (SQL/Framework)الجزء الإطاري من معيار SQL الدولي، الذي تنفّذه لهجات PostgreSQL وMySQL وSQLite وغيرها بدرجات متفاوتة.
- PostgreSQL Documentation: SQL Syntaxالبنية المعجمية لـ SQL في PostgreSQL: المعرّفات والاقتباس والثوابت النصية والتهريب.
- SQL As Understood By SQLiteقواعد SQL التي يقبلها SQLite، عبارة تلو الأخرى، مع مخططات نحوية.
تدرّب بأدوات متقن
فحص جمل SQL بحثاً عن مشكلات الصياغة والأقواس غير المتوازنة والسلاسل النصية غير المغلقة والأنماط الخطرة.
تنسيق استعلامات SQL بحالة أحرف متسقة للكلمات المفتاحية ومسافات بادئة وفواصل أسطر (MySQL وPostgreSQL وSQL Server وOracle وSQLite).
ولّد سكربتات تهيئة SQL (جملة CREATE TABLE وجمل INSERT) بصفوف اختبار واقعية لأي لهجة.
تحويل قائمة قيم (واحدة في كل سطر أو مفصولة بفواصل) إلى عبارة SQL IN (...) مقتبسة.
للتعمّق أكثر
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.