اذهب إلى المحتوى

تجاهل حساسية الأحرف العربية والتشكيل في أنظمة قواعد البيانات: حل مشكلة الألف بهمزة وبدونها نموذجًا


واثق الشويطر

عندما تبدأ تعلم قواعد البيانات لأول مرة، ستواجه عدة مشكلات في التعامل مع النصوص العربية، ولأنها خاصةٌ باللغة العربية لن تجد حلولًا سهلة على الإنترنت. نحاول في هذا المقال تلخيص حلول لحل هذه المشكلة الشائعة في قواعد البيانات.

هناك حروف عديدة في اللغة العربية يتعامل معها الناس أحيانًا وكأنها شيء واحد، وتتعامل معها أنظمة قواعد البيانات على أنها حروف مختلفة مثل الألف "ا" والألف ذات الهمزة.

عند إنشاء دالة بحث في موقع الويب فعليك تجاهل هذه الاختلافات، فلن يمكنك حل المشكلة عند استخدام الترميز غير الحساس لحالة الأحرف "utf8_unicode_ci"، ووفقًا لهذا الترميز لا يعد الحرفان "ا" و "أ" متساويين في حالة ربط المحارف character mapping، بل سيُعدّان مختلفان، وعند البحث عن أحدها لن يظهر الآخر. هناك ثلاثة حلول لهذه المشكلة:

سنوضح لك كيفية تطبيق هذه الحلول على MySQL. أما بالنسبة لأنظمة قواعد البيانات الأخرى، فيمكنك البحث في التوثيق عن حلول مشابهة. سيُطبّق كل حل على جدول المثال التالي:

+----+-------------- +
| id | name         |
+----+--------------+
|  1 | احمد         |
|  2 | أحمد         |
|  3 | أسامه        |
|  4 | أسامة        |
|  5 | اسامه        |
|  6 | اسَامه        |
+----+--------------+
6 rows in set

إنشاء ترميز محارف مخصص

هذا هو الحل الموصى به في معظم الحالات، إذ لن تغيّر أي بيانات في قاعدة البيانات، لكن كل ما ستفعله هو إعداد قاعدة البيانات لتتعامل مع هذه الأحرف على أنها حرف واحد.

سنعتمد في هذا على توثيق MySQL بإضافة ترميز UCA إلى مجموعة محارف يونيكود Unicode.

أولًا، سنحتاج إلى تعديل ملف MySQL خاص يحتوي على مجموعات الأحرف وإضافة مجموعتنا الجديدة هناك. اسم الملف هو "Index.xml"، ويختلف موقع الملف من نظام إلى آخر. يمكننا معرفة موقع الملف من قاعدة بيانات "information_schema"؛ وهي هي قاعدة البيانات التي تُخزّن فيها البيانات الوصفية metadata والمعلومات الخاصة بقواعد بيانات MySQL. يمكنك الاطلاع على مقال خصائص قواعد البيانات والمزايا التي تقدمها على أكاديمية حسوب لمزيدٍ من المعلومات حول خصائص قواعد البيانات وبعض المصطلحات الأساسية.

شغّل الاستعلام التالي في قاعدة بيانات "information_schema":

SHOW VARIABLES LIKE 'character_sets_dir';

من المفترض أن تحصل النتيجة التالية أو ما يشبهها:

+--------------------+----------------------------+
| Variable_name      | Value                      |
+--------------------+----------------------------+
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------+----------------------------+
1 row in set (0.00 sec)

يكون مسار الملف على نظام دبيان لينكس Debian Linux، هو "usr/share/mysql/charsets/Index.xml/".

خذ نسخة احتياطية من الملف، وافتحها، وانتقل إلى العنصر <charset name="utf8">، إذ سنضيف ترميزنا المخصص تحتها.

نحن بحاجة إلى رقم معرّف id واسم لترميزنا، إذ يجب أن يكون المعرّف ضمن النطاق المحجوز من الرقم 1024 إلى 2047 لأنه النطاق المحجوز للمستخدم، أما بالنسبة لاسم الترميز فليكن utf8_arabic_ci؛ إذ تدل ci على تجاهل حساسية الأحرف العربية، ويمكنك اختيار ما شئت من الأسماء.

لنُضِفْ الترميز إلى ملف "Index.xml"، ثم نشرح ما تعنيه قواعد الترتيب. ضِف ما يلي:

<collation name="utf8_arabic_ci" id="1029">
 <rules>
     <reset>\u0627</reset>   <!-- الألف دون همزة-ا -->
     <i>\u0623</i>           <!-- الألف بالهمزة فوقها-أ -->
     <i>\u0625</i>           <!-- الألف بالهمزة تحتها-إ -->
     <i>\u0622</i>           <!-- الألف بالمدة فوقها -آ -->
 </rules>
 <rules>
     <reset>\u0629</reset>   <!-- التاء المربوطة-ة -->
     <i>\u0647</i>           <!-- الهاء-ه -->
 </rules>
 <rules>
     <reset>\u0000</reset>   <!-- قيمة يونيكود المساوية لقيمة فارغة null -->
     <i>\u064E</i>           <!-- الفتحة -->
     <i>\u064F</i>           <!-- الضمّة -->
     <i>\u0650</i>           <!-- الكسرة -->
     <i>\u0651</i>           <!--الشدّة -->
     <i>\u064F</i>           <!-- السكون -->
     <i>\u064B</i>           <!-- الفتحتان -->
     <i>\u064C</i>           <!-- الضمتان -->
     <i>\u064D</i>           <!-- الكسرتان -->
 </rules>
</collation>

يُبلّغ هذا الجزء المضاف MySQL أن "utf8arabicci" -أو أيًا كانت تسميته- هو ابن لمجموعة محارف utf8، مضيفًا القواعد التالية:

  • 'أ'، و 'إ'، و 'آ' هن نفس حرف 'ا' (جميع أشكال الألف هي حرف واحد).
  • 'ه' هو نفس الحرف 'ة'، لذا فإن "نسمة" هي نفس "نسمه".
  • تُتجاهل أحرف التشكيل تمامًا كما لو أنها غير موجودة.

يمكنك إضافة المزيد من القواعد كما تريد.

بعد حفظ الملف، أعد تشغيل خادم MySQL لاستخدام ترتيبنا.

نكتب على نظام Debian ما يلي:

sudo service mysql restart

انتقل الآن إلى الجدول الذي يحتوي على النص العربي وغيّر ترميز الأعمدة إلى الترميز الجديد. سنفعل ذلك في جدول المثال بهذا الاستعلام:

ALTER TABLE persons
MODIFY name VARCHAR(50)
CHARACTER SET 'utf8'
COLLATE 'utf8_arabic_ci';

ملاحظة: إذا كنت تستخدم PHPMyAdmin أو واجهة رسومية مشابهة للتعامل مع MySQL، فلا تتوقع العثور على الترميز المخصص في القائمة المنسدلة للترميزات المتاحة.

سيتعين عليك كتابة استعلام مثل ما ورد أعلاه لتغيير العمود إلى الترميز الجديد.

إذا نجح الاستعلام، فيجب تعيين كل شيء إلى الترميز الجديد. لنجري استعلام بحث search query ونرn ما إذا كان يعمل كما ينبغي:

SELECT * FROM persons WHERE name = "اسامة";
+----+--------------+
| id | name         |
+----+--------------+
|  3 | أسامه        |
|  4 | أسامة        |
|  5 | اسامه        |
|  6 | اسَامه        |
+----+--------------+
4 rows in set (0.00 sec)

عُرضت أشكال الألف المختلفة (بهمزة أو بغيرها)، وكذلك التاء المربوطة والهاء، لكن مع تجاهل التشكيل (لاحظ أن الاسم الأخير في النتائج اسَامه يحتوي على تشكيل).

كان هذا هو الحل الأول، إذ لم نتطرق إلى البيانات نفسها هنا أو تعديلها، بل أُجريَت جميع التغييرات على نظام قواعد البيانات نفسه، ولكن ماذا لو لم يتح لك الوصول إلى ملفات مجموعات الأحرف، مثلًا عندما تستخدم استضافة مشتركة إذ تكون الصلاحيات مقيدة؟ أو عندما تستخدم قاعدة البيانات SQLite وليس لديك خيار لإضافة ترتيب جديد؟ هنا يأتي الحل الثاني ليفي بالغرض في هذه الحالات.

إضافة حقل موحد normalized

لا يتطلب هذا الحل تحرير ملفات الضبط، وهو مستقل عن نظام قاعدة البيانات؛ إذ يجب أن يعمل حتى لو غيرت نظام قواعد البيانات لأي سبب من الأسباب، ومع ذلك، سيتطلب الأمر إضافة عمود إضافي إلى جدولنا وبعض معالجة البيانات. الفكرة سهلة، أضِف عمودًا جديدًا واملأه بالنص العربي في حقل مُوَحَّد، ثم استخدم العمود المُوَحَّد في استعلاماتك. لنرى كيف نفعل ذلك.

سنستخدم بعض أكواد PHP في هذا المثال لإضافة العمود الذي المُوَحَّد إلى جدولنا. يمكنك إجراء دوال مماثلة بأي لغة برمجة بمجرد أن تفهم الفكرة. استفد من دالة PHP هذه:

function normalize_name($name) {
    $patterns     = array( "/إ|أ|آ/" ,"/ة/", "/َ|ً|ُ|ِ|ٍ|ٌ|ّ/" );
    $replacements = array( "ا" ,  "ه"      , ""         );
    return preg_replace($patterns, $replacements, $name);
}

ما تفعله هذه الدالة سهل جدًا، فهي تستبدل كل تاء مربوطة ة بهاء ه، وجميع أشكال الألف أإآ بألف بدون همزة ا، وتزيل التشكيل. دعونا نرى ذلك في المثال:

normalize_name("أحمد");  //‪ تعيد احمد ‪
normalize_name("آمنة");  //‪ تعيد امنه
normalize_name("أسامه"); //‪ تعيد اسامه
normalize_name("مٌحَمَّد");  //‪ تعيد محمد

حسنًا، حصلنا الآن على دالة توحيد النصوص. ستكون الخطوة التالية هي إضافة عمود جديد إلى جدولنا وملؤه بالاسم المُوَحَّد. يجب أن تملأ برمجية صغيرة بيانات العمود فلا داعي لذكرها هنا. بعد ذلك، يجب أن يكون لدينا جدول يحتوي على البيانات التالية:

1  احمد   احمد
2  أحمد   احمد
3  أسامه  اسامه
4  أسامة  اسامه
5  اسامه  اسامه
6  اسَامه  اسامه

العمود الثاني في التخطيط layout السابق هو حقل الاسم المُوَحَّد. حصلنا الآن على بياناتنا الموحّدة، فكيف نستخدمها لحل مشكلتنا؟

إذا بحث المستخدم عن اسم آسامة، فسنمرر هذا الاسم إلى دالة توحيد النص أولًا، والتي ستعيد الاسم المُوَحَّداسامه، ثم سنستعلم عن عمود normazlized_name الذي أنشأناه، ونعرض عمود الاسم الأصلي في نتائج البحث:

SELECT id, name FROM persons WHERE normalized_name = "اسامه";
+----+--------------+
| id | name         |
+----+--------------+
|  3 | أسامه        |
|  4 | أسامة        |
|  5 | اسامه        |
|  6 | اسَامه        |
+----+--------------+
4 rows in set (0.00 sec)

هذا كل ما في الأمر، إذ أدى البحث عن آسامة إلى ظهور كافة الاختلافات في الاسم.

باختصار، أضفنا الحقل الذي وحدنا نصّه إلى الجدول، ومرّرنا نص البحث إلى دالة توحيد النص، وبحثنا عن الاسم الذي المُوَحَّد، وعرضنا الاسم الأصلي.

يعد هذا حلًا أكثر معيارية وقابلية للتنفيذ في أكثر من قاعدة بيانات، ولكنه يتطلب مزيدًا من العمل.

استخدام التعابير النمطية في الاستعلامات

في هذا الحل، لن نغيّر أي ملفات ضبط، ولن نضيف أي بيانات إضافية إلى قاعدة البيانات. ومع ذلك، فإن البحث المعتمد على التعابير النمطية Regexp أبطأ من البحث العادي، وستفقد ميزة استخدام الفهارس التي قد تؤثر على الأداء تأثيرًا سيئًا. ما تزال دالة التعابير النمطية Regex مفقودة في لغة SQL القياسية، ولكن معظم أنظمة قواعد البيانات توفر الدعم لها بصيغ مختلفة.

لتطبيق هذا الحل، سنستبدل النمط القياسي "regexp" بنص البحث الأصلي.

تُنفّذ التعبيرات العادية في MySQL باستخدام REGEXP أو مرادفها RLIKE. يمكنك الاطلاع على مقال دوال التعامل مع النصوص في SQL لمعرفة المزيد حول كيفية كتابتها.

للبحث عن كافة تواجدات كلمة اسامة سنستخدم النمط التالي:

"[ا|أ|إ|آ]سام[ه|ة]"

يعني هذا النمط البحث عن أي شكل من أشكال الألف في بداية الكلمة، والبحث عن التاء المربوطة أو الهاء "ه" في نهايتها. لنجرب ذلك على جدول الأمثلة:

SELECT id, name FROM persons WHERE name REGEXP "[ا|أ|إ|آ]سام[ه|ة]";
+----+------------+
| id | name       |
+----+------------+
|  3 | أسامه      |
|  4 | أسامة      |
|  5 | اسامه      |
+----+------------+
3 rows in set (0.01 sec)

عُرضت اسامة أينما وجدت، ما عدا الذي يحتوي على التشكيل كما ذكرنا من قبل.

سيتعين علينا كتابة دالة لإنشاء هذا النمط إذا أردنا استخدام هذه الطريقة في حقول البحث. سأعطيك مثالا لذلك في PHP، ولكن هذا مجرد مثال وعلى الأرجح ستحتاج إلى طريقة مختلفة:

function generate_pattern($search_string) {
    $patterns     = array( "/(ا|إ|أ|آ)/", "/(ه|ة)/" );
    $replacements = array( "[ا|إ|أ|آ]", "[ه|ة]" );
    return preg_replace($patterns, $replacements, $search_string);
}

ستنشئ الدالة أنماطًا على النحو التالي:

generate_pattern("أسامة"); // return '[ا|إ|أ|آ]س[ا|إ|أ|آ]م[ة|ه]'
generate_pattern("أسامه"); // return '[ا|إ|أ|آ]س[ا|إ|أ|آ]م[ة|ه]'
generate_pattern("احمد");  // return '[ا|إ|أ|آ]حمد'

لاحظ في السطر الأول والثاني أن الدالة حلّت محل الألف في منتصف اسامة أيضًا. ستحتاج إلى حصرها لتستبدل الألف فقط في بداية الكلمة فقط، ولتستبدل التاء المربوطة والهاء في نهاية الكلمة فقط. سنترك الأمر لك لضبط الأنماط.

الخاتمة

تعرفنا في هذا المقال عن الطرق المتبعة لإنجاز البحث غير الحساس لحالة الأحرف العربية.

اللغة العربية لغة عظيمة وغنية جدًا، وهذا يتطلب معالجة خاصة في التخطيطات والبرمجة وقواعد البيانات، مما يخلق تحديات يتعين علينا التعامل معها نحن المبرمجون العرب.

مشاركة تجاربنا هي المفتاح لإنشاء مجتمع قوي ومفيد. ولإضافة مساهمتنا إلى مجتمع المطورين العالمي. نأمل أن تجد هذه المقالة مفيدة.

ترجمة -وبتصرُّف- للمقال Arabic Case Insensitive In Database Systems: How To Solve Alef With and Without Hamza Problem لصاحبه أحمد عصام Ahmed Essam.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...