EduFlow
SQL للمحلّلين
⁦0%⁩

الوحدة 1 · الاستعلام بهدف واضح

التصفية والترتيب بهدف

16 دقراءة 4 دتمرين SQL

الدرس في لمحة

اكتب عبارات WHERE تقول ما تقصده بالضبط باستخدام AND وOR وIN وBETWEEN وLIKE، ورتّب النتائج ترتيبًا حتميًا بـ ORDER BY متعدّد المفاتيح وLIMIT وOFFSET.

  1. 1AND قبل OR
  2. 2بقية أدوات التصفية
  3. 3قول «لا» بشكل صحيح
  4. 4ترتيب يمكنك الاعتماد عليه

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

تكتب هدى صالح، نائبة الرئيس للتسويق: «هل يمكنك أن تستخرج الطلبات التي استخدمت SPRING15 أو WELCOME10 على قنواتنا الخاصة، الويب أو التطبيق؟ أريد أن أعرف هل يجذب عرض الترحيب الناس إلى التطبيق». في جملتها «أو» مرتين و«و» مرة واحدة. هذا هو شكل الطلبات الذي ينتج أرقامًا خاطئة دون أي إنذار، لذا يستحق WHERE أن تضبطه تمامًا.

AND قبل OR

هذه هي المسودة الأولى التي يكتبها معظم الناس، بترجمة كلماتها من اليسار إلى اليمين:

SQL
SELECT COUNT(*) AS orders
FROM orders
WHERE channel = 'web' OR channel = 'mobile_app'
  AND coupon_code IN ('SPRING15', 'WELCOME10');

1,191 طلبًا. هذا أكثر من نصف كل الطلبات خلال سنتين، من أجل كوبونين محدودين. الرقم نفسه هو جرس الإنذار: AND يرتبط أقوى من OR، فقرأ SQL الشرط هكذا: «كل طلبات الويب، إضافةً إلى طلبات التطبيق التي استخدمت كوبونًا». كل طلب ويب موجود في النتيجة، بكوبون أو بدونه.

الأقواس تقول ما قصدته هدى:

SQL
SELECT COUNT(*) AS orders
FROM orders
WHERE (channel = 'web' OR channel = 'mobile_app')
  AND coupon_code IN ('SPRING15', 'WELCOME10');

110 طلبات. والأفضل أن يستخدم اختبار القناة IN أيضًا، فيختفي OR تمامًا: channel IN ('web', 'mobile_app'). يفحص IN عمودًا واحدًا مقابل قائمة، والقائمة لا يمكن أن تُجمَّع بشكل خاطئ.

بقية أدوات التصفية

بضعة معاملات تغطي في الغالب كل شرط ستكتبه:

المعامل مثال ملاحظة
IN (...) status IN ('cancelled', 'returned') NOT IN للاستبعاد؛ احذر من NULL (الدرس التالي)
BETWEEN a AND b shipping_fee BETWEEN 5 AND 10 يشمل الطرفين
LIKE name LIKE '%mat%' % أي سلسلة من الأحرف، و_ حرف واحد بالضبط
<> / != status <> 'cancelled' كلاهما يعني «لا يساوي»

لمقارنة النصوص في SQLite خصوصية. = حسّاس لحالة الأحرف، فـ 'a' = 'A' قيمته false، بينما يتجاهل LIKE حالة الأحرف الإنجليزية، فـ 'a' LIKE 'A' قيمته true. لهذا يجد name LIKE '%mat%' كلًّا من “Desk Mat XL” و"Cork Yoga Mat". أما LIKE في PostgreSQL فحسّاس لحالة الأحرف، وهو فرق ستقابله مجددًا في درس اللهجات.

SQL
SELECT product_id, name, unit_price
FROM products
WHERE name LIKE '%mat%'
  AND unit_price BETWEEN 20 AND 80;

قول «لا» بشكل صحيح

النفي هو المكان الذي يتعثّر فيه حتى الحذِرون. لنفترض أن هدى تريد الآن الطلبات التي ليست ملغاة ولا مرتجعة. يمكنك كتابتها بطريقتين متكافئتين:

SQL
SELECT
  (SELECT COUNT(*) FROM orders
   WHERE NOT (status = 'cancelled' OR status = 'returned')) AS not_either,
  (SELECT COUNT(*) FROM orders
   WHERE status <> 'cancelled' AND status <> 'returned') AS both_not;

كلاهما يعيد 1,864. القاعدة وراء ذلك هي قانون De Morgan: إدخال NOT إلى داخل القوسين يقلب كل OR إلى AND (وكل AND إلى OR). الخطأ الشائع أن تقلب المقارنات وتنسى قلب أداة الربط: status <> 'cancelled' OR status <> 'returned' قيمته TRUE لكل صف، لأن أي حالة تختلف عن واحدة من الاثنتين على الأقل. حين تكتب شرطًا منفيًا، فإن NOT IN ('cancelled', 'returned') هو أوضح صيغة على الإطلاق.

وهناك عادة أخرى تؤتي ثمارها هنا. قبل أن تثق بشرط فيه عدة أجزاء، عُدّ الصفوف لكل جزء وحده. لشرط هدى ثلاثة أجزاء: الويب أو التطبيق يُبقي 1,848 صفًا، والكوبونان يُبقيان 127، و«ليست ملغاة ولا مرتجعة» يُبقي 1,864. ولا يمكن لربطها بـ AND أن يُبقي أكثر من أصغرها، أي 127؛ فإن حدث ذلك فالمنطق خاطئ. فحص كهذا يستغرق عشر ثوانٍ ويلتقط معظم الأخطاء المنطقية قبل أن يراها أحد غيرك.

