المصفوفات مقابل الجداول المطَبَّعة
متى تكون المصفوفات الخيار المناسب
المصفوفات مقابل الجداول المطَبَّعة درس مجاني في SQL Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في SQL Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة SQL Academy 4 دروس في المجموع.
طريقتان لتخزين قيم متعددة
عندما يحتاج صف واحد إلى احتواء عدة قيم مترابطة، يوفر لك PostgreSQL نهجين رئيسيين: تخزينها في عمود مصفوفة ضمن الصف نفسه، أو إنشاء جدول فرعي منفصل يحصل فيه كل عنصر على صف خاص به.
يُعد فهم الوقت المناسب لاستخدام كل نهج مهارة أساسية لتصميم قواعد بيانات فعّالة وسهلة الصيانة.
النهج المُطبَّع
في مخطط مُطبَّع بالكامل، توجد كل قطعة من البيانات في صف خاص بها. فإذا كان بإمكان مستخدم امتلاك عدة أرقام هاتف، تنشئ جدول user_phones يحتوي على مفتاح أجنبي يعود إلى جدول users.
هذا هو النموذج العلائقي الكلاسيكي، وهو الخيار الافتراضي في معظم الحالات.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE user_phones (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id),
phone TEXT NOT NULL
);
INSERT INTO users (name) VALUES ('Alice'), ('Bob');
INSERT INTO user_phones (user_id, phone) VALUES
(1, '+1-555-0101'),
(1, '+1-555-0102'),
(2, '+1-555-0200');نهج المصفوفات
يتيح لك النوع TEXT[] في PostgreSQL، أو أي نوع آخر متبوعًا بـ []، تخزين عدة قيم مباشرةً داخل عمود واحد. ولا تكون هناك حاجة إلى جدول إضافي.
يمكن تخزين بيانات أرقام الهواتف نفسها في صف واحد مدمج لكل مستخدم.
CREATE TABLE users_with_phones (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
phones TEXT[]
);
INSERT INTO users_with_phones (name, phones) VALUES
('Alice', ARRAY['+1-555-0101', '+1-555-0102']),
('Bob', ARRAY['+1-555-0200']);الاستعلام عن المصفوفات سهل
يُعد البحث داخل عمود مصفوفة أمرًا مباشرًا باستخدام العامل ANY أو العامل @> (يحتوي على). ويمكنك العثور على جميع المستخدمين الذين لديهم رقم هاتف محدد باستخدام جملة WHERE بسيطة.
-- Find users who have a specific phone number
SELECT name
FROM users_with_phones
WHERE '+1-555-0101' = ANY(phones);
-- Or using the array-contains operator
SELECT name
FROM users_with_phones
WHERE phones @> ARRAY['+1-555-0101'];متى تتفوق المصفوفات: عمليات البحث البسيطة
تُعد المصفوفات خيارًا مناسبًا عندما:
- تُقرأ قائمة القيم معًا كوحدة واحدة، مثل الوسوم والتسميات والفئات
- لا تحتاج مطلقًا إلى إجراء ضم على العناصر الفردية
- للقائمة حد أقصى طبيعي ونادرًا ما تُحدَّث أجزاؤها بشكل منفصل
من الأمثلة الكلاسيكية تخزين وسوم تدوينة. فأنت تجلب جميع الوسوم معًا دائمًا، ونادرًا ما تستعلم عن التدوينات حسب وسم واحد ضمن عملية ضم معقدة.
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
tags TEXT[]
);
INSERT INTO posts (title, tags) VALUES
('Intro to SQL', ARRAY['sql', 'beginner', 'database']),
('Advanced Indexes', ARRAY['sql', 'performance', 'indexes']),
('NoSQL Overview', ARRAY['nosql', 'beginner']);
-- Get all posts tagged 'beginner'
SELECT title FROM posts
WHERE 'beginner' = ANY(tags);متى تتفوق الجداول المُطبَّعة: العلاقات
تكون الجداول المُطبَّعة الخيار الأفضل عندما:
- تحتاج القيم الفردية إلى سماتها الخاصة، مثل أن يكون لرقم الهاتف نوع: المنزل/العمل
- تحتاج إلى إجراء ضم على القيم الفردية
- تتغير القيم بشكل مستقل وبوتيرة متكررة
- تحتاج إلى سلامة مرجعية عبر مفاتيح أجنبية
-- Phone numbers need a 'type' attribute — array can't do this cleanly
CREATE TABLE user_phones (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id),
phone TEXT NOT NULL,
type TEXT CHECK (type IN ('home', 'work', 'mobile'))
);
INSERT INTO user_phones (user_id, phone, type) VALUES
(1, '+1-555-0101', 'home'),
(1, '+1-555-0102', 'work');الفرق في الفهرسة
مع الجدول المُطبَّع، يمكنك إضافة فهرس B-tree قياسي على المفتاح الأجنبي أو عمود القيمة. أما مع المصفوفات، فتحتاج إلى فهرس GIN (فهرس معكوس عام) لتمكين البحث السريع داخل المصفوفة.
تعمل فهارس GIN بكفاءة، لكنها أكبر حجمًا وأبطأ في التحديث من فهارس B-tree.
-- Index for fast array element lookups
CREATE INDEX idx_posts_tags ON posts USING GIN (tags);
-- Now this query uses the index efficiently
EXPLAIN SELECT title FROM posts
WHERE tags @> ARRAY['sql'];التجميع عبر الصفوف: تفوق الجداول المُطبَّعة
عندما تحتاج إلى العد أو التجميع أو إجراء عمليات تجميع على القيم الفردية، تكون الجداول المُطبَّعة أكثر ملاءمة بكثير. أما إجراء التجميع داخل المصفوفات فيتطلب استخدام unnest()، الذي يوسّع المصفوفة إلى صفوف أولًا، أي إنه يعيد إنشاء البنية المُطبَّعة وقت تنفيذ الاستعلام.
-- Count posts per tag (array approach — needs unnest)
SELECT tag, COUNT(*) AS post_count
FROM posts, unnest(tags) AS tag
GROUP BY tag
ORDER BY post_count DESC;
-- With a normalized post_tags table this would be simpler:
-- SELECT tag, COUNT(*) FROM post_tags GROUP BY tag;تعديل عناصر المصفوفة
يتطلب تحديث عنصر واحد داخل مصفوفة أو حذفه صياغة غير مريحة؛ إذ يجب استبدال المصفوفة بأكملها أو استخدام array_remove(). أما في الجدول المُطبَّع، فما عليك سوى استخدام DELETE أو UPDATE على الصف المحدد.
-- Remove a single tag from an array column
UPDATE posts
SET tags = array_remove(tags, 'beginner')
WHERE id = 1;
-- Append a new tag
UPDATE posts
SET tags = array_append(tags, 'tutorial')
WHERE id = 1;
SELECT title, tags FROM posts WHERE id = 1;فرض القيم الصالحة
في الجدول المُطبَّع، يمكنك استخدام مفتاح أجنبي لفرض أن تأتي كل قيمة من مجموعة معروفة. ولا يمكن للمصفوفات الإشارة إلى جدول آخر، إذ لا تدعم المفاتيح الأجنبية.
إذا كنت تحتاج إلى سلامة مرجعية مضمونة لكل عنصر، فإن الجدول الفرعي هو الخيار الوحيد.
-- Normalized: only valid category IDs allowed (FK enforced)
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name TEXT UNIQUE NOT NULL
);
CREATE TABLE post_categories (
post_id INT REFERENCES posts(id),
category_id INT REFERENCES categories(id),
PRIMARY KEY (post_id, category_id)
);
-- Array: no constraint possible — any text value is accepted
-- UPDATE posts SET tags = ARRAY['totally_invalid_tag'] WHERE id = 1;دليل عملي لاتخاذ القرار
استخدم مصفوفة عندما تكون البيانات قائمة بسيطة ومسطحة، وتُقرأ دائمًا كوحدة واحدة، ولا توجد لكل عنصر سمات إضافية، ولا تكون السلامة المرجعية مطلوبة، مثل الوسوم والتسميات والكلمات المفتاحية للبحث.
استخدم جدولًا فرعيًا مُطبَّعًا عندما تكون لكل عنصر سماته الخاصة، أو تحتاج إلى إجراء ضم أو تجميع على القيم الفردية، أو تحتاج إلى مفاتيح أجنبية، أو تُحدَّث العناصر الفردية أو تُحذف كثيرًا.
-- Summary example: tags as array (good fit)
SELECT title, tags
FROM posts
WHERE tags @> ARRAY['sql']
ORDER BY title;
-- Unnest when you need row-level processing
SELECT title, unnest(tags) AS tag
FROM posts
ORDER BY title, tag;اختبار سريع
أي سيناريو يُعد الأنسب لتخزين البيانات في صورة مصفوفة PostgreSQL بدلًا من جدول فرعي مُطبَّع؟
مراجعة الدرس
تعلمت في هذا الدرس المفاضلات الأساسية بين المصفوفات والجداول المُطبَّعة في PostgreSQL.
- المصفوفات مدمجة ومريحة للقوائم المسطحة التي تُقرأ كوحدة واحدة، مثل الوسوم، لكنها لا تدعم المفاتيح الأجنبية، وتجعل تحديث العناصر بشكل منفصل غير مريح، وتتطلب فهارس GIN لإجراء عمليات بحث سريعة.
- الجداول المُطبَّعة تدعم سمات العناصر الفردية، وسلامة المفاتيح الأجنبية، والتجميع الفعّال، والتحديث البسيط على مستوى الصف، لكن ذلك يأتي على حساب الحاجة إلى عملية ضم إضافية.
- يعتمد الخيار الصحيح على طريقة الاستعلام عن البيانات وتحديثها وربطها، وليس على طريقة تخزينها فحسب.
الأسئلة الشائعة
هل درس «المصفوفات مقابل الجداول المطَبَّعة» مجاني؟
نعم — نص درس «المصفوفات مقابل الجداول المطَبَّعة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة SQL Academy، انتقل إلى CoddyKit PRO. تتضمن دورة SQL Academy 4 دروس في المجموع.
ماذا ستتعلم في «المصفوفات مقابل الجداول المطَبَّعة»؟
متى تكون المصفوفات الخيار المناسب تتمرن على SQL Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ SQL Academy؟
لا تُشترط خبرة سابقة. SQL Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «المصفوفات مقابل الجداول المطَبَّعة»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس SQL Academy هذا؟
نعم. كل درس في SQL Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- أساسيات أعمدة المصفوفات
- البحث داخل المصفوفات
- UNNEST والتجميع
- المصفوفات مقابل الجداول المطَبَّعة