مقدمة عن SQL

ما هي SQL؟

SQL (Structured Query Language) هي لغة للتعامل مع قواعد البيانات العلائقية. تم تطويرها في السبعينيات من قبل IBM.

مميزات SQL:
  • لغة قياسية لمعظم قواعد البيانات
  • سهلة التعلم والفهم
  • قوية في التعامل مع البيانات
  • تدعم عمليات معقدة
أنواع أوامر SQL:
  • DDL: Data Definition Language (CREATE, ALTER, DROP)
  • DML: Data Manipulation Language (SELECT, INSERT, UPDATE, DELETE)
  • DCL: Data Control Language (GRANT, REVOKE)

قواعد البيانات والجداول

إنشاء قاعدة بيانات:

CREATE DATABASE mydb;

USE mydb;
حذف قاعدة بيانات:
DROP DATABASE mydb;
عرض قواعد البيانات:
SHOW DATABASES;

أنواع البيانات

أنواع البيانات الشائعة:

النوع الوصف مثال
INT عدد صحيح 25
VARCHAR(n) نص متغير الطول 'أحمد'
CHAR(n) نص ثابت الطول 'ABC'
DATE تاريخ '2024-01-15'
DECIMAL(p,s) عدد عشري 99.99
BOOLEAN قيمة منطقية TRUE/FALSE

CREATE TABLE

إنشاء جدول:

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE,
    age INT,
    created_at DATE DEFAULT CURRENT_DATE
);
تعديل جدول:
ALTER TABLE users 
ADD COLUMN phone VARCHAR(20);

ALTER TABLE users 
DROP COLUMN phone;

ALTER TABLE users 
MODIFY COLUMN name VARCHAR(200);
حذف جدول:
DROP TABLE users;

SELECT - الأساسيات

SELECT بسيط:

-- اختيار جميع الأعمدة
SELECT * FROM users;

-- اختيار أعمدة محددة
SELECT name, email FROM users;

-- اختيار مع أسماء مستعارة
SELECT name AS الاسم, email AS البريد FROM users;

-- اختيار قيم مميزة
SELECT DISTINCT city FROM users;

WHERE Clause

شروط WHERE:

-- مقارنة
SELECT * FROM users WHERE age > 25;
SELECT * FROM users WHERE age >= 18;
SELECT * FROM users WHERE age < 30;
SELECT * FROM users WHERE age <= 25;
SELECT * FROM users WHERE age = 25;
SELECT * FROM users WHERE age != 25;

-- AND و OR
SELECT * FROM users WHERE age > 25 AND city = 'القاهرة';
SELECT * FROM users WHERE age > 25 OR city = 'الإسكندرية';

-- IN و NOT IN
SELECT * FROM users WHERE city IN ('القاهرة', 'الإسكندرية');
SELECT * FROM users WHERE city NOT IN ('القاهرة');

-- LIKE (البحث)
SELECT * FROM users WHERE name LIKE 'أحمد%';
SELECT * FROM users WHERE email LIKE '%@gmail.com';

-- BETWEEN
SELECT * FROM users WHERE age BETWEEN 20 AND 30;

-- IS NULL و IS NOT NULL
SELECT * FROM users WHERE email IS NULL;
SELECT * FROM users WHERE email IS NOT NULL;

ORDER BY

ترتيب النتائج:

-- ترتيب تصاعدي
SELECT * FROM users ORDER BY name ASC;

-- ترتيب تنازلي
SELECT * FROM users ORDER BY age DESC;

-- ترتيب متعدد
SELECT * FROM users ORDER BY city ASC, age DESC;

-- ترتيب حسب رقم العمود
SELECT name, email, age FROM users ORDER BY 3 DESC;

INSERT

إضافة بيانات:

-- إضافة سجل واحد
INSERT INTO users (name, email, age) 
VALUES ('أحمد', 'ahmed@example.com', 25);

-- إضافة عدة سجلات
INSERT INTO users (name, email, age) 
VALUES 
    ('محمد', 'mohamed@example.com', 30),
    ('علي', 'ali@example.com', 28);

-- إضافة بدون تحديد الأعمدة (يجب ترتيب القيم)
INSERT INTO users VALUES (1, 'أحمد', 'ahmed@example.com', 25);

-- إضافة من جدول آخر
INSERT INTO users_backup 
SELECT * FROM users WHERE age > 25;

UPDATE

تحديث البيانات:

