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

البحث النصي الكامل في SQLite باستخدام FTS5

تعرّف على كيفية إضافة البحث النصي الكامل إلى SQLite عبر FTS5: إنشاء الجداول الافتراضية، استخدام عامل MATCH، ترتيب النتائج بـ BM25، ومزامنة الفهرس مع بياناتك.

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

لماذا لا يصلح LIKE للبحث النصي؟

لو سبق لك أن بحثت داخل نصوص في SQLite، فالغالب أنك استخدمت LIKE '%word%'. هذه الطريقة تنفع مع الجداول الصغيرة، لكنها تنهار تماماً مع الجداول الضخمة. لا يوجد فهرس يمكن الاستفادة منه هنا، فيضطر SQLite إلى مسح كل صف، وتحويله إلى أحرف صغيرة، ثم البحث عن النص الفرعي. أما حدود الكلمات وترتيب النتائج والاستعلامات متعددة الكلمات والبحث بالبادئة، فكلها أمور ستضطر إلى تنفيذها بنفسك.

وهنا يأتي دور FTS5، الحل المدمج للبحث النصي الكامل في SQLite. هو نوع من الجداول الافتراضية يحافظ على فهرس معكوس فوق أعمدتك النصية، ويفهم لغة استعلام مبسّطة، ويرتّب النتائج باستخدام خوارزمية BM25. والأجمل أنه يأتي جاهزاً مع SQLite افتراضياً، بدون الحاجة إلى تثبيت أي امتداد.

إنشاء جدول FTS5

لإنشاء جدول FTS5 تستخدم الصيغة CREATE VIRTUAL TABLE ... USING fts5(...)، مع تحديد الأعمدة النصية التي تريد فهرستها:

ثلاثة أمور تستحق الانتباه هنا. الأعمدة بلا أنواع بيانات — فـ FTS5 يتعامل مع كل شيء كنص. المُعامل MATCH يُكتب أمام اسم الجدول (posts MATCH ...)، لا أمام اسم عمود معيّن. كما أن البحث غير حسّاس لحالة الأحرف ويعتمد على التقطيع (tokenization)، ولذلك يعثر 'sqlite' على SQLite في أي صف.

لغة الاستعلام في MATCH

لا يقتصر MATCH على كلمة واحدة فحسب، بل لسلسلة الاستعلام قواعدها الصغيرة الخاصة بها:

وظيفة كل واحد من هذه الاستعلامات:

  • 'fts5 AND prefix' — لا بد من ظهور الكلمتين معًا (بأي ترتيب وفي أي موضع داخل الصف).
  • '"keep fts"' — عبارة حرفية مطابقة بنفس الترتيب.
  • 'trig*' — بحث بالبادئة، يطابق trigger وtriggers وtrigonometry...
  • 'index NOT trigger' — يحتوي على index ولا يحتوي على trigger.

كذلك يمكنك حصر البحث في عمود محدد عبر الصيغة column:term، مثل 'title:sqlite'. وتدعم القواعد الكاملة الأقواس للتجميع وOR للبدائل — نفس البنية المعتادة في أي محرك بحث.

ترتيب النتائج باستخدام BM25

افتراضيًا، يُضيف FTS5 عمودًا مخفيًا اسمه rank إلى كل صف، وهو درجة الصلة المحسوبة بخوارزمية BM25 — كلما صغرت القيمة كانت النتيجة أكثر صلة. رتّب النتائج تصاعديًا حسب هذا العمود لتحصل على الأكثر صلة أولًا:

هل تريد إعطاء بعض الأعمدة وزنًا أكبر من غيرها؟ مرّر الأوزان إلى bm25() بحيث يكون لكل عمود وزنه الخاص حسب ترتيب التعريف:

المنشور الأول يتصدّر النتائج لأن كلمة sqlite ظهرت في title (الذي يحمل وزن 10×) بدلاً من body فقط (الذي وزنه 1×). اختَر الأوزان بما يتناسب مع طريقة الترتيب التي يحتاجها تطبيقك فعلاً.

مزامنة الفهرس مع البيانات الأصلية

أبسط شكل لجدول FTS5 هو أن يحتفظ بنسخته الخاصة من النصوص. هذا مناسب لبيانات السجلات (logs) التي تُضاف إليها الصفوف فقط، لكن معظم التطبيقات لديها جدول حقيقي بالفعل، وتريد من FTS أن يتتبّعه. الحل الأنظف هنا هو استخدام جدول FTS من نوع external content مع ثلاثة triggers.

content='articles' معناها إنّك تطلب من FTS5 ألّا يُخزّن النص بنفسه، وإنما يجلبه من جدول articles عند الحاجة. أمّا المحفّزات (triggers) فدورها أن تعكس عمليات الكتابة داخل فهرس البحث. بهذه الطريقة يصبح articles هو المصدر الموثوق للبيانات، ويبقى articles_fts مجرّد بنية بحث ملحقة به.

الصياغة الغريبة INSERT INTO articles_fts(articles_fts, ...) VALUES ('delete', ...) ليست عملية إدخال عادية، بل هي الصيغة الأمريّة الخاصّة بـ FTS5 لإخبار الفهرس بحذف صف معيّن.

مقتطفات النتائج وتمييز الكلمات (snippet و highlight في fts5)

في الغالب، نتائج البحث تحتاج إلى معاينة قصيرة مع إبراز الكلمات المطابقة داخلها. ولهذا الغرض يوفّر FTS5 دالّتين:

  • highlight(table, column_index, open, close) تُرجع نص العمود كاملًا مع تغليف الكلمات المطابقة بالعلامتين اللتين تحدّدهما.
  • snippet(table, column_index, open, close, ellipsis, token_count) تُرجع مقتطفًا قصيرًا متمحورًا حول موضع التطابق.

ترقيم الأعمدة يبدأ من الصفر حسب ترتيب التعريف. هاتان الدالتان هما اللبنة الأساسية لإبراز "الكلمات المطابقة باللون الأصفر" التي تحتاجها أي واجهة بحث.

مزالق ينبغي الانتباه إليها

هناك تفاصيل تُربك كثيرًا من المطوّرين عند العمل مع البحث النصي الكامل في SQLite:

  • MATCH يعمل فقط على جداول FTS. لا يمكنك استخدام MATCH على عمود في جدول عادي. إن أردت البحث داخل جدول موجود مسبقًا، فاستعمل نمط external content fts5 الذي شرحناه أعلاه.
  • لا تنسَ الترتيب حسب rank. بدونه يُعيد FTS5 الصفوف بترتيب التخزين، وهو ترتيب لا علاقة له بمدى مطابقة النتيجة لاستعلامك.
  • اختيار المُجزّئ (Tokenizer) مهم. المُجزّئ الافتراضي (unicode61) يقسّم النص عند حدود الكلمات بمعيار Unicode ويحوّله إلى أحرف صغيرة. وإن أردت التجذير (Stemming) بحيث تطابق كلمة run كلمة running، فاستعمل مُجزّئ porter هكذا: USING fts5(body, tokenize='porter').
  • FTS5 ليس محرّكًا متسامحًا مع الأخطاء الإملائية. هو يدعم بحث بالبادئة fts5 وليس البحث التقريبي (Fuzzy). إن احتجت إلى ميزة "هل تقصد...؟" فعليك بناؤها كطبقة فوق FTS5.
  • الجداول بدون محتوى (content='') أصغر حجمًا لكنها ناقصة. يمكنك البحث فيها لكن لا تستطيع استرجاع النص الأصلي منها — فقط rowid. مفيدة حين يكون النص الأصلي مخزّنًا في مكان آخر.

التالي: دوال النافذة (Window Functions)

غطّينا في هذه الصفحة البحث النصي الكامل في SQLite عبر FTS5. الصفحة التالية تتناول نوعًا مختلفًا من الاستعلامات المتقدّمة، وهو دوال النافذة التي تُتيح لك حساب المجاميع التراكمية والترتيبات والتحليلات لكل مجموعة، دون أن تنهار صفوفك في تجميعات (Aggregates).

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

ما هو FTS5 في SQLite؟

FTS5 هو امتداد البحث النصي الكامل المدمج في SQLite. تنشئ جدولاً افتراضياً خاصاً عبر CREATE VIRTUAL TABLE ... USING fts5(...) ثم تستعلم منه باستخدام عامل MATCH. يقوم الامتداد بتقسيم النص إلى رموز عند الإدراج، ويخزّنه في فهرس معكوس، ويرتّب النتائج افتراضياً بخوارزمية BM25.

ما الفرق بين MATCH و LIKE في SQLite؟

LIKE يقوم بمسح خطي للسلسلة الفرعية ولا يأخذ حدود الكلمات بعين الاعتبار، بينما MATCH يستفيد من الفهرس المعكوس في FTS5، فيكون سريعاً جداً مع الجداول الكبيرة، ويفهم الرموز اللغوية، وبحث البادئة (term*)، والعوامل المنطقية (AND وOR وNOT)، والبحث عن العبارات الكاملة ("exact phrase"). لاحظ أن MATCH يعمل فقط مع الجداول الافتراضية لـ FTS.

كيف أبقي فهرس FTS5 متزامناً مع جدول عادي؟

لديك خياران: إما استخدام جدول FTS5 من نوع contentless أو external-content يشير إلى جدولك الأصلي، أو إنشاء مشغّلات (triggers) من نوع AFTER INSERT وAFTER UPDATE وAFTER DELETE لتعكس التغييرات داخل جدول FTS. ميزة نمط external-content (content='posts') أنه يجنّبك تخزين النص مرتين.

كيف أرتّب نتائج البحث النصي في SQLite؟

يوفّر FTS5 عموداً خفياً اسمه rank يُعيد قيمة BM25 (كلما قلّت كانت النتيجة أفضل). رتّب مباشرة بـ ORDER BY rank، أو استخدم bm25(table) للحصول على القيمة صراحةً. يمكنك أيضاً تمرير أوزان للأعمدة مثل bm25(posts, 10.0, 1.0) لإعطاء العنوان وزناً أكبر من المحتوى.

Coddy programming languages illustration

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

ابدأ الآن