Menu
flag Ar iconالعربيةdown icon
جرّب في Playground

الأعمدة المُولَّدة في SQLite: VIRTUAL مقابل STORED

شرح عملي للأعمدة المُولَّدة في SQLite: كيفية تعريفها، الفرق بين VIRTUAL وSTORED، وفهرستها لتسريع الاستعلامات.

تحتوي هذه الصفحة على محررات قابلة للتشغيل — حرّر، شغّل، وشاهد النتيجة فوراً.

العمود المُولَّد هو عمود محسوب

العمود المُولَّد (Generated Column) في SQLite هو عمود تأتي قيمته من تعبير حسابي، لا من جملة INSERT. تكتب الصيغة مرة واحدة داخل CREATE TABLE، وبعدها SQLite يتكفّل بالباقي. لا يمكنك الكتابة إلى هذا العمود إطلاقًا، وأي محاولة لذلك ستُرجع خطأ.

إليك أبسط مثال ممكن:

total لم يتم إدخاله أبداً، ومع ذلك يظهر في كل صف. فـ SQLite يعيد حسابه من price + tax في كل مرة تقرأ فيها الصف. حدِّث أيّاً من العمودين وسيتبعه total تلقائياً.

عبارة GENERATED ALWAYS AS إلزامية. وكلمة ALWAYS هي مجرد صيغة شكلية من معيار SQL — لا يوجد خيار آخر في SQLite أصلاً.

الفرق بين VIRTUAL و STORED في SQLite

كل عمود مُولَّد لا بد أن يكون من أحد نوعَين. والنوع الافتراضي هو VIRTUAL:

النموذج الذهني للموضوع:

  • VIRTUAL — لا يأخذ أي مساحة على القرص، لكنه يستهلك المعالج في كل قراءة. سهل الإضافة وسهل التعديل لاحقًا.
  • STORED — يستهلك مساحة تخزين، لكنه لا يكلّفك شيئًا إضافيًا عند القراءة. يستحق العناء عندما يكون التعبير مكلفًا حسابيًا، أو عندما يُقرأ العمود أضعاف مرات الكتابة عليه.

إن لم تكتب الكلمة المفتاحية، فالافتراضي هو VIRTUAL. وهذا هو الخيار الصحيح في معظم الحالات.

لماذا نستخدمها أصلًا؟ فهرسة القيم المُشتقّة

الميزة الأقوى في الأعمدة المُولَّدة في SQLite هي إمكانية إنشاء فهرس عليها. هذا يمنحك بحثًا سريعًا على قيم مُشتقّة دون الحاجة إلى إعادة كتابة كل استعلاماتك.

لنفترض أنك تريد البحث عن البريد الإلكتروني بصرف النظر عن حالة الأحرف:

يُغطي الفهرس النسخة المكتوبة بأحرف صغيرة. أيّ استعلام يُرشِّح على email_lower يستفيد من الفهرس مباشرةً. صحيحٌ أنّ SQLite يدعم فهارس التعابير (CREATE INDEX ... ON users(lower(email)))، لكنّ العمود المُولَّد يجعل القيمة المُشتقّة ظاهرةً كعمودٍ حقيقي يمكنك استخدامه في SELECT، والإشارة إليه داخل العروض (Views)، وإعادة استخدامه من شيفرة التطبيق.

استخراج القيم من JSON

تتألّق الأعمدة المُولَّدة في SQLite عند التعامل مع JSON. يمنحك دعم JSON المدمج في SQLite المُعامل ->> لاستخراج قيمة مفردة؛ ضَع هذا التعبير داخل عمودٍ مُولَّد، وستحصل على حقلٍ مُحدَّد النوع وقابلٍ للفهرسة فوق كتلةٍ مرنة من البيانات.

user_id و kind يبدوان كأعمدة عادية بالنسبة لاستعلاماتك، لكن البيانات الفعلية مخزَّنة داخل payload. غيّر محتوى الـ JSON، وستتحدّث قيم الأعمدة تلقائيًا. ووجود فهرس على user_id يجعل عمليات البحث سريعة جدًا.

القواعد والقيود

هناك بعض الأمور التي تفرضها SQLite، ومن الأفضل أن تعرفها قبل أن تصطدم بها:

  • يجب أن يكون التعبير حتميًا (deterministic). أي أن دوال مثل random() و datetime('now') وغيرها من الدوال غير الحتمية ممنوعة. القيمة يجب أن تكون قابلة لإعادة الحساب من بيانات نفس الصف.
  • التعبير لا يمكنه الإشارة إلا إلى أعمدة من نفس الصف. ممنوع استخدام الاستعلامات الفرعية أو دوال التجميع أو الإشارة إلى جداول أخرى.
  • لا يمكنك تنفيذ INSERT أو UPDATE على عمود مُولَّد بشكل مباشر. فمثلًا INSERT INTO products (total) VALUES (5) سيُرجع خطأ.
  • الأعمدة من نوع STORED لا يمكن إضافتها عبر ALTER TABLE ... ADD COLUMN. فقط أعمدة VIRTUAL هي التي يمكن إضافتها لاحقًا بعد إنشاء الجدول.
  • تستطيع الأعمدة المُولَّدة أن تحمل قيود NOT NULL و CHECK و UNIQUE، بل وحتى FOREIGN KEY. فهي تتصرّف مثل أي عمود آخر فيما يخصّ هذه القيود.