-- تحديث سجل واحد
UPDATE users 
SET email = 'newemail@example.com' 
WHERE id = 1;

-- تحديث عدة أعمدة
UPDATE users 
SET name = 'أحمد محمد', age = 26 
WHERE id = 1;

-- تحديث عدة سجلات
UPDATE users 
SET city = 'القاهرة' 
WHERE city IS NULL;

-- تحديث بشرط معقد
UPDATE users 
SET age = age + 1 
WHERE age < 18;

DELETE

حذف البيانات:

-- حذف سجل واحد
DELETE FROM users WHERE id = 1;

-- حذف عدة سجلات
DELETE FROM users WHERE age < 18;

-- حذف جميع السجلات (احذر!)
DELETE FROM users;

-- حذف مع JOIN
DELETE u FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.total < 100;

JOIN - المقدمة

ما هو JOIN؟

JOIN يستخدم لربط البيانات من جداول متعددة بناءً على علاقة مشتركة.

أنواع JOIN:
  • INNER JOIN: يعيد السجلات المطابقة فقط
  • LEFT JOIN: يعيد جميع السجلات من الجدول الأيسر
  • RIGHT JOIN: يعيد جميع السجلات من الجدول الأيمن
  • FULL OUTER JOIN: يعيد جميع السجلات من كلا الجدولين

INNER JOIN

INNER JOIN:

-- صيغة أساسية
SELECT u.name, o.order_date, o.total
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

-- مع WHERE
SELECT u.name, o.order_date
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.total > 1000;

-- JOIN متعدد
SELECT u.name, o.order_date, p.product_name
FROM users u
INNER JOIN orders o ON u.id = o.user_id
INNER JOIN products p ON o.product_id = p.id;

LEFT JOIN و RIGHT JOIN

LEFT JOIN:

SELECT u.name, o.order_date
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

-- يعيد جميع المستخدمين حتى لو لم يكن لديهم طلبات
RIGHT JOIN:
SELECT u.name, o.order_date
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;

-- يعيد جميع الطلبات حتى لو لم يكن المستخدم موجوداً

FULL OUTER JOIN

FULL OUTER JOIN:

SELECT u.name, o.order_date
FROM users u
FULL OUTER JOIN orders o ON u.id = o.user_id;

-- يعيد جميع السجلات من كلا الجدولين
ملاحظة:

بعض قواعد البيانات لا تدعم FULL OUTER JOIN مباشرة، يمكن استخدام UNION مع LEFT و RIGHT JOIN.

الدوال التجميعية

الدوال التجميعية:

-- COUNT: عدد السجلات
SELECT COUNT(*) FROM users;
SELECT COUNT(DISTINCT city) FROM users;

-- SUM: مجموع القيم
SELECT SUM(total) FROM orders;

-- AVG: المتوسط
SELECT AVG(age) FROM users;

-- MAX: القيمة العظمى
SELECT MAX(price) FROM products;

-- MIN: القيمة الصغرى
SELECT MIN(price) FROM products;

GROUP BY

تجميع البيانات:

-- تجميع بسيط
SELECT city, COUNT(*) 
FROM users 
GROUP BY city;

-- تجميع مع دوال
SELECT city, AVG(age), COUNT(*) 
FROM users 
GROUP BY city;

-- تجميع متعدد
SELECT city, gender, COUNT(*) 
FROM users 
GROUP BY city, gender;

-- مع ORDER BY
SELECT city, COUNT(*) as count
FROM users 
GROUP BY city
ORDER BY count DESC;

HAVING

HAVING Clause:

-- HAVING مع GROUP BY
SELECT city, COUNT(*) as count
FROM users 
GROUP BY city
HAVING COUNT(*) > 10;

-- HAVING vs WHERE
SELECT city, AVG(age) as avg_age
FROM users 
WHERE age > 18
GROUP BY city
HAVING AVG(age) > 25;
الفرق بين WHERE و HAVING:
  • WHERE: يفلتر السجلات قبل التجميع
  • HAVING: يفلتر المجموعات بعد التجميع

الدوال النصية

دوال النصوص:

-- UPPER و LOWER
SELECT UPPER(name) FROM users;
SELECT LOWER(email) FROM users;

-- SUBSTRING
SELECT SUBSTRING(name, 1, 5) FROM users;
SELECT SUBSTRING(email, 1, LOCATE('@', email) - 1) FROM users;

