ANUPET ACADEMY رجوع للموقع

SQL من الصفر: أول استعلام حقيقي على قاعدة بيانات

درس عملي تمشي فيه بإيدك: تعمل جدولين، تدخّل بيانات، وتكتب استعلامات SELECT وJOIN وGROUP BY تشتغل فعلاً — بمثال مركز تدريب حقيقي.

SQL من الصفر: أول استعلام حقيقي على قاعدة بيانات

لو فتحت أي إعلان شغل باك إند، هتلاقي SQL مكتوبة في المطلوب. والحلو في SQL إنها من أقل الحاجات اللي هتتعلمها تغيّراً مع الوقت: الاستعلام اللي هتكتبه دلوقتي هيفضل شغال بعد عشر سنين.

الدرس ده عملي بالكامل. هنبني مثال صغير لمركز تدريب — طلبة وحجوزات — ونمشي من أول جدول لحد استعلام بيجاوب على سؤال إدارة حقيقي. شغّل كل كود بإيدك؛ القراءة لوحدها مش هتنفع هنا.

التجهيز

محتاج قاعدة بيانات شغالة. أسهل طريقتين:

  • XAMPP على ويندوز، وتدخل على phpMyAdmin من المتصفح.
  • أو تنصّب MySQL لوحدها وتشتغل من الطرفية.

الأوامر اللي تحت شغالة على MySQL وMariaDB، وأغلبها هيشتغل على PostgreSQL بتغييرات بسيطة.

أول حاجة، اعمل قاعدة البيانات وادخل جواها:

CREATE DATABASE academy CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE academy;

الـ utf8mb4 مش تفصيلة كمالية: من غيرها الأسماء العربية والإيموجي بتتخزن غلط، ودي مشكلة بتظهر متأخر ووجعها كبير.

الخطوة ١: الجداول

جدول الطلبة:

CREATE TABLE students (
    id         INT AUTO_INCREMENT PRIMARY KEY,
    name       VARCHAR(120) NOT NULL,
    phone      VARCHAR(20)  NOT NULL UNIQUE,
    city       VARCHAR(60)  NOT NULL DEFAULT 'بنها',
    created_at DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP
);

خد بالك من تلات قرارات هنا:

  • PRIMARY KEY: مفتاح فريد لكل صف. من غيره مش هتعرف تشاور على صف بعينه.
  • NOT NULL: العمود ده ممنوع يفضل فاضي. كل NULL بتسمح بيها دلوقتي هي شرط IS NULL هتكتبه في كل استعلام بعد كده.
  • UNIQUE على التليفون: قاعدة البيانات نفسها هتمنع التكرار. متسيبش التحقق ده للكود بس — الكود ممكن يتنسى، القاعدة لأ.

وجدول الحجوزات، وكل حجز مربوط بطالب:

CREATE TABLE enrollments (
    id         INT AUTO_INCREMENT PRIMARY KEY,
    student_id INT          NOT NULL,
    course     VARCHAR(60)  NOT NULL,
    price      DECIMAL(8,2) NOT NULL,
    paid       TINYINT(1)   NOT NULL DEFAULT 0,
    created_at DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE
);

DECIMAL مش FLOAT في الفلوس — ديه مش تفضيل شخصي: FLOAT بيخزن تقريب ثنائي، فبتلاقي مجموع أرقام بيطلع بفرق قروش. وFOREIGN KEY معناها إن قاعدة البيانات هترفض حجز لطالب مش موجود.

الخطوة ٢: بيانات نلعب عليها

INSERT INTO students (name, phone, city) VALUES
    ('يوسف حسن',  '01011111111', 'بنها'),
    ('مريم سامي', '01022222222', 'طوخ'),
    ('أحمد كمال', '01033333333', 'بنها'),
    ('سارة وليد', '01044444444', 'القاهرة');

INSERT INTO enrollments (student_id, course, price, paid) VALUES
    (1, 'back-end',       4500.00, 1),
    (1, 'icdl',           1200.00, 1),
    (2, 'graphic-design', 3000.00, 0),
    (3, 'back-end',       4500.00, 1),
    (3, 'product-design', 3500.00, 0);

سارة (رقم ٤) معندهاش أي حجز عن قصد — هنستخدمها بعد شوية.

