الكويري بتتنفذ بأنهي ترتيب؟
الكويري بتتكتب بترتيب وبتتنفذ بترتيب تاني خالص. لو فهمت ترتيب التنفيذ، هتفهم لوحدك ليه أخطاء كتير بتحصل: الـ alias اللي مش موجود، و count اللي مش راضية تشتغل في WHERE، و NULL اللي بيختفي.
- تفرق بين SQL كلغة "قول عايز إيه" وبين الكود اللي بيمشي صف صف
- ترتّب أجزاء الكويري بترتيب تنفيذها المنطقي، وتستخدمه تتوقع الأخطاء قبل ما تحصل
- تعرف ليه الـ alias شغال في
ORDER BYومش شغال فيWHERE، وليه التجميع مكانهHAVING - تحط الـ 5 أسئلة اللي بتحل بيهم أي مسألة SQL
قول عايز إيه، مش تعمله إزاي
لو عايز أسماء المنتجات اللي سعرها فوق 50 ألف، في PHP هتكتب حاجة زي كده:
$result = [];
foreach ($products as $p) { // صف صف
if ($p['price'] > 50000) {
$result[] = $p['name'];
}
}انت هنا بتقول للكمبيوتر يعمل إيه خطوة خطوة. في SQL بتوصف النتيجة اللي عايزها وخلاص:
SELECT name FROM products WHERE price > 50000;
مين اللي قرر يمشي على الصفوف إزاي؟ يستخدم Index ولا يقرا الجدول كله؟ ده الـ Optimizer جوه الداتابيز. ودي فكرة اسمها Declarative.
الكويري بتتنفذ بترتيب غير اللي اتكتبت بيه
بتكتب SELECT الأول، بس الداتابيز مش بتبدأ بيها. ده الترتيب المنطقي:
| بتتكتب | بتتنفذ | بتعمل إيه |
|---|---|---|
SELECT |
5 | تختار الأعمدة وتحسبها |
FROM |
1 | تجيب الجدول (أو الجداول وتربطها) |
WHERE |
2 | تشيل الصفوف اللي مش مطابقة |
GROUP BY |
3 | تجمّع الصفوف في مجموعات |
HAVING |
4 | تشيل المجموعات اللي مش مطابقة |
ORDER BY |
6 | ترتّب النتيجة |
LIMIT |
7 | تاخد أول كام صف |
الكلام ده سهل يتحفظ وسهل يتنسي. فبدل ما تحفظه، اتفرج عليه: المحاكي ده بيشغّل كل خطوة على PostgreSQL الحقيقي ويوريك الصفوف بعد كل خطوة. جرّب الأمثلة اللي فوق، أو عدّل أي جزء ودوس Enter.
عدّل أي جزء ودوس Enter. سيبه فاضي لو مش عايزه.
جرّب “الـ NULL بيختفي”: الشرط price < 30000 OR price >= 30000 المفروض يغطي كل الأرقام، بس الـ WHERE رجّعت 17 صف من 18. المنتج اللي سعره NULL الشرط بتاعه طلع مش معروف (علامة ؟)، والـ WHERE بتعدّي اللي TRUE بس. وجرّب “أغلى 3”: أول صف طلع المنتج اللي ملوش سعر! في PostgreSQL، الـ NULL بيتحسب أكبر من أي قيمة في الترتيب، فـ DESC بيحطه في الأول.
دوس على أجزاء الكويري بالترتيب اللي بتتنفذ بيه فعلاً:
ليه الترتيب ده بيفرق؟ 4 نتايج مباشرة
1. الـ alias مش موجود في WHERE
الـ AS with_vat بيتعمل في خطوة 5 (SELECT). والـ WHERE بتشتغل في خطوة 2، يعني قبلها. فـ with_vat لسه ما اتولدش:
SELECT name, price * 1.14 AS with_vat FROM products WHERE with_vat > 60000;
الحل: كرر الحسبة في الـ WHERE (WHERE price * 1.14 > 60000)، أو استخدم Subquery أو CTE (المرحلة 5).
2. بس الـ alias موجود في ORDER BY
الـ ORDER BY خطوة 6، بعد الـ SELECT. فالـ alias اتعمل خلاص:
SELECT name, price * 1.14 AS with_vat FROM products ORDER BY with_vat DESC;
3. التجميع مينفعش في WHERE
count(*) بتعدّ صفوف المجموعة. والمجموعات بتتعمل في خطوة 3، والـ WHERE في خطوة 2. يعني وقت الـ WHERE لسه مفيش مجموعات تتعد:
SELECT brand_id, count(*) FROM products WHERE count(*) > 3 GROUP BY brand_id;
ودي بالظبط وظيفة HAVING: WHERE للمجموعات، وبتشتغل في خطوة 4 بعد ما المجموعات اتعملت. غيّر WHERE count(*) > 3 لـ HAVING count(*) > 3 وحطها بعد الـ GROUP BY.
4. LIMIT من غير ORDER BY = “أي كام صف”
الـ LIMIT آخر خطوة، بتقص النتيجة اللي جاتلها. لو مفيش ORDER BY، النتيجة ملهاش ترتيب (فاكر الدرس الأول؟)، فـ “أول 3” ملهاش معنى ثابت.
الكويري دي هتشتغل؟ SELECT brand_id, count(*) AS n FROM products GROUP BY brand_id ORDER BY n DESC;
آه. ORDER BY خطوة 6، والـ alias n اتعمل في خطوة 5. وده شغال في كل الداتابيز.
عايز الطلبات اللي اتعملت في 2024 بس، وبعدين تعدّهم لكل حالة. الشرط على السنة مكانه فين؟
WHERE. الشرط على كل صف لوحده (تاريخ الطلب ده)، فبيتنفذ قبل التجميع ويقلل الصفوف اللي هتتجمع. HAVING للشروط على المجموعة (زي “الحالات اللي فيها أكتر من 5 طلبات”). ولو حطيت شرط عادي في HAVING، غالباً هيشتغل بس هيبقى أبطأ وأوحش في القراية.
الـ Grain بعد كل خطوة
ارجع للمحاكي وجرّب “عدد المنتجات لكل ماركة”، وبص على خطوة GROUP BY: كان عندك 18 صف (كل صف = منتج)، وبقى عندك 6 (كل صف = ماركة). الـ Grain اتغيّر.
ومن اللحظة دي، أي عمود هتطلبه في الـ SELECT لازم يبقى ليه قيمة واحدة للماركة كلها: يا إما عمود من أعمدة الـ GROUP BY (brand_id)، يا إما تجميع (count(*)، max(price)). طب name؟ الماركة فيها كذا منتج، فأنهي اسم يطلع؟ علشان كده PostgreSQL بيرفض:
SELECT brand_id, name, count(*) FROM products GROUP BY brand_id;
SELECT brand_id, count(*) FROM products GROUP BY brand_id — كل صف في النتيجة بيمثّل إيه؟
ماركة. الـ GROUP BY هو اللي بيحدد الـ Grain الجديد: صف لكل قيمة مختلفة من brand_id.
خطة حل أي مسألة
كل اللي فات بيتلخص في 5 أسئلة. اسألهم لنفسك قبل ما تكتب أي حرف، في أي تمرين هنا، أو في أي مسألة على LeetCode، أو في الإنترفيو:
- النتيجة صف لكل إيه؟ (Grain) عميل؟ طلب؟ ماركة في شهر؟
- محتاج أنهي جداول؟ ومربوطة إزاي؟ والربط ده هيزوّد الصفوف؟
- الفلتر قبل التجميع ولا بعده؟ شرط على الصف =
WHERE. على المجموعة =HAVING. - كل صف محتاج يشوف صفوف تانية؟ (ترتيب، اللي قبله، تراكمي) يبقى Window Function. دي في المرحلة 5.
- الحالات الطرفية؟ NULL، تعادل، تكرار، نتيجة فاضية.
اتمرّن
التمرين ده أول واحد عليه Test مستخبي: حلك هيتجرّب على الداتا اللي قدامك، وعلى نسخة تانية فيها منتج زيادة ملوش سعر.
تمرين تطبيقي
متوسط+1 اختبار مستخبي✓ اتحلاعرض اسم وسعر كل المنتجات من الأغلى للأرخص، بس المنتجات اللي ملهاش سعر تيجي في الآخر مش في الأول. الترتيب هنا جزء من الإجابة.
SELECT name, price FROM products ORDER BY price DESC NULLS LAST;
تمرين تطبيقي
سهل✓ اتحلاعرض اسم كل منتج، وسعره بعد ضريبة 14% في عمود اسمه with_vat، ورتّب من الأرخص للأغلى بالسعر بعد الضريبة. استخدم الـ alias في الترتيب.
SELECT name, price * 1.14 AS with_vat FROM products ORDER BY with_vat;