-- CONCAT
SELECT CONCAT(name, ' - ', email) FROM users;

-- LENGTH
SELECT name, LENGTH(name) FROM users;

-- TRIM
SELECT TRIM('  أحمد  ') AS trimmed;

-- REPLACE
SELECT REPLACE(email, '@gmail.com', '@yahoo.com') FROM users;

الدوال التاريخية

دوال التاريخ:

-- NOW و CURRENT_DATE
SELECT NOW();
SELECT CURRENT_DATE;
SELECT CURRENT_TIME;

-- DATE_FORMAT
SELECT DATE_FORMAT(created_at, '%Y-%m-%d') FROM users;

-- DATEDIFF
SELECT DATEDIFF('2024-12-31', '2024-01-01') AS days;

-- DATE_ADD و DATE_SUB
SELECT DATE_ADD(created_at, INTERVAL 30 DAY) FROM users;
SELECT DATE_SUB(created_at, INTERVAL 1 MONTH) FROM users;

-- YEAR, MONTH, DAY
SELECT YEAR(created_at), MONTH(created_at), DAY(created_at) FROM users;

Subqueries

الاستعلامات الفرعية:

-- Subquery في WHERE
SELECT * FROM users 
WHERE age > (SELECT AVG(age) FROM users);

-- Subquery في SELECT
SELECT name, 
       (SELECT COUNT(*) FROM orders WHERE user_id = users.id) AS order_count
FROM users;

-- Subquery في FROM
SELECT * FROM (
    SELECT name, age FROM users WHERE age > 25
) AS filtered_users;

-- EXISTS
SELECT * FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.id
);

UNION و UNION ALL

UNION:

-- UNION (يحذف التكرارات)
SELECT name FROM users
UNION
SELECT name FROM customers;

-- UNION ALL (يحتفظ بالتكرارات)
SELECT name FROM users
UNION ALL
SELECT name FROM customers;

-- مع ORDER BY
SELECT name FROM users
UNION
SELECT name FROM customers
ORDER BY name;

Constraints

القيود (Constraints):

-- PRIMARY KEY
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(100)
);

-- FOREIGN KEY
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

-- UNIQUE
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(100) UNIQUE
);

-- CHECK
CREATE TABLE users (
    id INT PRIMARY KEY,
    age INT CHECK (age >= 18)
);

-- NOT NULL
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

Indexes

الفهارس (Indexes):

-- إنشاء فهرس
CREATE INDEX idx_email ON users(email);

-- فهرس مركب
CREATE INDEX idx_city_age ON users(city, age);

-- فهرس فريد
CREATE UNIQUE INDEX idx_email_unique ON users(email);

-- حذف فهرس
DROP INDEX idx_email ON users;

-- عرض الفهارس
SHOW INDEXES FROM users;
فوائد الفهارس:
  • تسريع عمليات البحث
  • تحسين أداء JOIN
  • تسريع ORDER BY

Views

العروض (Views):

-- إنشاء View
CREATE VIEW user_orders AS
SELECT u.name, o.order_date, o.total
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

-- استخدام View
SELECT * FROM user_orders;

-- تحديث View
CREATE OR REPLACE VIEW user_orders AS
SELECT u.name, o.order_date, o.total, o.status
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

-- حذف View
DROP VIEW user_orders;

Stored Procedures

الإجراءات المخزنة:

-- إنشاء Procedure
DELIMITER //
CREATE PROCEDURE GetUserOrders(IN user_id INT)
BEGIN
    SELECT * FROM orders WHERE user_id = user_id;
END //
DELIMITER ;

-- استدعاء Procedure
CALL GetUserOrders(1);

-- Procedure مع معاملات متعددة
DELIMITER //
CREATE PROCEDURE AddUser(
    IN p_name VARCHAR(100),
    IN p_email VARCHAR(100)
)
BEGIN
    INSERT INTO users (name, email) VALUES (p_name, p_email);
END //
DELIMITER ;

CALL AddUser('أحمد', 'ahmed@example.com');

Functions

الدوال المخصصة:

-- إنشاء Function
DELIMITER //
CREATE FUNCTION GetUserAge(user_id INT)
RETURNS INT
READS SQL DATA
BEGIN
    DECLARE user_age INT;
    SELECT age INTO user_age FROM users WHERE id = user_id;
    RETURN user_age;
END //
DELIMITER ;

-- استخدام Function
SELECT GetUserAge(1) AS age;

