المشروع الختامي: نظام المدونة
المشروع الختامي: نظام المدونة
هذا الدرس هو المشروع الختامي لبرنامج تعليم SQL. سنطبق جميع المعارف المكتسبة حتى الآن لتصميم وتنفيذ قاعدة بيانات نظام مدونة كامل من الصفر.
🎯 تشبيه من الحياة
بناء نظام مدونة مثل فتح مكتبة كتب:
- إدارة المستخدمين: تسجيل الأعضاء، تسجيل الدخول، الملفات الشخصية → جدول users
- إدارة المقالات: الكتابة، النشر، التصنيف على الرفوف → جدول articles + جدول categories
- نظام الوسوم: وسم الكتب لسهولة الاسترجاع → جدول tags + جدول وسيط
- نظام التعليقات: مناقشات وتعليقات القراء → جدول comments
- التحليلات: أي الكتب الأكثر شعبية، أي المؤلفين الأكثر شهرة → استعلامات معقدة
الجزء 1: تحليل المتطلبات
المتطلبات الوظيفية
| الوحدة | الميزات |
|---|---|
| المستخدمون | تسجيل، تسجيل دخول، ملف شخصي، متابعة مستخدمين آخرين |
| المقالات | نشر، تعديل، حذف (ناعم)، مسودة، نشر |
| الفئات | إدارة فئات المقالات (تدعم فئات متسلسلة) |
| الوسوم | وسم المقالات (عديد-إلى-عديد) |
| التعليقات | تعليقات المقالات، ردود متداخلة |
| التحليلات | ترتيب المقالات الشائعة، إحصائيات المستخدمين، إحصائيات الوسوم |
تقديرات حجم البيانات
| الجدول | الحجم المقدر | نسبة القراءة/الكتابة |
|---|---|---|
| users | 100K | قراءة مكثفة |
| articles | 1M | القراءة تفوق الكتابة بكثير |
| comments | 5M | قراءة/كتابة متوازنة |
| tags | 1K | قراءة مكثفة |
| categories | 100 | قراءة مكثفة |
الجزء 2: تصميم قاعدة البيانات
مخطط ER
[users] 1 ──── N [articles] N ──── N [tags]
│ │
│ │ 1
│ │
│ N
│ [comments]
│ │
N │ (مرجعي ذاتي)
│ │
[users] 1 ──── N [comments]
[categories] 1 ──── N [articles]
│
└── 1 (مرجعي ذاتي, parent_id)
وصف العلاقات
| العلاقة | النوع | التطبيق |
|---|---|---|
| مستخدم → مقال | واحد-إلى-عديد | articles.user_id |
| مقال → وسم | عديد-إلى-عديد | جدول وسيط article_tag |
| مقال → تعليق | واحد-إلى-عديد | comments.article_id |
| مستخدم → تعليق | واحد-إلى-عديد | comments.user_id |
| تعليق → تعليق | مرجعي ذاتي (متداخل) | comments.parent_id |
| فئة → مقال | واحد-إلى-عديد | articles.category_id |
| فئة → فئة | مرجعي ذاتي (متسلسل) | categories.parent_id |
| مستخدم → مستخدم | عديد-إلى-عديد (متابعة) | جدول وسيط user_follows |
الجزء 3: سكربتات إنشاء الجداول
-- =============================================
-- مخطط قاعدة بيانات نظام المدونة
-- مجموعة الأحرف: utf8mb4 (يدعم الرموز التعبيرية وتعدد اللغات)
-- محرك التخزين: InnoDB (يدعم المعاملات والمفاتيح الخارجية)
-- =============================================
-- إنشاء قاعدة البيانات
CREATE DATABASE IF NOT EXISTS blog_system
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
USE blog_system;
-- ----------------------------
-- 1. جدول المستخدمين
-- ----------------------------
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'معرف المستخدم',
username VARCHAR(50) NOT NULL COMMENT 'اسم المستخدم',
email VARCHAR(100) NOT NULL COMMENT 'البريد الإلكتروني',
password_hash VARCHAR(255) NOT NULL COMMENT 'تجزئة كلمة المرور',
nickname VARCHAR(50) COMMENT 'اللقب',
avatar_url VARCHAR(500) COMMENT 'رابط الصورة الرمزية',
bio VARCHAR(500) COMMENT 'نبذة تعريفية',
website VARCHAR(200) COMMENT 'الموقع الشخصي',
status TINYINT NOT NULL DEFAULT 1 COMMENT 'الحالة: 1-نشط 0-معطل 2-في انتظار التحقق',
role VARCHAR(20) NOT NULL DEFAULT 'user' COMMENT 'الدور: user/author/admin',
article_count INT NOT NULL DEFAULT 0 COMMENT 'عدد المقالات (مُلغاى تطبيعه)',
follower_count INT NOT NULL DEFAULT 0 COMMENT 'عدد المتابعين (مُلغاى تطبيعه)',
last_login_at DATETIME COMMENT 'وقت آخر تسجيل دخول',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'وقت التسجيل',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'وقت التحديث',
is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT 'حذف ناعم: 0-عادي 1-محذوف',
UNIQUE KEY uk_username (username),
UNIQUE KEY uk_email (email),
INDEX idx_status (status),
INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='جدول المستخدمين';
-- ----------------------------
-- 2. جدول علاقات المتابعة (عديد-إلى-عديد)
-- ----------------------------
CREATE TABLE user_follows (
follower_id BIGINT NOT NULL COMMENT 'معرف المتابع',
following_id BIGINT NOT NULL COMMENT 'معرف المتابَع',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'وقت المتابعة',
PRIMARY KEY (follower_id, following_id),
INDEX idx_following_id (following_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='جدول علاقات متابعة المستخدمين';
-- ----------------------------
-- 3. جدول الفئات (يدعم الفئات المتسلسلة)
-- ----------------------------
CREATE TABLE categories (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'معرف الفئة',
name VARCHAR(50) NOT NULL COMMENT 'اسم الفئة',
slug VARCHAR(100) NOT NULL COMMENT 'معرّف URL',
description VARCHAR(200) COMMENT 'وصف الفئة',
parent_id BIGINT DEFAULT NULL COMMENT 'معرف الفئة الأب، NULL للأعلى',
sort_order INT NOT NULL DEFAULT 0 COMMENT 'ترتيب الفرز',
article_count INT NOT NULL DEFAULT 0 COMMENT 'عدد المقالات (مُلغاى تطبيعه)',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_slug (slug),
INDEX idx_parent_id (parent_id),
INDEX idx_sort (sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='جدول الفئات';
-- ----------------------------
-- 4. جدول الوسوم
-- ----------------------------
CREATE TABLE tags (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'معرف الوسم',
name VARCHAR(50) NOT NULL COMMENT 'اسم الوسم',
slug VARCHAR(100) NOT NULL COMMENT 'معرّف URL',
article_count INT NOT NULL DEFAULT 0 COMMENT 'عدد المقالات (مُلغاى تطبيعه)',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_name (name),
UNIQUE KEY uk_slug (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='جدول الوسوم';
-- ----------------------------
-- 5. جدول المقالات
-- ----------------------------
CREATE TABLE articles (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'معرف المقال',
user_id BIGINT NOT NULL COMMENT 'معرف المؤلف',
category_id BIGINT COMMENT 'معرف الفئة',
title VARCHAR(200) NOT NULL COMMENT 'العنوان',
slug VARCHAR(200) NOT NULL COMMENT 'معرّف URL',
summary VARCHAR(500) COMMENT 'ملخص',
content LONGTEXT NOT NULL COMMENT 'المحتوى',
cover_image_url VARCHAR(500) COMMENT 'صورة الغلاف',
status TINYINT NOT NULL DEFAULT 0 COMMENT 'الحالة: 0-مسودة 1-منشور 2-غير مُدرج',
is_pinned TINYINT NOT NULL DEFAULT 0 COMMENT 'مثبت: 0-لا 1-نعم',
view_count INT NOT NULL DEFAULT 0 COMMENT 'عدد المشاهدات',
like_count INT NOT NULL DEFAULT 0 COMMENT 'عدد الإعجابات',
comment_count INT NOT NULL DEFAULT 0 COMMENT 'عدد التعليقات (مُلغاى تطبيعه)',
published_at DATETIME COMMENT 'وقت النشر',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'وقت الإنشاء',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'وقت التحديث',
is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT 'حذف ناعم: 0-عادي 1-محذوف',
UNIQUE KEY uk_slug (slug),
INDEX idx_user_id (user_id),
INDEX idx_category_id (category_id),
INDEX idx_status_published (status, published_at DESC),
INDEX idx_is_pinned (is_pinned),
INDEX idx_created_at (created_at DESC),
FULLTEXT INDEX ft_title_content (title, content)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='جدول المقالات';
-- ----------------------------
-- 6. جدول ارتباط المقالات بالوسوم (عديد-إلى-عديد)
-- ----------------------------
CREATE TABLE article_tag (
article_id BIGINT NOT NULL COMMENT 'معرف المقال',
tag_id BIGINT NOT NULL COMMENT 'معرف الوسم',
PRIMARY KEY (article_id, tag_id),
INDEX idx_tag_id (tag_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='جدول ارتباط المقالات بالوسوم';
-- ----------------------------
-- 7. جدول التعليقات (يدعم الردود المتداخلة)
-- ----------------------------
CREATE TABLE comments (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'معرف التعليق',
article_id BIGINT NOT NULL COMMENT 'معرف المقال',
user_id BIGINT NOT NULL COMMENT 'معرف المعلّق',
parent_id BIGINT DEFAULT NULL COMMENT 'معرف التعليق الأب، NULL للرئيسي',
reply_to_user_id BIGINT DEFAULT NULL COMMENT 'المستخدم المُرد عليه',
content TEXT NOT NULL COMMENT 'محتوى التعليق',
like_count INT NOT NULL DEFAULT 0 COMMENT 'عدد الإعجابات',
status TINYINT NOT NULL DEFAULT 1 COMMENT 'الحالة: 0-معلق 1-مقبول 2-مرفوض',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT 'حذف ناعم',
INDEX idx_article_id (article_id),
INDEX idx_user_id (user_id),
INDEX idx_parent_id (parent_id),
INDEX idx_created_at (created_at DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='جدول التعليقات';
-- ----------------------------
-- 8. جدول إعجابات المقالات
-- ----------------------------
CREATE TABLE article_likes (
user_id BIGINT NOT NULL COMMENT 'معرف المستخدم',
article_id BIGINT NOT NULL COMMENT 'معرف المقال',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, article_id),
INDEX idx_article_id (article_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='جدول إعجابات المقالات';
الجزء 4: عمليات CRUD
إدارة المستخدمين
-- تسجيل مستخدم جديد
INSERT INTO users (username, email, password_hash, nickname)
VALUES ('zhangsan', 'zhangsan@example.com', SHA2('mypassword123', 256), 'Zhang San');
-- تسجيل دخول المستخدم (استعلام باسم المستخدم)
SELECT id, username, nickname, avatar_url, role
FROM users
WHERE username = 'zhangsan' AND password_hash = SHA2('mypassword123', 256)
AND status = 1 AND is_deleted = 0;
-- تحديث الملف الشخصي
UPDATE users
SET nickname = 'Zhang Sanfeng',
bio = 'أحب البرمجة، أحب الحياة',
website = 'https://zhangsan.dev'
WHERE id = 1 AND is_deleted = 0;
-- حذف ناعم للمستخدم
UPDATE users SET is_deleted = 1 WHERE id = 1;
-- استعلام تفاصيل المستخدم (استثناء المحذوفين)
SELECT id, username, email, nickname, avatar_url, bio, website,
article_count, follower_count, created_at
FROM users
WHERE id = 1 AND is_deleted = 0;
إدارة الفئات
-- إنشاء فئات رئيسية
INSERT INTO categories (name, slug, description, sort_order)
VALUES ('Tech', 'tech', 'مقالات تقنية', 1),
('Life', 'life', 'مقالات حياتية', 2),
('Reading', 'reading', 'ملاحظات كتب', 3);
-- إنشاء فئات فرعية
INSERT INTO categories (name, slug, description, parent_id, sort_order)
VALUES ('Frontend', 'frontend', 'تطوير الواجهة الأمامية', 1, 1),
('Backend', 'backend', 'تطوير الواجهة الخلفية', 1, 2),
('Database', 'database', 'تقنيات قواعد البيانات', 1, 3),
('JavaScript', 'javascript', 'لغة JavaScript', 4, 1),
('Python', 'python', 'لغة Python', 5, 1);
-- استعلام شجرة الفئات
SELECT
c1.id, c1.name AS level1,
c2.name AS level2,
c3.name AS level3
FROM categories c1
LEFT JOIN categories c2 ON c2.parent_id = c1.id
LEFT JOIN categories c3 ON c3.parent_id = c2.id
WHERE c1.parent_id IS NULL
ORDER BY c1.sort_order, c2.sort_order, c3.sort_order;
إدارة الوسوم
-- إنشاء وسوم
INSERT INTO tags (name, slug) VALUES
('MySQL', 'mysql'),
('JavaScript', 'javascript'),
('Python', 'python'),
('Docker', 'docker'),
('Redis', 'redis'),
('Vue.js', 'vuejs'),
('React', 'react'),
('Algorithms', 'algorithms');
-- استعلام الوسوم (مرتبة حسب عدد المقالات)
SELECT t.id, t.name, t.slug, t.article_count
FROM tags t
ORDER BY t.article_count DESC
LIMIT 20;
إدارة المقالات
-- إنشاء مقال (مسودة)
INSERT INTO articles (user_id, category_id, title, slug, summary, content, status)
VALUES (
1,
7, -- فئة JavaScript
'دليل عملي لتحسين فهارس MySQL',
'mysql-index-optimization-guide',
'يشرح هذا المقال استراتيجيات وتقنيات تحسين فهارس MySQL الأساسية',
'# تحسين فهارس MySQL\n\n## ما هو الفهرس\n\nالفهرس هو...',
0 -- مسودة
);
-- نشر المقال
UPDATE articles
SET status = 1,
published_at = NOW()
WHERE id = 1 AND user_id = 1;
-- وسم المقال
INSERT INTO article_tag (article_id, tag_id) VALUES
(1, 1), -- MySQL
(1, 8); -- Algorithms
-- تحديث وسوم المقال (حذف ثم إدراج)
DELETE FROM article_tag WHERE article_id = 1;
INSERT INTO article_tag (article_id, tag_id) VALUES
(1, 1),
(1, 5); -- MySQL + Redis
-- حذف ناعم للمقال
UPDATE articles SET is_deleted = 1 WHERE id = 1 AND user_id = 1;
-- تحديث الحقل المُلغاى تطبيعه: عدد مقالات المستخدم
UPDATE users SET article_count = (
SELECT COUNT(*) FROM articles WHERE user_id = 1 AND is_deleted = 0
) WHERE id = 1;
-- تحديث الحقل المُلغاى تطبيعه: عدد مقالات الفئة
UPDATE categories SET article_count = (
SELECT COUNT(*) FROM articles WHERE category_id = 7 AND status = 1 AND is_deleted = 0
) WHERE id = 7;
-- تحديث الحقل المُلغاى تطبيعه: عدد مقالات الوسم
UPDATE tags SET article_count = (
SELECT COUNT(*) FROM article_tag WHERE tag_id = 1
) WHERE id = 1;
إدارة التعليقات
-- نشر تعليق
INSERT INTO comments (article_id, user_id, content)
VALUES (1, 2, 'مقال رائع، تعلمت الكثير!');
-- الرد على تعليق
INSERT INTO comments (article_id, user_id, parent_id, reply_to_user_id, content)
VALUES (1, 3, 1, 2, 'أوافقك الرأي، خاصة جزء الفهارس كان واضحاً جداً');
-- تحديث عدد تعليقات المقال
UPDATE articles SET comment_count = (
SELECT COUNT(*) FROM comments WHERE article_id = 1 AND status = 1 AND is_deleted = 0
) WHERE id = 1;
-- حذف ناعم للتعليق
UPDATE comments SET is_deleted = 1 WHERE id = 1 AND user_id = 2;
متابعة المستخدمين
-- متابعة مستخدم
INSERT INTO user_follows (follower_id, following_id) VALUES (2, 1);
-- تحديث عدد المتابعين المُلغاى تطبيعه
UPDATE users SET follower_count = follower_count + 1 WHERE id = 1;
-- إلغاء المتابعة
DELETE FROM user_follows WHERE follower_id = 2 AND following_id = 1;
UPDATE users SET follower_count = follower_count - 1 WHERE id = 1;
-- استعلام قائمة متابَعيني
SELECT u.id, u.username, u.nickname, u.avatar_url, u.bio
FROM user_follows f
JOIN users u ON f.following_id = u.id
WHERE f.follower_id = 2 AND u.is_deleted = 0
ORDER BY f.created_at DESC;
-- استعلام قائمة متابعيني
SELECT u.id, u.username, u.nickname, u.avatar_url, u.bio
FROM user_follows f
JOIN users u ON f.follower_id = u.id
WHERE f.following_id = 1 AND u.is_deleted = 0
ORDER BY f.created_at DESC;
الجزء 5: الاستعلامات المعقدة
ترتيب المقالات الشائعة (آخر 30 يوماً، درجة مركبة من المشاهدات والإعجابات)
SELECT
a.id,
a.title,
a.slug,
a.view_count,
a.like_count,
a.comment_count,
a.published_at,
u.username AS author,
u.avatar_url AS author_avatar,
c.name AS category_name,
-- درجة الشعبية المركبة: مشاهدات×1 + إعجابات×5 + تعليقات×3
(a.view_count * 1 + a.like_count * 5 + a.comment_count * 3) AS hot_score,
GROUP_CONCAT(t.name ORDER BY t.name SEPARATOR ', ') AS tags
FROM articles a
JOIN users u ON a.user_id = u.id
LEFT JOIN categories c ON a.category_id = c.id
LEFT JOIN article_tag at2 ON a.id = at2.article_id
LEFT JOIN tags t ON at2.tag_id = t.id
WHERE a.status = 1
AND a.is_deleted = 0
AND a.published_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY a.id
ORDER BY hot_score DESC
LIMIT 20;
لوحة إحصائيات المستخدم
SELECT
u.id,
u.username,
u.nickname,
u.article_count,
u.follower_count,
-- إجمالي المشاهدات
IFNULL(SUM(a.view_count), 0) AS total_views,
-- إجمالي الإعجابات
IFNULL(SUM(a.like_count), 0) AS total_likes,
-- إجمالي التعليقات
IFNULL(SUM(a.comment_count), 0) AS total_comments,
-- وقت آخر مقال منشور
MAX(a.published_at) AS last_published_at,
-- متوسط المشاهدات لكل مقال
IFNULL(ROUND(AVG(a.view_count), 0), 0) AS avg_views_per_article
FROM users u
LEFT JOIN articles a ON u.id = a.user_id AND a.status = 1 AND a.is_deleted = 0
WHERE u.id = 1 AND u.is_deleted = 0
GROUP BY u.id;
صفحة تفاصيل المقال (مع قائمة الوسوم)
SELECT
a.id,
a.title,
a.content,
a.cover_image_url,
a.view_count,
a.like_count,
a.comment_count,
a.published_at,
a.updated_at,
u.id AS author_id,
u.username AS author_username,
u.nickname AS author_nickname,
u.avatar_url AS author_avatar,
u.bio AS author_bio,
c.name AS category_name,
c.slug AS category_slug
FROM articles a
JOIN users u ON a.user_id = u.id
LEFT JOIN categories c ON a.category_id = c.id
WHERE a.slug = 'mysql-index-optimization-guide'
AND a.status = 1
AND a.is_deleted = 0;
-- استعلام وسوم المقال
SELECT t.id, t.name, t.slug
FROM article_tag at2
JOIN tags t ON at2.tag_id = t.id
WHERE at2.article_id = 1;
-- زيادة عدد المشاهدات
UPDATE articles SET view_count = view_count + 1 WHERE id = 1;
قائمة تعليقات المقال (بنية متداخلة)
-- استعلام التعليقات الرئيسية
SELECT
c.id,
c.content,
c.like_count,
c.created_at,
u.id AS user_id,
u.username,
u.nickname,
u.avatar_url
FROM comments c
JOIN users u ON c.user_id = u.id
WHERE c.article_id = 1
AND c.parent_id IS NULL
AND c.status = 1
AND c.is_deleted = 0
ORDER BY c.created_at DESC
LIMIT 20;
-- استعلام الردود على تعليق محدد
SELECT
c.id,
c.content,
c.like_count,
c.created_at,
u.username,
u.nickname,
u.avatar_url,
ru.username AS reply_to_username,
ru.nickname AS reply_to_nickname
FROM comments c
JOIN users u ON c.user_id = u.id
LEFT JOIN users ru ON c.reply_to_user_id = ru.id
WHERE c.parent_id = 1
AND c.status = 1
AND c.is_deleted = 0
ORDER BY c.created_at ASC;
قائمة المقالات المرتبطة بوسم
-- استعلام جميع المقالات تحت وسم محدد
SELECT
a.id,
a.title,
a.slug,
a.summary,
a.view_count,
a.like_count,
a.published_at,
u.username AS author,
u.avatar_url AS author_avatar
FROM article_tag at2
JOIN articles a ON at2.article_id = a.id
JOIN users u ON a.user_id = u.id
WHERE at2.tag_id = 1
AND a.status = 1
AND a.is_deleted = 0
ORDER BY a.published_at DESC
LIMIT 20;
إحصائيات مقالات الفئات
-- عدد المقالات ومتوسط المشاهدات لكل فئة
SELECT
c.id,
c.name,
c.slug,
c.article_count,
IFNULL(COUNT(a.id), 0) AS actual_count,
IFNULL(ROUND(AVG(a.view_count)), 0) AS avg_views,
IFNULL(MAX(a.published_at), NULL) AS latest_article_at
FROM categories c
LEFT JOIN articles a ON c.id = a.category_id
AND a.status = 1 AND a.is_deleted = 0
GROUP BY c.id
ORDER BY c.sort_order;
-- استعلام فئة وجميع فئاتها الفرعية عدد المقالات
WITH RECURSIVE category_tree AS (
SELECT id, name, parent_id
FROM categories
WHERE id = 1 -- فئة Tech
UNION ALL
SELECT c.id, c.name, c.parent_id
FROM categories c
JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT
ct.id,
ct.name,
COUNT(a.id) AS article_count
FROM category_tree ct
LEFT JOIN articles a ON a.category_id = ct.id AND a.status = 1 AND a.is_deleted = 0
GROUP BY ct.id, ct.name;
خلاصة المتابعة
-- استعلام أحدث المقالات من المستخدمين الذين أتابعهم
SELECT
a.id,
a.title,
a.slug,
a.summary,
a.view_count,
a.like_count,
a.published_at,
u.username AS author,
u.nickname AS author_nickname,
u.avatar_url AS author_avatar
FROM user_follows f
JOIN articles a ON f.following_id = a.user_id
JOIN users u ON a.user_id = u.id
WHERE f.follower_id = 2
AND a.status = 1
AND a.is_deleted = 0
ORDER BY a.published_at DESC
LIMIT 20;
الجزء 6: إنشاء العروض
-- عرض المقالات المنشورة (شائع الاستخدام، يبسط الاستعلامات)
CREATE VIEW v_published_articles AS
SELECT
a.id,
a.user_id,
a.category_id,
a.title,
a.slug,
a.summary,
a.cover_image_url,
a.status,
a.is_pinned,
a.view_count,
a.like_count,
a.comment_count,
a.published_at,
a.created_at,
u.username AS author_username,
u.nickname AS author_nickname,
u.avatar_url AS author_avatar,
c.name AS category_name,
c.slug AS category_slug
FROM articles a
JOIN users u ON a.user_id = u.id
LEFT JOIN categories c ON a.category_id = c.id
WHERE a.status = 1 AND a.is_deleted = 0;
-- استعلام باستخدام العرض
SELECT * FROM v_published_articles ORDER BY published_at DESC LIMIT 20;
SELECT * FROM v_published_articles WHERE category_id = 1 ORDER BY view_count DESC;
-- عرض ملخص وسوم المقالات
CREATE VIEW v_article_tags AS
SELECT
at2.article_id,
GROUP_CONCAT(t.name ORDER BY t.name SEPARATOR ', ') AS tag_names,
GROUP_CONCAT(t.slug ORDER BY t.name SEPARATOR ', ') AS tag_slugs,
COUNT(t.id) AS tag_count
FROM article_tag at2
JOIN tags t ON at2.tag_id = t.id
GROUP BY at2.article_id;
-- استعلام المقالات مع الوسوم
SELECT
vpa.*,
vat.tag_names,
vat.tag_count
FROM v_published_articles vpa
LEFT JOIN v_article_tags vat ON vpa.id = vat.article_id
ORDER BY vpa.published_at DESC
LIMIT 20;
الجزء 7: تحسين الفهارس
-- 1. تحسين استعلام قائمة المقالات (فهرس شامل)
-- لـ WHERE status=1 AND is_deleted=0 ORDER BY published_at DESC
CREATE INDEX idx_articles_list ON articles(status, is_deleted, published_at DESC, id, title, slug, summary);
-- 2. تحسين ترتيب المقالات الشائعة
CREATE INDEX idx_articles_hot ON articles(status, is_deleted, published_at, view_count, like_count, comment_count);
-- 3. تحسين تصفية الفئات
CREATE INDEX idx_articles_category ON articles(category_id, status, is_deleted, published_at DESC);
-- 4. تحسين قائمة مقالات المستخدم
CREATE INDEX idx_articles_user ON articles(user_id, status, is_deleted, published_at DESC);
-- 5. تحسين قائمة التعليقات
CREATE INDEX idx_comments_article ON comments(article_id, status, is_deleted, parent_id, created_at);
-- 6. تحسين علاقات المتابعة
CREATE INDEX idx_follows_following ON user_follows(following_id, follower_id);
-- 7. عرض استخدام الفهارس
SELECT
TABLE_NAME,
INDEX_NAME,
COLUMN_NAME,
CARDINALITY,
SEQ_IN_INDEX
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = 'blog_system'
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;
-- 8. تحليل الاستعلامات البطيئة
EXPLAIN SELECT * FROM v_published_articles WHERE category_id = 1 ORDER BY view_count DESC LIMIT 20;
❓ أسئلة شائعة
س: كيف تضمن اتساق البيانات للحقول المُلغاى تطبيعها (مثل article_count)؟ ج: حدّث الحقول المُلغاى تطبيعها بشكل متزامن أثناء عمليات الإنشاء/الحذف. يمكنك استخدام المحفزات للصيانة التلقائية، أو التحديث بشكل صريح في كود طبقة التطبيق. شغّل سكربتات التحقق من الاتساق بشكل دوري لتصحيح الاختلافات.
س: كيف أختار بين الحذف الناعم والحذف الفعلي؟ ج: استخدم الحذف الناعم للبيانات التجارية الأساسية (المستخدمين، المقالات، التعليقات) لسهولة الاستعادة والتدقيق. استخدم الحذف الفعلي للسجلات والبيانات المؤقتة لتوفير مساحة التخزين.
س: هل يجب تخزين محتوى المقال في قاعدة البيانات أم نظام الملفات؟ ج: النص القصير (<64KB) في MySQL TEXT/LONGTEXT كافٍ. للمحتوى الطويل جداً أو عند الحاجة لتحسين البحث النصي الكامل، فكّر في التخزين في Elasticsearch أو تخزين الكائنات، مع قاعدة البيانات التي تخزن الملخصات والفهارس فقط.
س: كيف تتعامل مع تحديثات عدد مشاهدات المقال تحت التزامن العالي؟ ج: لا تُحدّث عند كل مشاهدة. استخدم عداد Redis، ومزامنة دورية مجمعة مع قاعدة البيانات، أو استخدم قائمة رسائل للمعالجة غير المتزامنة.
📖 ملخص
طبّق هذا الدرس بشكل شامل جميع معارف SQL:
- تحليل المتطلبات: وضح المتطلبات الوظيفية وتقديرات حجم البيانات
- تصميم قاعدة البيانات: 8 جداول تغطي المستخدمين، المقالات، الفئات، الوسوم، التعليقات، الإعجابات، والمتابعات
- القيود والفهارس: المفاتيح الأساسية، القيود الفريدة، دلالات المفتاح الخارجي، تحسين الفهارس المركبة
- عمليات CRUD: إنشاء، قراءة، تحديث، حذف كامل مع الحذف الناعم وصيانة الحقول المُلغاى تطبيعها
- الاستعلامات المعقدة: التصنيفات الشائعة، لوحة الإحصائيات، التعليقات المتداخلة، ارتباطات الوسوم، خلاصة المتابعة
- العروض: تغليف منطق الاستعلام الشائع لتبسيط SQL في طبقة العمل
- تحسين الفهارس: إنشاء فهارس شاملة للاستعلامات عالية التكرار
📝 تمارين
- أضف ميزة "المفضلة" لنظام المدونة: يمكن للمستخدمين إضافة المقالات للمفضلة. صمم جدول المفضلة واكتب استعلام SQL لـ "قائمة مفضلاتي."
- أضف ميزة "سجل تصفح المقالات": سجّل المقالات التي تصفحها المستخدم مؤخراً. اكتب استعلام SQL لـ "آخر 10 مقالات تمت مشاهدتها."
- نفّذ بحثاً نصياً كاملاً: استخدم
MATCH AGAINSTأوLIKEللبحث في عناوين ومحتوى المقالات، وقارن اختلافات الأداء بين النهجين. - اكتب سكربت تحقق من اتساق البيانات لنظام المدونة: تحقق من تطابق الحقول المُلغاى تطبيعها مثل
article_countوcomment_countمع البيانات الفعلية.
تهانينا على إكمال جميع دروس برنامج تعليم SQL! استمر في التدريب والتحسن!



