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

المفاتيح الأساسية في SQLite: INTEGER وAUTOINCREMENT

كيف تعمل المفاتيح الأساسية في SQLite: المفتاح الخاص INTEGER PRIMARY KEY، والمفاتيح المركّبة، وAUTOINCREMENT، والفخاخ التي يقع فيها المبتدئون.

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

ما الذي يفعله المفتاح الأساسي فعلياً

المفتاح الأساسي في SQLite هو العمود (أو مجموعة الأعمدة) الذي يميّز كل صف في الجدول بشكل فريد. لا يمكن لصفّين أن يحملا نفس قيمة المفتاح الأساسي، وSQLite هو من يفرض هذه القاعدة عليك، كما يستعين بهذا المفتاح للوصول إلى الصفوف بسرعة.

أبسط صورة لكتابته تكون مباشرة بجانب تعريف العمود:

لم تُمرِّر قيمة id، ومع ذلك ملأها SQLite عنك. هذه ليست خدعة سحرية — إنها حالة خاصة بـ INTEGER PRIMARY KEY يستحق أن تفهمها قبل أن تكتب أي شيء آخر.

ما الذي يجعل INTEGER PRIMARY KEY مميزاً في SQLite؟

في معظم قواعد البيانات، المفتاح الأساسي ليس سوى فهرس فريد. أما في SQLite، فكل جدول عادي يحتوي أصلاً على عدد صحيح مخفي بحجم 64 بت اسمه rowid، يُستخدم داخلياً للتعرّف على الصفوف. وعندما تُعرِّف عموداً على أنه INTEGER PRIMARY KEY تماماً بهذه الصيغة، فإن هذا العمود يصبح هو الـ rowid نفسه. لا فهرس إضافي، ولا تخزين زائد — فالمعرّف الذي تتعامل معه والموقع الفعلي للصف على القرص شيء واحد.

id و rowid هما في الحقيقة عمود واحد باسمين مختلفين. عند البحث عن صف باستخدام id يصل المحرك إليه مباشرة، دون الحاجة للمرور على شجرة فهرس ثانية. ولهذا السبب، النصيحة المتعارف عليها في SQLite هي: إذا أردت مفتاحًا أساسيًا رقميًا، اكتبه بالضبط INTEGER PRIMARY KEY. ليس INT، ولا BIGINT، ولا حتى INTEGER NOT NULL PRIMARY KEY (هذا الأخير يعمل فعلًا، لكن المهم أن يكون النوع INTEGER تحديدًا).

الأنواع الأخرى تشتغل أيضًا، لكنها تُنشئ فهرسًا فريدًا منفصلًا، وهذا لا ضرر فيه، فقط أنه أقل كفاءة من حيث المساحة.

هل تحتاج فعلًا إلى AUTOINCREMENT في SQLite؟

من العادات المتوارثة من قواعد بيانات أخرى أن يكتب المطور id INTEGER PRIMARY KEY AUTOINCREMENT بشكل تلقائي. لكن في SQLite، الكلمة المفتاحية AUTOINCREMENT تقوم بمهمة أضيق بكثير مما يوحي به اسمها، وفي معظم الحالات أنت لست بحاجة إليها أصلًا.

بدون AUTOINCREMENT، يقوم العمود من نوع INTEGER PRIMARY KEY بتعبئة قيمة جديدة تساوي أكبر rowid موجود + 1. وإذا حذفت آخر صف، فقد يُعاد استخدام نفس الـ id في الإدخال التالي.

أما مع AUTOINCREMENT، فإن SQLite يحتفظ بأكبر قيمة id استُخدمت على الإطلاق في جدول جانبي اسمه sqlite_sequence، ولا يعيد استخدام أي قيمة أبدًا، حتى بعد حذف الصفوف.