التواريخ في Cartwheel نصوص بصيغة ISO (2025-11-28 19:55:30)، وترتيبها الأبجدي يطابق ترتيبها الزمني. لذا يعمل order_date >= '2025-11-01' كشرط على التاريخ. تتناول الدروس القادمة الفخاخ، بدءًا بما يحدث عند نهاية اليوم.

ترتيب يمكنك الاعتماد عليه

يقبل ORDER BY عدة مفاتيح، لكلٍّ منها اتجاهه. المفتاح الثاني لا يؤثّر إلا بين الصفوف المتساوية في الأول:

SQL
SELECT order_id, customer_id, order_date, coupon_code
FROM orders
WHERE channel = 'marketplace'
  AND coupon_code IS NOT NULL
ORDER BY order_date DESC, order_id
LIMIT 5;

لماذا نضيف order_id والتواريخ نادرًا ما تتساوى؟ لأن «نادرًا» ليست «أبدًا». حين يتساوى صفّان في كل مفاتيح الترتيب، لا يَعِد SQL بأي ترتيب بينهما، وقد يختلف من تشغيل إلى آخر. إنهاء الـ ORDER BY بعمود فريد، وهو عادةً المفتاح الأساسي، يجعل الترتيب حتميًا تمامًا.

وهذا أهم ما يكون في تقسيم الصفحات. LIMIT 20 OFFSET 40 يعني «تخطَّ 40 صفًا وأعِد الـ 20 التالية»، أي الصفحة الثالثة من 20. إذا لم يكن الترتيب حتميًا، فقد يظهر صف في الصفحة الثانية ثم مرة أخرى في الثالثة.

أُجيب عن سؤال هدى عن الكوبونات: 110 طلبات. وسؤالها التالي في التمرين أدناه. بعد ذلك تأتي القيمة التي تُربك كل هذه المعاملات بطرق مفاجئة: NULL.

أهم النقاط

  • AND يرتبط أقوى من OR، لذا ضع كل مجموعة OR بين قوسين حين تجاور AND.
  • IN يغني عن سلسلة من اختبارات المساواة المربوطة بـ OR على عمود واحد، وهو أسهل قراءة وأصعب خطأً.
  • في SQLite تكون المقارنة بـ = على النص حسّاسة لحالة الأحرف، بينما يتجاهل LIKE حالة الأحرف الإنجليزية (ASCII).
  • أضف عمودًا فريدًا كآخر مفتاح في ORDER BY كي تُرتَّب الصفوف المتساوية بالطريقة نفسها في كل مرة.
  • LIMIT مع OFFSET يقسّم النتيجة إلى صفحات، لكن الصفحات لا تثبت إلا مع ORDER BY حتمي.

قراءات إضافية

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

ما الصفوف التي يُبقيها هذا الشرط؟

SQL
WHERE status = 'delivered' OR status = 'shipped' AND channel = 'web'
السؤال 2 من 4

تحتاج إلى طلبات الويب من العميل 42، وقد تكون قيمة القناة مخزّنة بالشكل ‘Web’ أو ‘web’. أي اختبار يطابق الاثنين؟

السؤال 3 من 4

لوحة معلومات تعرض الطلبات 20 في كل صفحة باستخدام ORDER BY order_date LIMIT 20 OFFSET 40. يشتكي المستخدمون من ظهور طلب في صفحتين. ما الإصلاح الأرجح؟

السؤال 4 من 4

BETWEEN 10 AND 20 على العمود shipping_fee: أي قيمة لا تدخل في النتيجة؟

تدريب

تمرين SQL

هل يتسرّب BLACKFRIDAY25؟

سؤال هدى التالي: «كان BLACKFRIDAY25 مخصّصًا لشهر نوفمبر فقط. أرني كل طلب في 2025 استخدمه خارج نوفمبر، على الويب أو التطبيق، ولم يُلغَ».

أعِد order_id وorder_date وchannel، مرتّبة حسب order_date (الأقدم أولًا). الترتيب مهم.

الكود المبدئي يعمل، لكنه يعيد مئات الصفوف. أصلح منطقه.

الجداول
customers
(customer_id, first_name, last_name, email, country, city, signup_date, segment, referred_by)
orders
(order_id, customer_id, order_date, status, channel, coupon_code, shipping_fee)
order_items
(order_item_id, order_id, product_id, quantity, unit_price, discount)
products
(product_id, name, category_id, unit_price, unit_cost, launched_on, is_active)
categories
(category_id, name, parent_id)
returns
(return_id, order_item_id, return_date, reason, refund_amount)
employees
(employee_id, first_name, last_name, title, department, manager_id, hire_date, salary)
support_tickets
(ticket_id, customer_id, order_id, opened_at, closed_at, priority, channel, satisfaction, agent_id)
web_sessions
(session_id, customer_id, started_at, source, device, pages_viewed, added_to_cart, purchased)
-- BLACKFRIDAY25 used outside November 2025, web or app, not cancelled
SELECT order_id, order_date, channel
FROM orders
WHERE coupon_code = 'BLACKFRIDAY25'
  AND channel = 'web' OR channel = 'mobile_app'
  AND status <> 'cancelled'
  AND order_date >= '2025-01-01'
  AND order_date < '2025-11-01' OR order_date >= '2025-12-01'
ORDER BY order_date;جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.