flask_cms ترتيب السجلات والحد من عددها والحصول على سجلات بشكل عشوائي في SQLAlchemy


عبدالهادي الديوري

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

ترتيب السجلات حسب قيم عمود معين

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

>>> from project.models import User
>>> users1 = User.query.order_by("created_date").all()
>>> users2 = User.query.order_by(User.created_date).all()
>>> users1 == users2
True  

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

>>> user = User.query.order_by(User.created_date).first()
>>> user
<username: khalid | email: khalid@example.com >  

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

>>> User.query.order_by(User.id).first()
<username: khalid | email: khalid@example.com >
>>> User.query.order_by(User.name).first()
<username: abdelhadi | email: abdelhadi@example.com >
>>> User.query.order_by(User.email).first()
<username: abdelhadi | email: abdelhadi@example.com >  

لنطبّق على جدول المقالات كذلك:

>>> from project.models import Post
>>> Post.query.order_by(Post.id).first()
<title Post 1 | post 1 content >
>>> Post.query.order_by(Post.title).first()
<title another post from abdelhadi | some other content for the post >
>>> Post.query.order_by(Post.created_date).first()
<title Post 1 | post 1 content >  

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

ملاحظة: أستخدمُ التّابع first فقط لكي لا يكون الخرج كبيرا، ولو أردتَ جميع السّجلات على شكل قائمة فتستطيع استخدام التّابع all.

الترتيب عكسيّا

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

>>> User.query.order_by(User.created_date.desc()).first()  
<username: user12 | email: user12@example.com >  

لاحظ بأنّنا حصلنا على المُستخدم user12 وهو آخر مُستخدم أضيف باستعمال حلقة التّكرار في درس سابق. إن استعملت الطّريقة الأخرى في التّرتيب فتستطيع عكسه كما يلي:

User.query.order_by("created_date desc")  

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

>>> Post.query.order_by(Post.created_date.desc()).all()  
>>> User.query.order_by(User.created_date.desc()).all()  

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

تحديد الناتج بعدد معين من السجلات

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

وبما أنّ قواعد بيانات SQL تُمكّننا من الحصول على عدد معيّن من السجلّات بجملة LIMIT فمن الأفضل أن نستخدمها عوضا عن معالجة آلاف البيانات لعرض جزء صغير منها.

ولتحديد عدد معيّن من النّتائج في SQLAlchemy، استعمل التّابع limit مع تمرير عدد صحيح كمُعامل ليُمثّل عدد النّتائج، فمثلا للحصول على خمسة مقالات وخمسة مُستخدمين فقط يُمكنك استخدام التّابع limit() مع تمرير العدد 5:

>>> five_posts = Post.query.limit(5).all()  
>>> five_users = User.query.limit(5).all()  

لاحظ أنّنا نستخدم التّابع all مرة أخرى للحصول على جميع النّتائج.

سيحمل الآن كلا المُتغيّرينfive_posts و five_users خمسة سجلّات في كل متغيّر، ويُمكنك أن تصل إلى كل سجل وبياناته من خلال استخدام حلقة for كما في السّابق. كذلك يُمكنك تغيير المُعامل المُمرّر إلى التّابع limit إلى أي عدد تريده:

Post.query.limit(10).all()  
Post.query.limit(7).all()  
Post.query.limit(8).all()  
Post.query.limit(1).first()  

لعلك لاحظت أننا نحصل في آخر سطر على سجل واحد فقط، يُمكنك استعمال هذه الطّريقة إن لم تكن مُهتما بالحصول على سجل مُعيّن من قاعدة البيانات (باستعمال إمّا get أو filter_by).

مزج ما سبق لنتائج أكثر دقة

الشّيء المُميّز في قواعد بيانات SQL هو أنّها تُوفّر لنا طريقة لمزج العديد من الشّروط في استعلام واحد، فمثلًا يُمكنك إخبار قاعدة البيانات أنّك تريد الحصول على مقالات مُستخدم رقم مُعرّفه كذا، لكن بدلًا من جلب جميع مقالاته، أعطني مقالين فقط.

وتستطيع مزج التوابع في SQLAlchemy للحصول على هذه النّتيجة، فالسّطر التّالي مثلًا يستعلم عن المقالات من المُستخدم abdelhadi عن طريق رقم مُعرّفه، ثمّ يطبّق حدّا على النّتائج للحصول على سجلّين فقط:

>>> posts_from_abdelhadi = Post.query.filter_by(author_id=2).limit(2).all()  

هنا مزجنا كلّا من التّابعين filter_by وlimit، الأول للحصول على المقالات التّي يحمل عمود author_id فيها القيمة 2 (أي رقم مُعرّف المُستخدم عبد الهادي)، والثاني لحدّ النّتائج في مقالين فقط.
لاحظ أن التّابع all هو آخر ما يُضاف إلى الاستعلام للحصول على قائمة بجميع النّتائج، لذا لا تضعه والتّابع first في وسط الشّيفرة.