-- Function مع معاملات
DELIMITER //
CREATE FUNCTION CalculateDiscount(price DECIMAL(10,2), discount_percent INT)
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
    RETURN price * (1 - discount_percent / 100);
END //
DELIMITER ;

SELECT CalculateDiscount(100, 20) AS final_price;

Triggers

المشغلات (Triggers):

-- Trigger قبل INSERT
DELIMITER //
CREATE TRIGGER before_user_insert
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
    SET NEW.created_at = NOW();
END //
DELIMITER ;

-- Trigger بعد UPDATE
DELIMITER //
CREATE TRIGGER after_order_update
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
    INSERT INTO order_history (order_id, old_status, new_status, updated_at)
    VALUES (NEW.id, OLD.status, NEW.status, NOW());
END //
DELIMITER ;

-- حذف Trigger
DROP TRIGGER before_user_insert;

مشروع قاعدة بيانات

نظام إدارة متجر:

-- إنشاء قاعدة البيانات
CREATE DATABASE store_db;
USE store_db;

-- جدول العملاء
CREATE TABLE customers (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE,
    phone VARCHAR(20),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- جدول المنتجات
CREATE TABLE products (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    stock INT DEFAULT 0,
    category_id INT
);

-- جدول الطلبات
CREATE TABLE orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT,
    order_date DATE,
    total DECIMAL(10,2),
    status VARCHAR(20) DEFAULT 'pending',
    FOREIGN KEY (customer_id) REFERENCES customers(id)
);

-- جدول تفاصيل الطلبات
CREATE TABLE order_items (
    id INT PRIMARY KEY AUTO_INCREMENT,
    order_id INT,
    product_id INT,
    quantity INT,
    price DECIMAL(10,2),
    FOREIGN KEY (order_id) REFERENCES orders(id),
    FOREIGN KEY (product_id) REFERENCES products(id)
);

Best Practices

أفضل الممارسات:

  • استخدم أسماء واضحة للجداول والأعمدة
  • استخدم PRIMARY KEY لكل جدول
  • استخدم FOREIGN KEY للعلاقات
  • أضف فهارس على الأعمدة المستخدمة في WHERE و JOIN
  • استخدم Transactions للعمليات المهمة
  • احذف البيانات القديمة بانتظام
  • استخدم Prepared Statements للحماية من SQL Injection
  • احتفظ بنسخ احتياطية منتظمة
  • استخدم EXPLAIN لتحليل أداء الاستعلامات
  • تجنب SELECT * واستخدم أسماء الأعمدة

مشروع تطبيقي كامل

نظام إدارة مكتبة:

-- قاعدة البيانات
CREATE DATABASE library_db;
USE library_db;

-- جدول الكتب
CREATE TABLE books (
    id INT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(200) NOT NULL,
    author VARCHAR(100),
    isbn VARCHAR(20) UNIQUE,
    category VARCHAR(50),
    published_year INT,
    copies INT DEFAULT 1
);

-- جدول الأعضاء
CREATE TABLE members (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE,
    phone VARCHAR(20),
    membership_date DATE
);

-- جدول الإعارات
CREATE TABLE loans (
    id INT PRIMARY KEY AUTO_INCREMENT,
    member_id INT,
    book_id INT,
    loan_date DATE,
    return_date DATE,
    due_date DATE,
    status VARCHAR(20) DEFAULT 'active',
    FOREIGN KEY (member_id) REFERENCES members(id),
    FOREIGN KEY (book_id) REFERENCES books(id)
);

-- View للإعارات النشطة
CREATE VIEW active_loans AS
SELECT m.name AS member_name, 
       b.title AS book_title,
       l.loan_date,
       l.due_date
FROM loans l
INNER JOIN members m ON l.member_id = m.id
INNER JOIN books b ON l.book_id = b.id
WHERE l.status = 'active';

-- Procedure لإضافة إعارة
DELIMITER //
CREATE PROCEDURE AddLoan(
    IN p_member_id INT,
    IN p_book_id INT
)
BEGIN
    DECLARE available_copies INT;
    SELECT copies INTO available_copies FROM books WHERE id = p_book_id;
    
    IF available_copies > 0 THEN
        INSERT INTO loans (member_id, book_id, loan_date, due_date)
        VALUES (p_member_id, p_book_id, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 14 DAY));
        
        UPDATE books SET copies = copies - 1 WHERE id = p_book_id;
    END IF;
END //
DELIMITER ;