الخطوة ٣: أول استعلام

SELECT name, city FROM students WHERE city = 'بنها' ORDER BY name;

اقرا الاستعلام بالترتيب المنطقي مش بترتيب الكتابة: من أنهي جدول (FROM)، بأنهي شرط (WHERE)، أعرض إيه (SELECT)، مرتب إزاي (ORDER BY).

جرّب كمان:

SELECT * FROM enrollments WHERE paid = 0 ORDER BY price DESC LIMIT 5;

ده بيديك أغلى خمس حجوزات لسه متدفعتش. لاحظ إن الاستعلام ده لوحده فيه قيمة إدارية حقيقية — ودي فكرة SQL كلها.

الخطوة ٤: الـ JOIN

نتيجة الاستعلام اللي فوق فيها student_id بس، والإدارة عايزة اسم. هنا بييجي JOIN:

SELECT s.name, s.phone, e.course, e.price
FROM enrollments AS e
JOIN students   AS s ON s.id = e.student_id
WHERE e.paid = 0;

JOIN معناها: لكل صف في enrollments، هات الصف المقابل ليه في students عن طريق الشرط ده. لو مفيش مقابل، الصف بيتشال من النتيجة.

عشان كده لو عايز كل الطلبة حتى اللي محجزش، استخدم LEFT JOIN:

SELECT s.name, COUNT(e.id) AS courses_count
FROM students AS s
LEFT JOIN enrollments AS e ON e.student_id = s.id
GROUP BY s.id, s.name
ORDER BY courses_count DESC;

هنا هتشوف سارة بـ 0 — لأن LEFT معناها «سيب الصفوف اللي على الشمال حتى لو ملقتش مقابل». الفرق بين JOIN وLEFT JOIN من أكتر الحاجات اللي بتتسأل في الإنترفيو، وأكتر حاجة بتضيّع بيانات بالغلط.

الخطوة ٥: التجميع

السؤال: كل كورس دخّل كام، وكام واحد حجزه؟

SELECT course,
       COUNT(*)   AS students,
       SUM(price) AS total,
       SUM(CASE WHEN paid = 1 THEN price ELSE 0 END) AS collected
FROM enrollments
GROUP BY course
HAVING total > 1000
ORDER BY total DESC;

اتنين مهمين هنا: HAVING بتفلتر بعد التجميع (بينما WHERE بتفلتر قبله)، وSUM(CASE ...) بتخليك تحسب عمودين مختلفين في مرة واحدة — الاستعلام ده لوحده تقرير مالي.

الخطوة ٦: الفهرس

لما الجدول يكبر، الاستعلام اللي بيدوّر بالشرط بيبطّأ لأن القاعدة بتقرا كل الصفوف. الحل:

CREATE INDEX enrollments_paid_idx ON enrollments (paid);
EXPLAIN SELECT * FROM enrollments WHERE paid = 0;

EXPLAIN بتوريك القاعدة نفّذت الاستعلام إزاي. القاعدة العملية: حط فهرس على الأعمدة اللي بتظهر في WHERE وJOIN كتير، ومتحطش فهارس على كل حاجة — كل فهرس بيسرّع القراية وبيبطّأ الكتابة شوية.

اللي تعمله دلوقتي

اقفل المقال، وامسح الجدولين، واعملهم من دماغك تاني، وجاوب على تلات أسئلة بنفسك: مين أكتر مدينة فيها طلبة؟ مين حجز أكتر من كورس؟ كام فلوس لسه متحصّلتش؟ لو عرفت تكتب التلاتة من غير ما ترجع، إنت خلّصت أساس SQL فعلاً.

بعد كده بتيجي حتة إن الفريم ورك يكتب الاستعلامات دي عنك — وده اللي بنبني عليه في [الجزء التاني من مسار الباك إند](/courses/back-end-2). ولو لسه بتحدد إنت واقف فين في الترتيب، الخريطة الكاملة هنا: [خريطة طريق الباك إند من الصفر لأول شغلانة](/blog/backend-roadmap).

الكلمات المفتاحية تعلم sql من الصفر sql للمبتدئين شرح join mysql بالعربي قواعد البيانات select group by

كل المقالات