الجدول plain أعاد استخدام المعرّف 3، بينما الجدول الذي يستخدم AUTOINCREMENT قفز إلى 4. ما لم يكن لديك سبب فعلي يمنعك من إعادة استخدام المعرّفات — كالتدقيق (auditing) أو وجود مراجع خارجية تبقى بعد الحذف — فاترك AUTOINCREMENT جانباً. فهو يكلّفك عملية كتابة إضافية مع كل إدخال، فضلاً عن جدول مستقل لتتبّع الترقيم.

المفتاح الأساسي المركّب في SQLite

أحياناً لا يكفي عمود واحد. خذ مثلاً جدول الربط بين المستخدمين والأدوار: ما يميّز كل صف بشكل فريد هو الزوج (user_id, role_id) معاً. في هذه الحالة، يُعرَّف المفتاح الأساسي على مستوى الجدول نفسه:

يجب أن يكون الزوج فريدًا على مستوى الجدول كله — فالزوج (1, 10) لا يمكن أن يظهر إلا مرة واحدة. أما كل عمود بمفرده فيمكن أن يتكرر بلا أي قيود. وهذه هي الفكرة بالضبط: كل مستخدم يمكن أن يحمل عدة أدوار، وكل دور يمكن أن يُسنَد لعدة مستخدمين، لكن أي ارتباط بين مستخدم ودور بعينه لا يتكرر أبدًا.

المفتاح الأساسي المركّب في SQLite يُنشئ فهرسًا مستقلًا يغطي الأعمدة المذكورة. وهو لا يصبح rowid — هذا الامتياز محصور في INTEGER PRIMARY KEY وحده.

فخّ الـ NULL في المفتاح الأساسي

إليك سلوكًا غريبًا يُربك القادمين من PostgreSQL أو MySQL: في الجداول العادية في SQLite، يمكن لأي عمود مفتاح أساسي عدا INTEGER PRIMARY KEY أن يحتوي على قيم NULL. وهذه في الأصل علّة قديمة، لكن مطوّري SQLite أبقوا عليها حفاظًا على التوافق مع الإصدارات السابقة.

تسلّل صفّان بقيم NULL رغم وجود المفتاح الأساسي. الحل هو إضافة NOT NULL صراحةً على كل عمود مفتاح أساسي ليس من نوع integer:

أو استخدم جدولاً من نوع STRICT حيث تم إصلاح علّة قبول NULL في المفتاح الأساسي. وعلى أي حال، تعويد نفسك على كتابة NOT NULL لكل عمود في المفتاح الأساسي يُعدّ تأميناً رخيص الثمن.

الفرق بين PRIMARY KEY و UNIQUE

كلاهما يمنع التكرار، لكن الفروقات بينهما كالتالي:

  • الجدول الواحد يملك مفتاحاً أساسياً واحداً على الأكثر، بينما يمكن أن يحتوي على عدد غير محدود من قيود UNIQUE.
  • المفتاح الأساسي هو المُعرِّف "الرئيسي" للجدول، والمفاتيح الخارجية (foreign keys) تشير إليه افتراضياً.
  • العمود المُعرَّف بـ INTEGER PRIMARY KEY يصبح هو الـ rowid نفسه، أما العمود الصحيح الموسوم بـ UNIQUE فلا يقوم بهذا الدور.
  • أعمدة UNIQUE تقبل عدة قيم NULL بلا مشكلة، إذ تُعامَل كل قيمة NULL على أنها مختلفة عن الأخرى.

id هو هوية الصف. صحيح أن email و username فريدان أيضاً، لكنهما خاصيتان متعلقتان بالعمل — يمكن أن يتغيّرا، أما id فلا ينبغي أن يتغيّر.

إضافة المفتاح الأساسي لاحقاً (الأفضل: لا تفعل)

أمر ALTER TABLE في SQLite محدود الإمكانيات. لا يمكنك تنفيذ ALTER TABLE ... ADD PRIMARY KEY — هذه الصيغة غير موجودة أصلاً. إذا نسيت تعريف المفتاح الأساسي وكان الجدول يحتوي على بيانات بالفعل، فالحل هو إعادة إنشاء الجدول:

هذه هي الطريقة المعتادة للترحيل (migration) في SQLite. في الكود الحقيقي، لفّ العملية داخل transaction، وعطّل المفاتيح الأجنبية مؤقتًا إذا كانت هناك جداول أخرى تشير إلى هذا الجدول. الخلاصة: اضبط المفتاح الأساسي بشكل صحيح من البداية عند CREATE TABLE.

قائمة تحقق سريعة

عندما تنشئ جدولًا جديدًا، اسأل نفسك:

  • هل لهذا الصف معرّف فريد طبيعي؟ إذا كان عددًا صحيحًا واحدًا، استخدم INTEGER PRIMARY KEY.
  • هل الهوية في الواقع مزيج من عدة أعمدة (كما في جداول الربط)؟ استخدم مفتاحًا أساسيًا مركبًا على مستوى الجدول: PRIMARY KEY (col_a, col_b).
  • هل المفتاح نصّي أو من نوع آخر غير العدد الصحيح؟ أضف NOT NULL بشكل صريح.
  • هل تحتاج فعلًا إلى AUTOINCREMENT؟ على الأرجح لا.
  • هل الجدول صغير ويُقرأ منه أكثر مما يُكتب فيه، مع مفتاح أساسي ليس عددًا صحيحًا؟ فكّر في استخدام WITHOUT ROWID (سنغطّيه مع موضوع rowid).

التالي: rowid

ظهر INTEGER PRIMARY KEY بشكل عابر بوصفه "اسمًا بديلًا لـ rowid"، لكن rowid هو الأساس الذي يقوم عليه كل جدول عادي في SQLite، ويستحق أن نفهمه عن قرب. هذا هو موضوع الصفحة التالية.

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

كيف أعرّف مفتاحًا أساسيًا في SQLite؟

أضف PRIMARY KEY بجانب العمود داخل جملة CREATE TABLE، مثل id INTEGER PRIMARY KEY. وإذا أردت مفتاحًا يمتد على أكثر من عمود، استخدم قيدًا على مستوى الجدول بهذا الشكل: PRIMARY KEY (col_a, col_b). المهم أن تكون القيمة (أو مجموعة القيم) فريدة في كل صف.

ما الفرق بين INTEGER PRIMARY KEY وبقية المفاتيح الأساسية في SQLite؟

INTEGER PRIMARY KEY له معاملة خاصة: يصبح اسمًا مرادفًا للـ rowid المدمج في الجدول، ويُخزَّن مباشرة داخل شجرة الـ B-tree دون فهرس إضافي. أما أي نوع آخر، أو أي مفتاح مركّب، فيحصل على فهرس فريد منفصل. لذلك إذا كان مفتاحك رقميًا في عمود واحد، فإن INTEGER PRIMARY KEY أسرع وأقل استهلاكًا للمساحة.

هل أحتاج إلى AUTOINCREMENT مع المفتاح الأساسي في SQLite؟

في الغالب لا. عمود INTEGER PRIMARY KEY يُسنِد قيمة rowid فريدة تلقائيًا عند إدراج NULL. أما AUTOINCREMENT فكل ما يضيفه هو ضمان عدم إعادة استخدام المعرفات بعد الحذف، مقابل جدول إضافي اسمه sqlite_sequence. لا تستخدمه إلا إذا كنت تحتاج فعلًا هذا السلوك التصاعدي.

لماذا يقبل المفتاح الأساسي في SQLite قيم NULL؟

هذا خطأ تاريخي أُبقي عليه للحفاظ على التوافق: في الجداول العادية، يمكن لعمود مفتاح أساسي من نوع غير INTEGER أن يقبل قيم NULL ما لم تُضِف صراحةً NOT NULL. الاستثناء الوحيد هو عمود INTEGER PRIMARY KEY الذي لا يسمح بـ NULL أبدًا. كحل آمن، اكتب NOT NULL على كل عمود مفتاح أساسي، أو استخدم جدولًا من نوع STRICT حيث تُطبَّق القاعدة بشكل صحيح.

Coddy programming languages illustration

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

ابدأ الآن