وهذا مثال آخر نقوم فيه بترتيب المُستخدمين حسب تاريخ إضافتهم عكسيا ثمّ نُحدّد ثلاثة نتائج.

>>> User.query.order_by(User.created_date.desc()).limit(3).all()  
[<username: user12 | email: user12@example.com >,  
 <username: user11 | email: user11@example.com >,  
 <username: user10 | email: user10@example.com >]  

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

يُمكنك مزج ما تشاء من التّوابع للحصول على نتائج أكثر دقّة من قاعدة بياناتك، لكن احترس، فمكتبة SQLAlchemy لا تسمح بتطبيق بعض التّوابع إلّا بترتيب خاص، فمثلا لا يُمكنك وضع حدّ لعدد النّتائج قبل ترتيبها، أي أنّ الشّيفرة التّاليّة خاطئة:

User.query.limit(3).order_by(User.created_date).all()  

إن جرّبت تنفيذ الشّيفرة فستحصل على خطأ يُشبه نصّه ما يلي:

sqlalchemy.exc.InvalidRequestError: Query.order_by() being called on a Query which already has LIMIT or OFFSET applied. To modify the row-limited results of a  Query, call from_self() first.  Otherwise, call order_by() before limit() or offset() are applied.  

لذا تأكّد دائما من أنّك تُطبّق التّابع limit في مكانه الصّحيح، والأغلب أنّك ستحتاج إليه في آخر الشّيفرة لتحديد عدد النّتائج بعد إجراء العمليّات الأخرى. يُمكن أن تواجهك بعض المشاكل من هذا القبيل، فإن واجهتك فاقرأ نص الخطأ أولا ثمّ ابحث عن حل له في الويب، فإن لم تجد فيمكنك طرح سؤال في قسم الأسئلة والأجوبة في الأكاديميّة، وتأكّد أن تضع نص الخطأ كاملا والجزء الذي سبّب الخطأ من شيفرتك.

الحصول على سجلات بشكل عشوائي

يُمكن أن تجد نفسك بحاجة إلى بناء تطبيق يتطلّب الحصول على سجلّات من قاعدة البيانات بشكل عشوائي، صحيح أنك تستطيع استخدام بايثون والوحدة random للقيام بالأمر، لكن تذكر قولي أن استخدام بايثون لمعالجة بيانات كبيرة فكرة سيّئة، لذا لا تقم بذلك.

الحصول على سجلّات بشكل عشوائي ممكن، لكن للأسف فالطّريقة مُختلفة في قواعد بيانات SQL المُتعدّدة، وبما أنّنا نستعمل هنا قاعدة البيانات PostgreSQL فسأشرح طريقة القيام بالأمر فيها فقط، وإن أردت الحصول على كيفيّة القيام بالأمر في قواعد البيانات الأخرى فيُمكنك الرّجوع إلى توثيق SQLAlchemy.
للحصول على سجلّات عشوائيّة في كلّ مرّة ننفّذ فيها الاستعلام في SQLAlchemy و PostgreSQL (نفس الطّريقة تعمل على SQLite كذلك) يُمكنك استعمال التّابع func المتواجد في الكائنdb الذي أنشأناه بمُساعدة إضافة Flask-SQLAlchemy ، لذا إن لم تستورده فلن تعمل الشّيفرة:

>>> Post.query.order_by(db.func.random()).first()  
<title Post from user11 | Content 11 >  

>>> Post.query.order_by(db.func.random()).first()  
<title Post from user12 | Content 12 >  

>>> Post.query.order_by(db.func.random()).first()  
<title Post from user7 | Content 7 >  

الشّيفرة الأساسيّة لترتيب السّجلات بشكل عشوائي هي باستعمال التّابع func المتواجد في الكائن db ثمّ التّابع random مع تمرير كل شيء إلى التّابع order_by كما يلي:

order_by(db.func.random())  

لاحظ بأنّنا في كل مرّة نقوم فيها بتنفيذ الشّيفرة تختلف النّتيجة بشكل غير مُتوقّع. أيضًا، لعلك لاحظت أننا نقوم هنا بتطبيق التّابع first للحصول على أول سجل، ويُمكنك قطعًا الوصول إلى جميع السّجلات عبر التّابع all، وكذلك تحديد عدد من السّجلات بالتّابع limit بعد ترتيبها عشوائيّا:

>>> User.query.order_by(db.func.random()).limit(3).all()    
[<username: user5 | email: user5@example.com >,  
 <username: abdelhadi | email: abdelhadi@example.com >,  
 <username: user11 | email: user11@example.com >]  

>>> User.query.order_by(db.func.random()).limit(3).all()    
[<username: user9 | email: user9@example.com >,  
 <username: abdelhadi | email: abdelhadi@example.com >,  
 <username: dyouri | email: dyouri@example.com >]  

>>> User.query.order_by(db.func.random()).limit(3).all()  
[<username: user8 | email: user8@example.com >,  
 <username: user5 | email: user5@example.com >,  
 <username: user6 | email: user6@example.com >]  

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

ترشيح السجلات حسب شروط منطقية

لم تعد تطبيقات الويب بسيطة كالسابق حين كان المستخدم يطلب صفحة المقال فيحصل عليها، أما الآن فقد كثرت الخيارات المنطقية في التطبيقات، فبعد أن كانت خيارات المستخدم محدودة جدًا بين الحصول على المقالات بترتيب معيّن أو مقالات كاتب محدد، صارت تطبيقات الويب تُتيح للمُستخدم حريّة كبيرة في كيفيّة حصوله على النّتائج.
فالصّفحة والنّتائج المعروضة عليها يُمكن أن تتغيّر طبقا لعدّة شروط، وهناك خيارات كثيرة للحصول على نتائج دقيقة، فمثلا يُمكن أن يطلب المُستخدم مقالات أضيفت في شهر معيّن، أو الحصول على جميع المقالات المُضافة من السّنة الفلانيّة إلى يومنا الحالي، أو كل مقال يبدأ عنوانه بكلمة "تحديث" وفي نفس الوقت يكون كاتبه المُستخدم فلان بالإضافة إلى أنّه أضيف قبل 5 أيام مع وجوب تجاوزه المئة تعليق وهكذا…، وإن نظرنا إلى آخر مثال وحلّلناه فسنجد أنّه يحتوي على العديد من الشّروط المنطقيّة:

فالمقال يبدأ بكلمة "تحديث” وكاتب المقال هو المُستخدم "فلان” وتاريخ إضافته قبل خمسة أيام وعدد تعليقاته مئة أو أكثر. لاحظ الآن كلّا من حرفي "و” و"أو” في التّحليل السّابق، هل يُذكّرك ذلك بشيء معيّن في لغة بايثون؟ الجواب هو المعاملات المنطقيّة and وor، التّي تعمل كما يلي:

and: إن تحقّق هذا الشّرط و الشّرط الآخر فالجملة صحيحة، ولو تحقّق شرط دون آخر أو لم يتحقّق أي شرط فالجملة خاطئة.

>>> 1 == 1 and 2 == 2  
True  

>>> 1 == 2 and 2 == 2  
False  

>>> 1 == 2 and 2 == 3  
False  

or: إن تحقّق هذا الشّرط أو الشّرط الآخر فالجملة صحيحة إن كانت جميع الشّروط خاطئة فالجملة خاطئة.

>>> 1 == 1 or 2 == 3  
True  

>>> (1 == 1 or 2 == 3)  
True  

>>> 1 == 2 or 2 == 3  
False  

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

يُمكنك استعمال أحدها أكثر من مرّة:

>>> 1 == 2 or 2 == 3 or 3 == 3  
>>> True  
>>> 1 == 2 or 2 == 3 or 3 == 4  
>>> False  
>>>  2 == 2 and 3 == 3 and 4 == 4  
>>>  True  

يُمكن مزج هذه الشّروط المنطقيّة الواحد داخل الآخر لتصل إلى نتائج أكثر دقّة أو نتائج تستوفي شروطا عديدة والقوانين هنا كقوانين الرّياضيّات البسيطة (ما بداخل القوسين منعزل عن بقيّة الجملة):

>>> (1 == 2 or 6 == 3)  
False  

>>> (1 == 1 and 2 == 2)  
True  

>>> (1 == 1 and 2 == 2) or 1 == 2  
True  

>>> (1 == 1 and 2 == 2) and 1 == 2  
False  

>>> (1 == 1 and 2 == 2) and (1 == 2 or 6 == 3)  
False  

>>> (1 == 1 and 2 == 2) and (1 == 2 or 6 == 6)  
True  

>>> (1 == 1 and 2 == 2) and (1 == 2 or 6 == 3)  
False  

>>> (1 == 1 and 2 == 2) or (1 == 2 or 6 == 3)  
True  

يُمكنك كذلك اختبار هذه القواعد عبر استعمال كل من القيمتين True و False فقط:

>>> True or False    
True  
>>> True and True  
True  
>>> False or False  
False  
>>> False and False  
False  

خاتمة

تعرّفنا في هذا الدّرس على كيفيّة استعمال مكتبة SQLAlchemy لترتيب السّجلات والحدّ من عددها والحصول على سجلات عشوائيّا من قاعدة البيانات، و كذلك ألقينا نظرة على كيفيّة استعمال المعامليْن and وor في لغة بايثون، واللذيْن يُعدان أساسًا سنبني عليه بقية معارفنا في الدّروس القادمة للحصول على نتائج أكثر دقّة من قاعدة البيانات، ومن ثم إتاحة إمكانية ترشيح النّتائج لتجربة استخدام أكثر أريحيّة.





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


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



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن