العروض (Views)

العروض (Views) في SQL هي جداول افتراضية (Virtual Tables) تُنشأ من نتائج استعلام محفوظ. يمكنك التعامل مع العرض كأنه جدول عادي في معظم الحالات، لكنه في الواقع لا يخزن البيانات بنفسه، بل يعرض البيانات من الجداول الأساسية بناءً على الاستعلام المحدد. العروض هي أداة قوية جداً لتبسيط الاستعلامات المعقدة، تحسين الأمان، وإخفاء تعقيد قاعدة البيانات عن المستخدمين. في هذا الدرس الشامل والمفصل من سلسلة تعلم لغة SQL باللغة العربية، سنتعلم كل شيء عن العروض: ما هي، كيف تعمل، كيفية إنشائها وتعديلها وحذفها، الفرق بين العروض العادية والعروض المادية، فوائدها وعيوبها، ومتى تستخدمها مع أمثلة عملية تطبيقية مفصلة.

1. ما هو العرض (View) في قواعد البيانات؟

العرض (View) هو استعلام SQL محفوظ يُعامل كجدول افتراضي. عندما تستعلم من عرض، يتم تنفيذ الاستعلام الأساسي المحفوظ في العرض، وتُعرض النتائج كأنها جدول عادي. العرض لا يخزن البيانات فعلياً (في معظم الحالات)، بل يعرض البيانات من الجداول الأساسية في كل مرة تستعلم منه.

لفهم العروض بشكل أفضل، تخيل أنك تدير شركة ولديك جدول ضخم يحتوي على معلومات الموظفين: الاسم، الراتب، القسم، تاريخ التوظيف، رقم الهاتف، العنوان، وغيرها. قسم الموارد البشرية يحتاج لرؤية جميع هذه المعلومات، لكن المديرين في الأقسام المختلفة يحتاجون فقط لرؤية أسماء الموظفين في أقسامهم دون الرواتب. بدلاً من إنشاء جداول منفصلة أو كتابة استعلامات معقدة في كل مرة، يمكنك إنشاء عرض يعرض فقط المعلومات المطلوبة لكل مجموعة.

العروض تعمل كطبقة تجريد (Abstraction Layer) بين المستخدمين والجداول الفعلية. هذا يعني أن المستخدمين يتعاملون مع بنية بسيطة ومفهومة (العرض)، بينما يمكن أن تكون البنية الفعلية للجداول معقدة جداً. إذا تغيرت بنية الجداول الأساسية، يمكنك تعديل العرض دون التأثير على التطبيقات التي تستخدمه.

2. إنشاء العروض: CREATE VIEW

إنشاء عرض في SQL بسيط جداً باستخدام أمر CREATE VIEW.

الصيغة الأساسية
الصيغة الأساسية لإنشاء عرض
CREATE VIEW view_name AS
SELECT column1, column2, ...
FROM table_name
WHERE condition;
مثال بسيط: عرض الموظفين النشطين
مثال 1: عرض بسيط للموظفين النشطين
-- الجدول الأساسي
CREATE TABLE employees (
    employee_id INT PRIMARY KEY,
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    email VARCHAR(100),
    salary DECIMAL(10, 2),
    department VARCHAR(50),
    is_active BOOLEAN DEFAULT TRUE
);

-- إنشاء عرض للموظفين النشطين فقط
CREATE VIEW active_employees AS
SELECT 
    employee_id,
    first_name,
    last_name,
    email,
    department
FROM employees
WHERE is_active = TRUE;

الآن يمكنك الاستعلام من العرض كأنه جدول عادي:

الاستعلام من العرض
-- استعلام بسيط من العرض
SELECT * FROM active_employees;

-- استعلام مع شرط إضافي
SELECT * FROM active_employees WHERE department = 'IT';

-- استعلام مع ترتيب
SELECT * FROM active_employees ORDER BY last_name;

3. عروض معقدة مع JOIN و GROUP BY

القوة الحقيقية للعروض تظهر عند التعامل مع استعلامات معقدة تجمع بيانات من عدة جداول.

مثال: عرض يجمع بيانات من عدة جداول
عرض معقد مع JOIN
-- الجداول الأساسية
CREATE TABLE departments (
    department_id INT PRIMARY KEY,
    department_name VARCHAR(100),
    location VARCHAR(100)
);

CREATE TABLE employees (
    employee_id INT PRIMARY KEY,
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    department_id INT,
    salary DECIMAL(10, 2),
    hire_date DATE,
    FOREIGN KEY (department_id) REFERENCES departments(department_id)
);

-- عرض يجمع معلومات الموظفين مع أقسامهم
CREATE VIEW employee_details AS
SELECT 
    e.employee_id,
    CONCAT(e.first_name, ' ', e.last_name) AS full_name,
    e.salary,
    e.hire_date,
    d.department_name,
    d.location,
    YEAR(CURRENT_DATE) - YEAR(e.hire_date) AS years_of_service
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id;

الآن بدلاً من كتابة استعلام JOIN معقد في كل مرة، يمكنك ببساطة:

استعلام بسيط من عرض معقد
-- بدلاً من كتابة JOIN في كل مرة
SELECT * FROM employee_details WHERE department_name = 'IT';

-- الموظفون الأقدم
SELECT * FROM employee_details WHERE years_of_service > 5;

-- الموظفون في الرياض
SELECT * FROM employee_details WHERE location = 'الرياض';
مثال: عرض مع GROUP BY للإحصائيات
عرض للإحصائيات حسب القسم
-- عرض يعرض إحصائيات كل قسم
CREATE VIEW department_statistics AS
SELECT 
    d.department_name,
    COUNT(e.employee_id) AS total_employees,
    AVG(e.salary) AS average_salary,
    MAX(e.salary) AS highest_salary,
    MIN(e.salary) AS lowest_salary,
    SUM(e.salary) AS total_payroll
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_id, d.department_name;
استخدام عرض الإحصائيات
-- عرض جميع الإحصائيات
SELECT * FROM department_statistics;

-- الأقسام ذات الرواتب الأعلى
SELECT * FROM department_statistics ORDER BY average_salary DESC;

-- الأقسام الكبيرة (أكثر من 10 موظفين)
SELECT * FROM department_statistics WHERE total_employees > 10;

4. فوائد استخدام العروض

1. تبسيط الاستعلامات المعقدة

بدلاً من كتابة استعلامات معقدة بـ JOIN و GROUP BY في كل مرة، تكتبها مرة واحدة في العرض، ثم تستعلم من العرض ببساطة.

2. تحسين الأمان (Security)

يمكنك إخفاء أعمدة حساسة (مثل الرواتب) عن مستخدمين معينين من خلال منحهم صلاحية الوصول للعرض فقط وليس للجدول الأساسي.

عرض للأمان - إخفاء الرواتب
-- عرض للمديرين (بدون رواتب)
CREATE VIEW employees_for_managers AS
SELECT 
    employee_id,
    first_name,
    last_name,
    email,
    department,
    hire_date
FROM employees;

-- منح صلاحية للمديرين على العرض فقط
GRANT SELECT ON employees_for_managers TO manager_role;
-- لا يمكنهم رؤية جدول employees الأساسي الذي يحتوي على الرواتب
3. الاستقلالية عن البنية الفعلية (Data Independence)

إذا تغيرت بنية الجداول الأساسية، يمكنك تعديل العرض دون التأثير على التطبيقات التي تستخدمه.

4. إعادة الاستخدام (Reusability)

بدلاً من تكرار نفس الاستعلام المعقد في أماكن متعددة، تنشئه مرة واحدة كعرض وتستخدمه في أي مكان.

5. تنظيم أفضل للكود

العروض تجعل قاعدة البيانات أكثر تنظيماً وسهولة في الفهم والصيانة.

5. تعديل وحذف العروض

تعديل عرض موجود: ALTER VIEW
تعديل عرض موجود
-- في MySQL و PostgreSQL
ALTER VIEW active_employees AS
SELECT 
    employee_id,
    first_name,
    last_name,
    email,
    department,
    hire_date  -- إضافة عمود جديد
FROM employees
WHERE is_active = TRUE;
استبدال عرض: CREATE OR REPLACE VIEW
إنشاء أو استبدال عرض
-- إذا كان العرض موجوداً، يتم استبداله. وإلا، يتم إنشاؤه
CREATE OR REPLACE VIEW active_employees AS
SELECT 
    employee_id,
    CONCAT(first_name, ' ', last_name) AS full_name,
    email,
    department
FROM employees
WHERE is_active = TRUE;
حذف عرض: DROP VIEW
حذف عرض
-- حذف عرض
DROP VIEW active_employees;

-- حذف آمن (لا يعطي خطأ إذا لم يكن موجوداً)
DROP VIEW IF EXISTS active_employees;

-- حذف عدة عروض
DROP VIEW IF EXISTS view1, view2, view3;
عرض قائمة العروض
عرض جميع العروض في قاعدة البيانات
-- في MySQL
SHOW FULL TABLES WHERE Table_type = 'VIEW';

-- أو
SELECT TABLE_NAME 
FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = 'your_database_name';

-- عرض تعريف العرض
SHOW CREATE VIEW active_employees;

6. العروض القابلة للتحديث (Updatable Views)