ولنرَ مثالًا سريعًا يوضّح قاعدة منع الكتابة المباشرة:

sqlite> INSERT INTO products (price, tax, total) VALUES (10, 1, 999);
Runtime error: cannot INSERT into generated column "total"

الحل ببساطة هو حذف العمود المُولَّد من قائمة INSERT وترك SQLite يحسبه تلقائيًا.

الفرق بين VIRTUAL و STORED في SQLite

عادةً يعتمد الاختيار بين النوعين على نسبة القراءة إلى الكتابة وعلى تكلفة التعبير المحسوب:

قواعد عامة مفيدة:

  • خلِّ VIRTUAL هو الخيار الافتراضي. ما يكلّفك شي عند الكتابة، ويفي بالغرض في معظم الحالات.
  • انتقل إلى STORED لما تحتاج تعمل فهرسة على العمود في جدول كثير الكتابة (الفهرس أصلاً يحتاج القيمة مخزّنة)، أو لما يكون التعبير مُكلِفاً فعلاً.
  • لا تضيّع وقتك في الحيرة. النوع جزء من تعريف الـ schema، لكن تقدر تحذف العمود وتعيد إنشاءه لو غيّرت رأيك — على الأقل مع VIRTUAL.

الأعمدة المُولَّدة مقابل الـ Views

في تداخل بين الاثنين: كلاهما يعرض قيماً محسوبة دون تخزينها (أحياناً). والتفريق عادةً يكون كذا:

  • العمود المُولَّد يخصّ صفّاً واحداً في جدول واحد. استعمله للاشتقاقات على مستوى الصف — تنسيق بريد إلكتروني، استخراج حقل من JSON، حساب مجموع.
  • الـ view عبارة عن استعلام محفوظ. استعمله لما تحتاج عمليات تشمل joins أو تجميع (aggregation) أو فلترة عبر عدة صفوف.

ولا مانع من الجمع بينهما. تقدر تكتب view يعمل SELECT على جدول فيه أعمدة مُولَّدة ويضمّ معه بيانات إضافية بـ join. الأعمدة المُولَّدة تشتغل في طبقة التخزين، أما الـ views فتشتغل في طبقة الاستعلام.

التالي: ATTACH DATABASE

الأعمدة المُولَّدة تتيح للجدول أن يحسب قيمه بنفسه. الصفحة الجاية تأخذنا في الاتجاه المعاكس: ربط أكثر من قاعدة بيانات SQLite في نفس الوقت عن طريق ATTACH DATABASE، بحيث يمتدّ الاستعلام الواحد عبر عدة ملفات.

الأسئلة الشائعة

ما هو العمود المُولَّد (Generated Column) في SQLite؟

هو عمود قيمته محسوبة من تعبير يعتمد على أعمدة أخرى في نفس الصف. يُعرَّف داخل CREATE TABLE باستخدام GENERATED ALWAYS AS (expression)، ولا يمكنك الكتابة إليه مباشرة — SQLite هو من يحسب قيمته تلقائيًا عند القراءة أو التخزين.

ما الفرق بين الأعمدة VIRTUAL وSTORED؟

العمود VIRTUAL يُحسب في كل عملية قراءة ولا يأخذ أي مساحة على القرص، وهو الوضع الافتراضي. أما STORED فيُحسب مرة واحدة عند الكتابة ويُخزَّن فعليًا في الملف، فتصبح القراءة أرخص والكتابة أبطأ قليلًا. كلاهما قابل للفهرسة، لكن STORED يكون الخيار الأنسب عندما يكون التعبير ثقيلًا أو القراءة أكثر بكثير من الكتابة.

هل يمكن فهرسة عمود مُولَّد في SQLite؟

نعم، يدعم CREATE INDEX الأعمدة المُولَّدة بنوعيها VIRTUAL وSTORED. وهذا في الحقيقة هو السبب الرئيسي لاستخدامها — فبإمكانك فهرسة قيمة مُشتقّة مثل lower(email) أو حقل JSON مُستخرَج باستخدام ->>، ويستفيد مُخطِّط الاستعلامات من الفهرس تلقائيًا دون الحاجة لإعادة كتابة استعلاماتك.

هل يمكن استخدام ALTER TABLE لإضافة عمود مُولَّد؟

نعم، لكن للأعمدة VIRTUAL فقط. الأمر ALTER TABLE ... ADD COLUMN ... GENERATED ALWAYS AS (...) VIRTUAL يعمل بدون مشاكل، أما إضافة عمود STORED عبر ALTER TABLE فهي غير مدعومة، وستحتاج إلى إعادة بناء الجدول كاملًا. لذا خطِّط مسبقًا إذا كنت تنوي استخدام أعمدة مخزَّنة في جداول قائمة.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن