المفاتيح والعلاقات بين الجداول
إزاي الداتابيز تعرف إن الصف ده هو الصف ده، وإزاي الجداول بتشاور على بعض. ده الأساس اللي الـ JOIN كله مبني عليه، واللي من غيره هتكتب JOINs بتطلّع أرقام غلط.
- تفرق بين Primary Key و Foreign Key، وتعرف كل واحد بيمنع إيه
- تحدد نوع العلاقة بين أي جدولين: واحد لواحد، واحد لكتير، كتير لكتير
- تفهم ليه علاقة كتير لكتير محتاجة جدول تالت في النص
- تتوقع عدد الصفوف لما تمشي من جدول للتاني (الـ Grain)
الـ Primary Key: هوية الصف
عندك عميلين اسمهم Ahmed Hassan (روح جدول customers وشوف الصف 1 والصف 13). إزاي تفرق بينهم؟ مش بالاسم أكيد. كل جدول محتاج عمود (أو أكتر) قيمته مختلفة في كل صف، يبقى هو هوية الصف. ده اسمه Primary Key.
جرّب تضيف ماركة بنفس الـ id بتاع Dell:
INSERT INTO brands VALUES (1, 'Samsung', 'South Korea');
ودلوقتي بـ id جديد، وبعدها اعرض الجدول في نفس التشغيلة:
INSERT INTO brands VALUES (7, 'Samsung', 'South Korea'); SELECT * FROM brands;
Samsung ظهرت. وشغّل SELECT * FROM brands; لوحدها بعدها، هتلاقيها اختفت: كل تشغيل بيترجع ROLLBACK في الآخر، علشان الداتا تفضل زي ما هي للكل.
طبيعي ولا متركّب؟
| النوع | يعني إيه | مثال |
|---|---|---|
| Surrogate Key (مصطنع) | رقم ملوش معنى غير إنه هوية | id في كل جداولنا |
| Natural Key (طبيعي) | قيمة ليها معنى في الحقيقة وفريدة | الرقم القومي، الإيميل |
| Composite Key (مركّب) | أكتر من عمود مع بعض هما الهوية | (order_id, product_id) في order_items |
الـ Foreign Key: الجداول بتشاور على بعض
الطلب محتاج يعرف هو بتاع أنهي عميل. بدل ما نكرر اسم العميل وإيميله ومدينته في كل طلب، بنخزّن رقمه بس: orders.customer_id. والعمود ده بيشاور على customers.id.
جرّب طلب لعميل رقم 999، وهو مش موجود:
INSERT INTO orders VALUES (100, 999, '2024-11-01 10:00', 'pending', 'Cairo');
والعكس: امسح عميل عنده طلبات. الـ WHERE هنشرحها في المرحلة الجاية، دلوقتي اعرف إنها بتختار صف معين:
DELETE FROM customers WHERE id = 1;
الداتابيز رفضت، لأن لو العميل اتمسح، طلباته هتبقى بتشاور على حاجة مش موجودة (Orphan Rows). وده بالظبط اللي بيمنع داتا الشركة تتبوّظ مع الوقت.
أنواع العلاقات
تبص على أي جدولين وتسأل نفسك سؤالين: الصف الواحد هنا، يقابله كام صف هناك؟ والعكس؟
| العلاقة | المعنى | في داتابيز الكورس |
|---|---|---|
| واحد لكتير (1 : N) | العميل ليه طلبات كتير، والطلب لعميل واحد | customers ← orders |
| الماركة ليها منتجات كتير | brands ← products |
|
| كتير لكتير (N : M) | الطلب فيه منتجات كتير، والمنتج في طلبات كتير | orders ↔ products عن طريق order_items |
| مع نفسه (Self) | الموظف ليه مدير، والمدير موظف برضه | employees.manager_id ← employees.id |
| واحد لواحد (1 : 1) | كل صف يقابله صف واحد بس | مش موجود عندنا. مثال: users و user_settings |
تتبّع عميل بعينك
اختار عميل، وشوف طلباته، وبعدها المنتجات اللي في كل طلب. ركّز في عدد الصفوف فوق كل عمود: هو ده اللي بيتغيّر لما تربط الجداول.
جرّب Karim Mostafa: عميل موجود وملوش ولا طلب. وجرّب Ziad Nabil: عنده طلب ملغي، وطلب تاني فاضي مفيهوش منتجات. الحالتين دول هما بالظبط الفرق بين INNER JOIN و LEFT JOIN، اللي هنشرحه في مرحلة الربط بين الجداول.
ليه كتير لكتير محتاجة جدول تالت؟
الطلب ممكن يبقى فيه أكتر من منتج. طب نحط product_id في جدول orders؟ يبقى طلب فيه منتج واحد بس. نحط product_id_1 و product_id_2 و…؟ يبقى فيه حد أقصى، وكويري “مين اشترى المنتج ده” هتبقى كابوس.
الحل: جدول في النص، كل صف فيه = منتج جوه طلب:
SELECT * FROM order_items;
وكمان فيه أعمدة مكانها الطبيعي هو العلاقة نفسها: quantity (اشترى كام واحد من المنتج ده في الطلب ده)، و unit_price (السعر وقت الشراء، اللي ممكن يختلف عن سعر المنتج النهارده: بص على الطلب 9).
الـ Primary Key بتاع order_items هو (order_id, product_id) مع بعض. ده بيمنع إيه بالظبط؟
المفتاح المركّب معناه إن التركيبة متتكررش. المنتج 13 موجود في الطلبات 2 و 9 و 22، وده عادي. بس مينفعش يبقى فيه صفين للمنتج 13 جوه الطلب 2. لو العميل عايز اتنين، بنغيّر quantity لـ 2 بدل ما نضيف صف تاني.
العلاقة بين categories و products نوعها إيه؟
واحد لكتير، والـ FK (category_id) في ناحية الكتير: المنتجات. وكون العمود يقبل NULL معناه إن العلاقة اختيارية: ممكن منتج ملوش قسم (المنتج 18). العلاقة موجودة، بس مش إجبارية.
الـ Grain: الصف بيمثّل إيه؟
ده أهم مفهوم في الدرس كله، وهتسمعه كتير:
لما تمشي من جدول لجدول عن طريق علاقة واحد لكتير، الـ Grain بيتغيّر وعدد الصفوف بيزيد. عميل واحد بقى 4 طلبات، والـ 4 طلبات بقوا 5 صفوف في order_items. ولو جمعت أي حاجة من جدول customers بعد الربط (زي عدد العملاء)، هتلاقي الرقم متضاعف. وده أشهر سبب للأرقام الغلط في التقارير.
ربطت customers بـ orders بـ order_items. عميل عنده 3 طلبات، وكل طلب فيه منتجين. العميل ده هيظهر في كام صف؟
- كل صف في النتيجة = منتج جوه طلب، فالعميل بيتكرر مع كل واحد منهم. لو عايز صف واحد لكل عميل، لازم تجمّع (GROUP BY)، وده موضوع مرحلة التجميع.
اتمرّن
لسه ما اتعلمناش الربط ولا الفلترة، فالتمارين دي عن قراية الجداول صح.
تمرين تطبيقي
سهل✓ اتحلاعرض لكل طلب: رقمه (id)، ورقم العميل اللي طلبه، والحالة بتاعته.
SELECT id, customer_id, status FROM orders;
تمرين تطبيقي
سهل✓ اتحلاعرض كل موظف بالـ id والاسم، ورقم المدير بتاعه. مين الموظف اللي manager_id بتاعه NULL؟ وده منطقي ليه؟
SELECT id, name, manager_id FROM employees;
تمرين تطبيقي
متوسط✓ اتحللكل صف في order_items: اعرض رقم الطلب، ورقم المنتج، وإجمالي السطر (الكمية × سعر الوحدة) في عمود اسمه line_total. ده الـ Grain بتاع الجدول: منتج جوه طلب.
SELECT order_id, product_id, quantity * unit_price AS line_total FROM order_items;