المخططات تتغيّر، وSQLite يسمح لك بتعديلها… في الغالب
بعد إنشاء أي جدول، ستحتاج عاجلاً أم آجلاً إلى إعادة تسميته أو إضافة عمود إليه أو حذف عمود منه أو إعادة هيكلته بالكامل. يدعم SQLite الحالات الشائعة مباشرة عبر DROP TABLE وALTER TABLE، ويوفّر حلاً موثّقاً للحالات الأخرى.
لكن هناك ملاحظة مهمة: أمر ALTER TABLE في SQLite محدود أكثر بكثير مقارنةً بـ Postgres أو MySQL. ومعرفة ما يستطيع فعله وما لا يستطيع، إلى جانب نمط إعادة البناء للحالات غير المدعومة، هي جوهر المهارة المطلوبة هنا.
أمر DROP TABLE يحذف الجدول وكل ما يتعلّق به
يقوم DROP TABLE بحذف الجدول وصفوفه وفهارسه وأي مُشغِّلات (triggers) مُعرَّفة عليه. ولا توجد طريقة للتراجع:
اختفى الجدول. ولو حاولت الاستعلام عنه الآن، فستحصل على الخطأ no such table: scratch.
وإن كنت غير متأكد من وجود الجدول أصلًا — وهذا أمر شائع في سكربتات التهيئة — فاستخدم IF EXISTS لتتجاهل العبارة العملية بصمت إن لم يكن الجدول موجودًا:
بدون IF EXISTS، ستفشل عملية الحذف الثانية وتُرجع خطأً. أما مع استخدامها، فستعمل العمليتان دون أي مشاكل.
المفاتيح الأجنبية قد تمنع تنفيذ DROP
إذا كان فرض المفاتيح الأجنبية مُفعّلاً (PRAGMA foreign_keys = ON;) وكان هناك جدول آخر يشير إلى الجدول الذي تحاول حذفه، فإن عملية الحذف ستفشل:
sqlite> PRAGMA foreign_keys = ON;
sqlite> DROP TABLE users;
Runtime error: FOREIGN KEY constraint failed
أمامك عدة خيارات: إمّا أن تحذف الجدول المُشير (child table) أولًا، أو تحذف الصفوف المرتبطة، أو تُعرِّف المفتاح الأجنبي مع ON DELETE CASCADE لحظة إنشائه. SQLite لن يتغاضى عن خرق التكامل المرجعي من تلقاء نفسه.
تعديل جدول sqlite بأمر ALTER TABLE: العمليات الأربع المتاحة
يدعم أمر ALTER TABLE في SQLite أربع عمليات لا غير:
كل واحدة من هذه التعليمات تُنفَّذ كأمر واحد. الأولى والثانية تكلفتهما شبه معدومة — كل ما تفعلانه هو تحديث المخطط (schema). أمر ADD COLUMN بدوره سريع: SQLite لا يعيد كتابة الجدول، بل يكتفي بتسجيل تعريف العمود الجديد. أما DROP COLUMN فهو الأثقل، لأن SQLite مضطر لإعادة كتابة كل صف من أجل إزالة بيانات العمود فعلياً.
إضافة عمود بقيمة افتراضية باستخدام ADD COLUMN
عند إضافة عمود جديد إلى جدول قائم، تكون قيمته NULL في جميع الصفوف، ما لم تُحدِّد له قيمة افتراضية:
الصفّان الموجودان مسبقًا سيُملآن بالقيمة 'active'. لاحظ أن القيمة الافتراضية يجب أن تكون ثابتة — SQLite ما يسمح لك تستخدم CURRENT_TIMESTAMP أو أي تعبير غير ثابت كقيمة افتراضية مع ADD COLUMN، لأنه يحتاج قيمة يقدر يطبّقها على كل الصفوف الموجودة دون الحاجة لتقييمها صفًّا صفًّا.
إذا كنت تحتاج NOT NULL بدون قيمة افتراضية، فالحل هو إضافة العمود قابلًا لقيم NULL أولًا، ثم تعبئته عبر UPDATE، وبعدها إعادة بناء الجدول لإضافة القيد. وهذا يقودنا مباشرة إلى القيود.
ما الذي لا يستطيع ALTER TABLE فعله في SQLite
أمور شغّالة في Postgres أو MySQL، لكنها لا تعمل في SQLite:
- تغيير نوع العمود (
ALTER COLUMN ... TYPE ...). - تغيير القيمة الافتراضية للعمود مباشرةً.
- إضافة أو إزالة
NOT NULLأوCHECKأوUNIQUEأوPRIMARY KEYعلى عمود موجود. - إضافة مفتاح أجنبي على عمود موجود.
- إعادة ترتيب الأعمدة.
أي محاولة لأي من هذه العمليات ستعطيك خطأ في الصياغة (syntax error)، فـ SQLite أصلًا ما عنده جملة ALTER COLUMN من الأساس. والحل الرسمي لكل هذه الحالات واحد: إعادة بناء الجدول.
نمط إعادة بناء جدول sqlite
لمّا يعجز ALTER TABLE عن تنفيذ ما تريد، الفكرة هي أن تُنشئ جدولًا جديدًا بالمخطّط (schema) المطلوب، تنسخ البيانات إليه، تحذف الجدول القديم، ثم تعيد تسمية الجديد ليأخذ مكانه. ولفّ كل هذا داخل معاملة (transaction) حتى يكون التنفيذ إما كاملًا أو لا شيء:
الآن أصبح العمود users.age من نوع integer مع قيد تحقق (check constraint)، والعمود email صار NOT NULL. والبيانات انتقلت معها دون أن تضيع.
أمور ينبغي الانتباه لها عند تطبيق هذا فعلياً:
- عطّل المفاتيح الأجنبية طوال العملية. إذا كانت هناك جداول أخرى تشير إلى جدولك، فنفّذ
PRAGMA foreign_keys = OFF;قبل بدء المعاملة، ثمPRAGMA foreign_keys = ON;بعدها. وإلا ستفشل عمليةDROP TABLE. لاحظ أن هذا الـ pragma لا يمكن تغييره داخل المعاملة، لذا اضبطه خارجها. - أعد إنشاء الفهارس والمشغّلات (triggers). حذف الجدول القديم يحذف معه فهارسه ومشغّلاته أيضاً. لذا أضفها من جديد إلى الجدول بعد إعادة التسمية.
- راجع الـ views. أي view يشير إلى الجدول سيظل محتفظاً بالاسم القديم في الـ SQL المخزّن له. لذا أعد بناء أي view يعتمد على أعمدة تغيّرت.
نمط إعادة البناء هذا طويل لكنه موثوق. وهو نفس ما تفعله أدوات الترحيل (migrations) مثل Alembic و Rails خلف الكواليس عندما تتعامل مع SQLite.
حذف أكثر من جدول في sqlite
لا توجد جملة واحدة لحذف عدة جداول دفعة واحدة — عليك تنفيذ DROP TABLE لكل جدول على حدة. ويمكنك تجميعها داخل معاملة (transaction) إذا أردت:
لفّ هذه العمليات داخل معاملة (transaction) واحدة يضمن أن العمليات الثلاث إمّا تنجح كلها وإمّا لا ينفّذ منها شيء — وهذا مفيد عند هدم جداول مرتبطة قد تتعثّر بسبب المفاتيح الأجنبية في منتصف الطريق.
الخلاصة
DROP TABLEيحذف الجدول مع فهارسه ومحفّزاته (triggers). استخدمIF EXISTSلتجعل سكربتاتك قابلة للتشغيل المتكرّر دون أخطاء.ALTER TABLEفي sqlite يقتصر على أربع عمليات فقط: إعادة تسمية الجدول، إعادة تسمية عمود، إضافة عمود، وحذف عمود.- أي شيء خارج هذه القائمة — كتغيير نوع البيانات، أو إضافة قيود جديدة، أو ربط مفتاح أجنبي بعمود قائم — يستلزم إعادة بناء الجدول داخل معاملة.
- انتبه للمفاتيح الأجنبية والفهارس والمحفّزات والـ views عند إعادة البناء؛ فهي لا تنتقل مع البيانات تلقائيًا.
الخطوة التالية: إدخال البيانات
قضيت فصلاً كاملاً مع الجداول والقيود التي تشكّلها. حان وقت ملئها بالبيانات — الفصل القادم يبدأ بـ INSERT، بما في ذلك صيغة إدراج عدة صفوف دفعة واحدة، والقيم الافتراضية، وكيف يتعامل SQLite مع عمليات الإدراج التي تتعارض مع القيود التي وضعتها.
الأسئلة الشائعة
كيف أحذف جدولاً في SQLite؟
استخدم DROP TABLE table_name;، وأضف IF EXISTS لتجنّب الخطأ إذا لم يكن الجدول موجوداً أصلاً، هكذا: DROP TABLE IF EXISTS users;. لاحظ أن حذف الجدول يحذف معه فهارسه ومشغّلاته (triggers)، وإذا كانت قيود المفاتيح الأجنبية مفعّلة فسيفشل الحذف ما دامت هناك جداول أخرى تشير إليه.
ما الذي يستطيع ALTER TABLE فعله في SQLite؟
أربع عمليات فقط لا غير: RENAME TO لإعادة تسمية الجدول، وRENAME COLUMN ... TO ... لإعادة تسمية عمود، وADD COLUMN لإضافة عمود جديد، وDROP COLUMN لحذف عمود (متاح منذ الإصدار 3.35). هذا كل شيء، فلا يمكنك تغيير نوع عمود، ولا تعديل قيمته الافتراضية مباشرة، ولا إضافة قيد (constraint) إلى عمود موجود.
كيف أغيّر نوع عمود أو قيوده في SQLite؟
لا يدعم SQLite ذلك مباشرة، والحل المعتمد هو نمط إعادة البناء: أنشئ جدولاً جديداً بالمخطط (schema) الذي تريده، ثم انسخ البيانات بـ INSERT INTO new SELECT ... FROM old، ثم DROP TABLE old، وأخيراً ALTER TABLE new RENAME TO old. والأفضل أن تضع كل ذلك داخل transaction واحدة لتكون العملية ذرّية (atomic).