سنُتم في هذا الدرس ما بدأناه في الدرس السابق من تعلم لكيفيّة استعمال الشروط والمعاملات المنطقيّة المتقدمة في لغة بايثون، من أجل الحصول على نتائج دقيقة من قاعدة البيانات عند استعمال مكتبة SQLAlchemy وإضافة Flask-SQLAlchemy.
التعامل مع السلاسل النصية
قد تحتاج أحيانا إلى التّحقق من أنّ سلسلة نصيّة ما تبدأ أو تنتهي بكلمة أو حرف، أو أنّ كلمة أو حرفا معيّنا يتواجد في قائمة أو سلسلة نصيّة، لذا سنتعرّف في الفقرات التّاليّة على كيفيّة فعل ذلك في لغة بايثون.
التابع startswith
يُطبّق التّابع startswith
على السّلسلة النّصيّة للتأكّد من أنّها تبدأ بمقطع معيّن، فمثلًا، سنتأكّد أنّ السّلسلة النّصيّة abdelhadi
تبدأ بالمقطع abd
:
>>> name = 'abdelhadi' >>> name.startswith('abd') True
يُمكنك تطبيق هذه الميّزة من أجل الحصول على جميع المقالات التّي يبدأ عنوانها بمقطع معيّن، فمثلا لنقل بأنّك تمتلك في مدوّنتك مقالات مُختلفة إضافة إلى مقالات تُشير إلى كل تحديث في تطبيقك لتعرض الميّزات الجديدة، ثم لنفترض زيادة حجم التطبيق بعد مدة إلى أن صارت له مُدونته المستقلة عن مُدوّنتك، سيتوجّب عليك الآن نقل المقالات المُخصّصة للتّحديثات إلى هذه المُدوّنة الجديدة، وعلى فرض أنّ تلك المقالات تبدأ بكلمة "تحديث"، فكيف يُمكنك الحصول على هذه المقالات لنقلها دون المقالات الأخرى؟
فكما ترى، تلك طريقة واحدة لاستعمال هذا التّابع، إذ يُمكنك استعمال startswith
للحصول على جميع المقالات التّي تبدأ بالمقطع، ومن ثم تنقلها إلى مكان آخر بشكل آلي.
العامل in
يُستعمل العامل in
للتّأكّد من أن سلسلة نصيّة أو قائمة في بايثون تحتوي على مقطع نصي معيّن. فمثلا، سنتأكّد من أن الحرف a
يتواجد بكلمة abcd
، وسنتأكّد في المثال الآخر من أنّ اسم المُستخدم khalid
يتواجد ضمن قائمة usernames
، بالإضافة إلى حالتين لعدم استيفاء الشّرط والحصول على القيمة المنطقيّة False
:
>>> 'a' in 'abcd' True >>> 'e' in 'abcd' False >>> usernames = ['abdelhadi', 'ahmed', 'ibrahim', 'ali', 'khalid', 'mohammed'] >>> 'khalid' in usernames True >>> 'youssuf' in usernames False
إليك استخدامًا للعامل in
:
قلنا في الفقرة السّابقة أنك تستطيع نقل جميع المقالات التّي تبدأ بالمقطع "تحديث" إلى مدونة أخرى، لكن ماذا لو كانت مقالاتنا التّي تبدأ بهذا المقطع تُشير إلى أكثر من تطبيق واحد؟ لنقل بأنّ المدونة حاليا بها تحديثات لتطبيقين، تطبيق "كلمة" وتطبيق آخر، وكلاهما يحصلان على تحديثات ومقال لكل تحديث، هذا يعني بأنّ الحصول على المقالات التّي تبدأ بالمقطع "تحديث" ستكون مزيجا بين تحديثات التّطبيقين، ولكنّنا نرغب بنقل مقالات التطبيق "كلمة" إلى مدونة "كلمة" دون المقالات الخاصّة بالتّطبيق الآخر.
نستطيع هنا مزج كل من startswith
وin
وand
للوصول إلى مُرادنا، بحيث نتأكّد من أنّ عناوين المقالات التّي سنحصل عليها تبدأ بكلمة "تحديث" وفي نفس الوقت تحتوي على اسم التّطبيق "كلمة" وإليك مثالا بسيطا:
>>> kalima_update = 'تحديث في تطبيق كلمة' >>> app_update = 'تحديث في تطبيق آخر' >>> kalima_update.startswith('تحديث') and 'كلمة' in kalima_update True >>> app_update.startswith('تحديث') and 'كلمة' in app_update False >>> app_update.startswith('تحديث') True >>> 'كلمة' in kalima_update True >>> 'كلمة' in app_update False
كما تُلاحظ، استخدمنا كلًا من startswith
مع and
وin
لنستطيع ترشيح عنوان المقال الذي يحمل تحديثا للتطبيق "كلمة" من العنوان الآخر.
هذا مجرّد مثال على كيفيّة الاستفادة من مختلف الأدوات التّي توفّرها لنا لغة بايثون وكيفيّة مزجها لحل مشاكل تحدث على الواقع، وسنرى فيما بعد كيفيّة استخدام هذه الجمل التّي تُرجع حاليا القيمتين True
وFalse
فقط لترشيح بياناتنا في قاعدة البيانات باستعمال SQLAlchemy.
التابع endswith
يُطبّق التّابع endswith
على السّلسلة النّصيّة للتأكّد من أنّ سلسلة نصيّة تنتهي بمقطع معيّن، على سبيل المثال، يُمكن أن نتأكّد من أنّ بريدا إلكترونيّا ينتهي بـexample.com
كما يلي:
>>> 'abdelhadi@example.com'.endswith('example.com') True >>> 'abdelhadi@gmail.com'.endswith('example.com') False
لاحظ أن البريد الإلكتروني الأول ينتهي بـexample.com
لذا تحقّق الشّرط وأرجع مُفسّر بايثون القيمة True
، أمّا الثّاني فينتهي بـgmail.com
وليس example.com
لذا أُرجِعّت القيمة المنطقيّة False
.
إن تساءلت عن الفائدة من هذا الأمر على الواقع، فإليك السيناريو التّالي: لنقل مثلا بأنّ خدمة example.com ستلغي خدمات بريدها الإلكتروني وسيتوجّب عليك تنبيه جميع مُستخدميك لتنصحهم بالانتقال إلى خدمة بريد إلكتروني أخرى، يُمكنك مثلا أن تستعمل هذه الطّريقة للحصول على جميع المُستخدمين الذين سجّلوا ببريد إلكتروني من هذه الخدمة، ومن ثم تستطيع تنبيههم وحدهم دون المُستخدمين الآخرين. لا شك أن هناك أمثلة كثيرة أخرى على الواقع، وستجد تحديّات ومشاكل يُمكنك حلّها بمثل هذه الأدوات التّي توفّرها لغة بايثون.
ترشيح البيانات التي نحصل عليها من قاعدة البيانات باستعمال شروط منطقية عبر مكتبة SQLAlchemy
في السّابق، كنّا نستعمل التّابع filter_by
للحصول على سجل أو عدّة سجلّات عند تَطابُقِ قيمة عمود معيّن مع القيمة المُمرّرة، فمثلا لو أردنا الحصول على مُستخدم يُطابق اسمه السّلسلة النّصيّة khalid
يُمكن أن نستعمل التّابع filter_by
مع تمرير السّلسلة النّصيّة إلى المعامل name
والذي يمثّل اسم العمود في هذه الحالة:
>>> from project.models import User >>> User.query.filter_by(name = 'khalid').first() <username: khalid | email: khalid@example.com >
في الشّيفرة أعلاه، كل ما نفعله هو طلب سجل من قاعدة البيانات في كل مكان تُساوي فيه قيمة العمود name
السّلسلة النّصيّة "khalid”. أي كأنّنا نقول: أعطني السّجل الذي يُرجع طَلبُه القيمة المنطقيّة True
عند تنفيذ الجملة التّاليّة:
User.name == 'khalid'
وبالمثل يُمكن أن تستخدم filter_by مع بقيّة الأعمدة.
يُعد التّابع fliter_by
جيدًا في حالات كثيرة كالوصول إلى مستخدم عن طريق اسمه أو بريده الإلكتروني، أو حتى مقال عبر عنوانه، لكن ماذا لو أردنا إجراء عمليّات أكثر تقدّما، مثل الحصول على السّجلات التّي تبدأ أو تنتهي بمقطع نصي معيّن أو تلك التّي تحتوي بداخلها على مقطع ما، أو الوصول إلى جميع السّجلات التّي تحقّق مجموعة من الشّروط أو بعضها؟
التابع filter
في SQLAlchemy يُمكنك استعمال التّابع filter
للحصول على نتائج تستوفي شروطا معيّنة تُمرّر إلى هذا التّابع كما يلي:
>>> Table.query.filter(Table.column == value)
لدينا ثلاثة متغيّرات يُمكنك استبدالها هنا لتتحصّل على نتيجتك المرغوبة: Table
هو اسم الصّنف المرتبط بالجدول الذي ترغب بالحصول على البيانات منه، وcolumn
يُمثّل اسم العمود، أمّا value
فتمثّل القيمة.
وكما تلاحظ، فهذا التّعبير شبيه جدّا بالتّابع filter_by
لأنّ الشّرط هو ما إذا كانت قيمة العمود تُساوي كذا. أمّا للوصول إلى نتائج متنوّعة فتستطيع استبدال العامل ==
لكتابة شروط منطقيّة أخرى، وإليك بعض الأمثلة:
Table.query.filter(Table.column == value) Table.query.filter(Table.column < value) Table.query.filter(Table.column > value) Table.query.filter(Table.column.endswith(value)) Table.query.filter(Table.column.startswith(value))
يُمكنك أن تعوض المتغيّر value
بقيمة عمود آخر من الجدول:
Table.query.filter(Table.column == Table.column2) Table.query.filter(Table.column.startswith( Table.column2))
بعد ترشيح السّجلات، يُمكنك بعد ذلك تطبيق كل من all
أو first
على النّتيجة. لاحظ في هذا المثال كيف أنّ كلّا من filter
و filter_by
يرجعان نفس النّتيجة:
>>> User.query.filter_by(name = 'khalid').first() <username: khalid | email: khalid@example.com > >>> User.query.filter(User.name == 'khalid').first() <username: khalid | email: khalid@example.com >
وكذلك يتّبع get
نفس المنهج لكنّه لا يحتاج إلى التّابع first
لأنّ المعروف أنّ المفتاح الأولي لا يُمكن أن يحمل من طرف أكثر من سجل واحد:
>>> User.query.filter_by(id = 3).first() <username: dyouri | email: dyouri@example.com > >>> User.query.get(3) <username: dyouri | email: dyouri@example.com > >>> User.query.filter(User.id == 3).first() <username: dyouri | email: dyouri@example.com >
ترشيح السجلات عن طريق in
قد تُفكّر في تطبيق العامل in
للحصول على سجلات تتواجد إحدى قيم أعمدتها في قائمة ما، لكنّ SQLAlchemy لن يقبل العامل in
الذي توفّره لنا لغة بايثون، وعليه لن تحصل على أيّة نتيجة.
يُمكنك تنفيذ التّابع in_
في SQLAlchemy مع تمرير قائمة من القيم كما يلي:
>>> User.query.filter(User.name.in_(['abdelhadi', 'user4', 'user5'])).all() [<username: abdelhadi | email: abdelhadi@example.com >, <username: user4 | email: user4@example.com >, <username: user5 | email: user5@example.com >]
لاحظ كيف طبّقنا التّابع in_
كقيمة ممرّرة للتّابع filter
:
Table.column.in_(list)
أمّا كل من startswith
و endswith
فلا تتغيّر ويُمكنك استخدامها كما وضّحت سابقا:
>>> User.query.filter(User.name.startswith('abd')).first() <username: abdelhadi | email: abdelhadi@example.com > >>> User.query.filter(User.name.endswith('lid')).first() <username: khalid | email: khalid@example.com >
تعلّمنا الآن كيفيّة الحصول على سجلّات تبدأ أو تنتهي أحد قيم أعمدتها بمقطع معيّن، لكن ماذا لو أردنا الحصول على السّجلات التي تحتوي على مقطع معيّن في أي مكان من النّص؟
توفّر لنا مكتبة SQLAlchemy طريقة للحصول على السّجلات التّي تحتوي على مقطع معيّن بغض النّظر عن مكان تواجد هذا المقطع (سواء كان في بداية السّلسلة النّصيّة أو وسطها أو نهايتها)، وذلك باستعمال contains
:
>>> User.query.filter(User.name.contains('d')).all() [<username: khalid | email: khalid@example.com >, <username: dyouri | email: dyouri@example.com >, <username: abdelhadi | email: abdelhadi@example.com >]
كما تلاحظ فقد حصلنا على جميع المُستخدمين الذين تحتوي أسماءهم على الحرف d
سواء أكان في بداية الاسم أو في آخره أو وسطه.
ترشيح السجلات بأكثر من شرط واحد
قد تود عند استعمال filter
أن تحصل على نتائج تُحقّق أكثر من شرط واحد، وعلى سجل أو سجلّات تستوفي العديد من الشّروط (مثل الحصول على كل مُستخدم انضمّ قبل شهر واحد وأضاف أكثر من 10 مقالات).
وغالبًا ستستعمل and
التّي توفّرها لغة بايثون، لكن كما الحال مع in
فمكتبة SQLAlchemy توفّر بديلا لها، والبديل هو الدّالة and_
في SQLAlchemy والتّي نصل إليها عن طريق الكائن db
. إليك مثالا لطريقة القيام بالأمر:
>>> user = User.query.filter(db.and_(condition1, condition2))
مع استبدال condition1
بالشّرط الأول و condition2
بالشّرط الثّاني.
وبما أنّ and_
شبيهة بجملة and
في لغة بايثون فالمتوقّع أنّ حالة تحقّق الشّرطين معا هي الحالة الوحيدة التي يُمكن أن تُرجع نتيجة من الجدول:
>>> True and True True >>> False and False False >>> False and True False >>> True and False False
لنفترض أنّنا نُريد الحصول على أول مُستخدم يبدأ اسمه بكلمة abd
وفي نفس الوقت يكون رقم مُعرّفه أكبر من الرّقم 1
:
>>> from project import db >>> user = User.query.filter( ... db.and_(User.name.startswith('abd'), ... User.id > 1)).first() >>> user <username: abdelhadi | email: abdelhadi@example.com >
كما تُلاحظ، حصلنا على المُستخدم عبدالهادي لأنّه يستوفي الشّرطين، فرقم المعرفّ الخاص بالمُستخدم هو 2 أي أنّه أكبر من 1، ثمّ إنّ اسم المُستخدم يبدأ بالمقطع abd
بكل تأكيد. يُمكنك كذلك استخدام filter
أكثر من مرّة لتطبيق العديد من الشّروط:
>>> user = User.query.filter( ... User.name.startswith('abd')).filter( ... User.id > 1).first() >>> user <username: abdelhadi | email: abdelhadi@example.com > >>> user.id 2
لاحظ هنا بأنّ النّتيجة هي نفسها، أي أنّ كلّا من db.and_
و استعمال filter
أكثر من مرّة له نفس المفعول وشخصيّا أرى أنّ استعمال db.and_
أفضل وأسهل.
يُمكنك كذلك استخدام الدّالّة or_
للحصول على شرط يستوفي إمّا هذا الشّرط أو ذاك، وهو شبيه بجملة or
في بايثون، وإليك تذكيرا بسيطا:
>>> True or True True >>> False or False False >>> False or True True >>> True or False True
يعني لو أردنا الحصول على مُستخدم ينتهي اسمه بالحرف d
أو رقم مُعرّفه يُساوي 10 لكتبنا استعلامنا كما يلي:
>>> user = User.query.filter( db.or_(User.name.endswith('d'), User.id == 10)).first() >>> user <username: khalid | email: khalid@example.com > >>> user.id 1
لاحظ بأنّ الشّرط الثّاني خاطئ، ومع ذلك حصلنا على المُستخدم خالد لأنّ الشّرط الأول صحيح، لو استخدمت and_
هنا لكانت النّتيجة عبارة عن القيمة None
أو بمعنى آخر لا نتيجة لهذا الاستعلام وهذا لأنّ الشّرط الثّاني خاطئ.
مزج and و or معًا
كما رأينا سابقا، يُمكنك وضع شروط معقّدة بمزج and
و or
أكثر من مرّة داخل بعضها البعض كما يلي:
>>> (True and True) or (False and True) True
الشّرط الأول في الشيفرة أعلاه ينتِج القيمة True
، والثّاني يُنتج القيمة False
، يعني كأنّنا نقول:
True or False
ومن ثم فالخرج هو القيمة المنطقيّة True
. يُمكنك أن تأخذ هذا المبدأ إلى أبعد الحدود لتكتب شروطًا أكثر تعقيدًا:
>>> ((True and True) or (False and True)) and ((True and True) and (False and True)) False >>> (True and True) or (False and True) True >>> (True and True) and (False and True) False
أعتقد بأنّني بالغت في مزج كل هذه الشّروط، لكنّني أشعر بأنّ فهم طريقة العمل هذه مُهمّة، خاصّة إن كنت تخطّط لقراءة شيفرة مشروع مفتوح المصدر.
قد لا تحتاج أبدا إلى مزج كل من or
و and
لإنشاء شروط معقّدة في SQLAlchemy، لكنّي رأيت أن أمنحك مثالا لكيفيّة القيام بالأمر، ولا بأس إن لم تفهم المثال الآن إذ أنّه يختلف بعض الشّيء عمّا تطرّقنا إليه سابقا، وستستطيع أن تفهمه ببعض من المُمارسة.
إليك مثالا على كيفيّة مزج and
و or
في SQLAlchemy:
db.and_( db.or_( condition1, condition2 ), db.and_( condition3, condition4 ) )
لاحظ بأنّك تستطيع وضع الشّروط في مُتغيّرات لتعمل الشّيفرة السّابقة، مثلا:
condition1 = User.name == 'khalid' condition2 = User.name == 'username' condition3 = User.id < 10 condition4 = User.email.endswith('example.com')
هذا كأنّنا نقول:
(condition1 or condition2) and (condition3 and condition4)
إن لاحظت بيانات المُستخدم khalid فستدرك بأنّ هذه الشّروط تتحقّق فيه. وفي الشّيفرة التّاليّة، أضع الشّروط في مُتغيّر باسم condition لأمرّرها فيما بعد إلى التّابع filter.
condition = db.and_( db.or_( User.name == 'khalid', # True User.name == 'username' # False ), db.and_( User.id < 10, # True User.email.endswith('example.com') # True ) )
لنجرّب الآن تمرير هذا الشّرط إلى مكتبة SQLAlchemy وللنَنظر هل هناك من مُستخدم يحقّق الشّروط المذكورة:
>>> User.query.filter(condition).all() [<username: khalid | email: khalid@example.com >]
جيد! كل شيء سار كما هو متوقّع، فالمُستخدم خالد هو الوحيد الذي يُحقّق جميع الشّروط.
خاتمة
تعرّفنا في هذا الدّرس على كيفيّة استعمال التابع filter
في مكتبة SQLAlchemy لترشيح السّجلات عند الحصول عليها من قاعدة البيانات باستعمال شروط منطقيّة متقدّمة. وسنتعرّف في الدرس القادم على كيفيّة استغلال ما تعلّمناه من حيل للتّعامل مع قواعد البيانات بطريقة أفضل.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.