في بعض الحالات، يمكنك تحديث البيانات من خلال العرض، وسيتم تحديث الجداول الأساسية تلقائياً. لكن هناك شروط معينة يجب توفرها.

شروط العرض القابل للتحديث
  • العرض يجب أن يعتمد على جدول واحد فقط (لا JOIN)
  • لا يحتوي على DISTINCT
  • لا يحتوي على GROUP BY أو HAVING
  • لا يحتوي على دوال تجميعية (SUM, COUNT, AVG, etc.)
  • لا يحتوي على UNION
مثال: عرض قابل للتحديث
-- عرض بسيط قابل للتحديث
CREATE VIEW it_employees AS
SELECT 
    employee_id,
    first_name,
    last_name,
    email,
    salary
FROM employees
WHERE department = 'IT';

-- يمكنك التحديث من خلال العرض
UPDATE it_employees 
SET salary = salary * 1.10 
WHERE employee_id = 123;

-- سيتم تحديث جدول employees الأساسي

-- يمكنك أيضاً الإدراج
INSERT INTO it_employees (first_name, last_name, email, salary)
VALUES ('أحمد', 'محمد', 'ahmed@company.com', 5000);

-- ويمكنك الحذف
DELETE FROM it_employees WHERE employee_id = 456;
تحذير مهم:

كن حذراً عند التحديث من خلال العروض. في المثال أعلاه، إذا قمت بتحديث قسم موظف من IT إلى HR، سيختفي من العرض لكنه سيبقى في الجدول الأساسي. استخدم WITH CHECK OPTION لمنع هذا.

استخدام WITH CHECK OPTION
-- عرض مع حماية
CREATE VIEW it_employees AS
SELECT 
    employee_id,
    first_name,
    last_name,
    email,
    department,
    salary
FROM employees
WHERE department = 'IT'
WITH CHECK OPTION;

-- الآن، هذا سيفشل لأنه يحاول تغيير القسم إلى غير IT
UPDATE it_employees SET department = 'HR' WHERE employee_id = 123;
-- Error: CHECK OPTION failed

7. العروض المادية (Materialized Views)

العروض العادية لا تخزن البيانات، بل تنفذ الاستعلام في كل مرة. العروض المادية (Materialized Views) تخزن نتائج الاستعلام فعلياً على القرص، مما يجعلها أسرع بكثير، لكنها تحتاج تحديثاً دورياً.

الميزة View عادي Materialized View
تخزين البيانات لا يخزن (افتراضي) يخزن البيانات فعلياً
السرعة ينفذ الاستعلام في كل مرة سريع جداً (قراءة مباشرة)
البيانات دائماً محدثة قد تكون قديمة
المساحة لا يستهلك مساحة يستهلك مساحة تخزينية
التحديث تلقائي يدوي أو مجدول
Materialized View في PostgreSQL
-- إنشاء عرض مادي (PostgreSQL)
CREATE MATERIALIZED VIEW sales_summary AS
SELECT 
    DATE(order_date) AS sale_date,
    COUNT(*) AS total_orders,
    SUM(total_amount) AS total_sales,
    AVG(total_amount) AS average_order
FROM orders
GROUP BY DATE(order_date);

-- الاستعلام من العرض المادي (سريع جداً)
SELECT * FROM sales_summary WHERE sale_date >= '2024-01-01';

-- تحديث العرض المادي (يدوياً)
REFRESH MATERIALIZED VIEW sales_summary;

-- تحديث متزامن (لا يقفل العرض)
REFRESH MATERIALIZED VIEW CONCURRENTLY sales_summary;

ملاحظة: MySQL لا يدعم Materialized Views بشكل مباشر، لكن يمكنك محاكاتها باستخدام جداول عادية وتحديثها بشكل دوري باستخدام Events أو Triggers.

8. مثال عملي شامل: نظام تجارة إلكترونية

لنطبق ما تعلمناه في مثال واقعي متكامل لنظام تجارة إلكترونية.

الجداول الأساسية
CREATE TABLE customers (
    customer_id INT PRIMARY KEY AUTO_INCREMENT,
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    email VARCHAR(100),
    city VARCHAR(50),
    country VARCHAR(50)
);

CREATE TABLE products (
    product_id INT PRIMARY KEY AUTO_INCREMENT,
    product_name VARCHAR(200),
    category VARCHAR(50),
    price DECIMAL(10, 2),
    stock_quantity INT
);

CREATE TABLE orders (
    order_id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT,
    order_date DATETIME,
    status VARCHAR(20),
    total_amount DECIMAL(10, 2),
    FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);

CREATE TABLE order_items (
    order_item_id INT PRIMARY KEY AUTO_INCREMENT,
    order_id INT,
    product_id INT,
    quantity INT,
    unit_price DECIMAL(10, 2),
    FOREIGN KEY (order_id) REFERENCES orders(order_id),
    FOREIGN KEY (product_id) REFERENCES products(product_id)
);
العروض المفيدة للنظام
عروض متعددة للنظام
-- 1. عرض تفاصيل الطلبات الكاملة
CREATE VIEW order_details AS
SELECT 
    o.order_id,
    CONCAT(c.first_name, ' ', c.last_name) AS customer_name,
    c.email,
    c.city,
    o.order_date,
    o.status,
    o.total_amount,
    COUNT(oi.order_item_id) AS total_items
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
LEFT JOIN order_items oi ON o.order_id = oi.order_id
GROUP BY o.order_id, c.first_name, c.last_name, c.email, c.city, o.order_date, o.status, o.total_amount;

-- 2. عرض المنتجات الأكثر مبيعاً
CREATE VIEW top_selling_products AS
SELECT 
    p.product_id,
    p.product_name,
    p.category,
    p.price,
    SUM(oi.quantity) AS total_sold,
    COUNT(DISTINCT oi.order_id) AS number_of_orders,
    SUM(oi.quantity * oi.unit_price) AS total_revenue
FROM products p
JOIN order_items oi ON p.product_id = oi.product_id
GROUP BY p.product_id, p.product_name, p.category, p.price
ORDER BY total_sold DESC;

-- 3. عرض إحصائيات العملاء
CREATE VIEW customer_statistics AS
SELECT 
    c.customer_id,
    CONCAT(c.first_name, ' ', c.last_name) AS customer_name,
    c.email,
    c.city,
    COUNT(o.order_id) AS total_orders,
    SUM(o.total_amount) AS total_spent,
    AVG(o.total_amount) AS average_order_value,
    MAX(o.order_date) AS last_order_date
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.first_name, c.last_name, c.email, c.city;

-- 4. عرض المنتجات التي تحتاج إعادة تخزين
CREATE VIEW low_stock_products AS
SELECT 
    product_id,
    product_name,
    category,
    stock_quantity,
    price
FROM products
WHERE stock_quantity < 10
ORDER BY stock_quantity ASC;
استخدام العروض
استعلامات بسيطة من العروض المعقدة
-- عرض جميع الطلبات المعلقة
SELECT * FROM order_details WHERE status = 'pending';

-- أفضل 10 منتجات مبيعاً
SELECT * FROM top_selling_products LIMIT 10;

-- العملاء الأكثر إنفاقاً
SELECT * FROM customer_statistics ORDER BY total_spent DESC LIMIT 20;

-- المنتجات التي تحتاج طلب فوري
SELECT * FROM low_stock_products WHERE stock_quantity < 5;

9. أفضل الممارسات للعروض

افعل (DO)
  • استخدم أسماء واضحة للعروض
  • وثق الغرض من كل عرض
  • استخدم العروض لتبسيط الاستعلامات المعقدة
  • استخدم العروض لتحسين الأمان
  • اختبر أداء العروض
  • استخدم Materialized Views للبيانات الكبيرة
لا تفعل (DON'T)
  • لا تنشئ عروضاً على عروض (متداخلة كثيراً)
  • لا تستخدم SELECT * في العروض
  • لا تنسَ تحديث Materialized Views
  • لا تستخدم العروض لجداول صغيرة بسيطة
  • لا تعتمد على العروض القابلة للتحديث بكثرة
ملخص الدرس

في هذا الدرس الشامل، تعلمنا كل شيء عن العروض (Views) في SQL:

  • ما هي العروض وكيف تعمل كجداول افتراضية
  • إنشاء عروض بسيطة ومعقدة مع JOIN و GROUP BY
  • فوائد العروض: التبسيط، الأمان، إعادة الاستخدام
  • تعديل وحذف العروض
  • العروض القابلة للتحديث و WITH CHECK OPTION
  • الفرق بين العروض العادية والمادية
  • أمثلة عملية من نظام تجارة إلكترونية
  • أفضل الممارسات

العروض هي أداة قوية جداً لتنظيم قاعدة البيانات وتبسيط العمل مع البيانات المعقدة. استخدمها بحكمة لجعل قاعدة بياناتك أكثر أماناً وسهولة في الاستخدام.

الخطوة التالية: الإجراءات المخزنة (Stored Procedures)

أكمل رحلتك التعليمية وانتقل إلى الدرس التالي لتعلم الإجراءات المخزنة (Stored Procedures) وتطوير مهاراتك في قواعد البيانات.

الانتقال إلى الدرس التالي
المحرر الذكي

اكتب الكود وشاهد النتيجة فوراً

جرب الآن مجاناً
قناة ديف عربي

تابع أحدث الدروس والتحديثات مباشرة على واتساب

انضم الآن