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

كيفية استخدام الوحدة SQLite3 في لغة بايثون


محمد الخضور

تُعد SQLite قاعدة بيانات SQL قائمة بحد ذاتها self-contained، ومعتمدة على الملفات file-based، وهي مُضمّنة في بايثون افتراضيًا، إذ من الممكن استخدامها في أي من تطبيقات بايثون دون الحاجة لتثبيت أي برمجيات إضافية.

سنتعرف في هذا المقال على الوحدة sqlite3 في بايثون 3، إذ سننشئ اتصالاً مع قاعدة بيانات SQLite، كما سنضيف جدولاً إليها، وسندخل بعض البيانات إلى هذا الجدول ونقرأها ونعدّلها.

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

مستلزمات العمل

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

الخطوة الأولى – إنشاء اتصال مع قاعدة بيانات SQLite

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

يمكن الاتصال بقاعدة البيانات SQLite باستخدام الوحدة sqlite3 في بايثون على النحو التالي:

import sqlite3

connection = sqlite3.connect("aquarium.db")

حيث تستورد التعليمة import sqlite3 الوحدة sqlite3، مما يمنح برنامج بايثون وصولًا إلى هذه الوحدة، بينمّا تعيد الدالة ()sqlite3.connect كائن اتصال Connection والذي سنستخدمه في التخاطب مع قاعدة البيانات SQLite الموجودة في الملف aquarium.db الذي يُنشأ تلقائيًا من قبل الدالة ()sqlite3.connect في حال عدم وجود ملف بنفس الاسم أصلًا في الحاسوب.

يمكن التأكد من أنّ الكائن connection قد أُنشئ بنجاح عبر تشغيل الأمر التالي:

print(connection.total_changes)

وبتشغيل شيفرة بايثون السابقة، سيظهر لنا الخرج التالي:

0

إذ تمثّل سمة الكائن connection.total_changes إجمالي عدد السجلات (الأسطر) التي جرى تغييرها من قبل الكائن connection، ولكننا لم ننفّذ أي تعليمات SQL حتى الآن، ما يعني أنّ العدد 0 صحيح لعدد التغييرات الإجمالي total_changes حاليًا.

وفي حال رغبتك بإعادة خطوات هذا المقال من البداية في أي وقت، يمكنك حذف الملف "aquarium.db" من حاسوبك.

ملاحظة: من الممكن أيضًا الاتصال بقاعدة بيانات SQLite مُتصلة بالذاكرة مُباشرةً (وليس بملف) عبر تمرير السلسة النصية الخاصّة ":memory:" إلى الدالة ()sqlite3.connect كما يلي:

 sqlite3.connect(":memory:")

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

الخطوة الثانية – إضافة بيانات إلى قاعدة البيانات SQLite

الآن وبعد أن أنشأنا الاتصال مع قاعدة بيانات SQLite المُتمثلّة بالملف "aquarium.db"، أصبح من الممكن البدء بإدخال البيانات إلى قاعدة البيانات وقراءتها منها؛ إذ تُخزّن البيانات في قواعد بيانات SQLite ضمن جداول، التي تعرّف مجموعةً من الأعمدة قد تكون خالية تمامًا، أو محتوية على سجل واحد أو أكثر، بحيث يتضمّن كل سجل بيانات موافقة للأعمدة المُعرفّة في الجدول.

سننشئ جدولًا باسم "fish" يتتبع البيانات التالية:

------------------------------------------------------
|  tank_number  |   species    |  name  |
------------------------------------------------------
|            1             |     shark     | Sammy |
------------------------------------------------------
|            7             | cuttlefish |   Jamie   |
------------------------------------------------------

سيتتبع الجدول "fish" قيم الاسم name والنوع species ورقم الخزّان tank_number لكل سمكة في الحوض، وقد ضمّنا مثالين لسجلات الأسماك، الأول لسمكة من نوع قرش shark باسم Sammy، والآخر لسمكة من النوع حبّار cuttlefish باسم Jamie.

ومن الممكن إنشاء الجدول fish في SQLite اعتمادًا على الكائن connection المُنشأ في الخطوة الأولى من هذا المقال، على النحو التالي:

cursor = connection.cursor()
cursor.execute("CREATE TABLE fish (name TEXT, species TEXT, tank_number INTEGER)")

إذ تعيد الدالة ()connection.cursor في الشيفرة السابقة كائن مؤشّر Cursor، والذي يمكنّنا من تنفيذ تعليمات SQL على قاعدة البيانات SQLite باستخدام الدالة ()cursor.execute، أمّا السلسة النصية "... CREATE TABLE fish" فهي تعليمة SQL تُنشئ جدولًا باسم fish مُتضمنًا الأعمدة الثلاث التي أشرنا إليها سابقًا وهي الاسم name، الذي يحتوي بيانات من النوع النصي TEXT، والنوع species الذي يحتوي أيضًا بيانات من النوع النصي TEXT، ورقم الخزان tank_number، الذي يحتوي بيانات من نوع عدد صحيح INTEGER.

الآن وبعدما أنشأنا الجدول، أصبح من الممكن إدخال سجلات البيانات إليه:

cursor.execute("INSERT INTO fish VALUES ('Sammy', 'shark', 1)")
cursor.execute("INSERT INTO fish VALUES ('Jamie', 'cuttlefish', 7)")

استدعينا في الشيفرة السابقة الدالة ()cursor.execute مرتين، المرة الأولى بغية إدخال السجل الخاص بالسمكة القرش المسماة Sammy إلى الخزان رقم "1"، والثانية لإدخال سمكة الحبار المسمّاة Jamie إلى الخزان رقم "7"، أمّا السلسة النصية "... INSERT INTO fish VALUES" فهي تعليمة SQL مسؤولة عن إدخال السجلات إلى الجدول.

أمّا في الخطوة التالية فسنستخدم تعليمة SELECT من تعليمات SQL بغية التحقّق من السجلات المُدخلة إلى جدول الأسماك "fish".

الخطوة الثالثة – قراءة بيانات من قاعدة البيانات SQLite

أضفنا في الخطوة السابقة سجلين إلى جدول الأسماك "fish" في قاعدة البيانات SQLite، ومن الممكن جلب هذه السجلات باستخدام التعليمة SELECT من تعليمات SQL على النحو التالي:

rows = cursor.execute("SELECT name, species, tank_number FROM fish").fetchall()
print(rows)

وحال تشغيل هذه الشيفرة، سيظهر الخرج التالي:

[('Sammy', 'shark', 1), ('Jamie', 'cuttlefish', 7)]

شغّلت الدالة ()cursor.execute -في الشيفرة السابقة- تعليمة SELECT بغية جلب قيم كل من أعمدة الاسم والنوع ورقم الخزان من جدول الأسماك "fish"، لتجلب الدالة ()fetchall كافّة نتائج التعليمة SELECT، ولدى تنفيذ التعليمة (print(rows ستظهر قائمةٌ مكونةٌ من سجلين، ولكل سجل ثلاثة مُدخلات يمثّل كل منها عمود من الأعمدة التي اخترناها من جدول الأسماك، إذ يتضمّن هذان السجلان البيانات التي أدخلناها في الخطوة الثانية، بمعنى أنّنا سنحصل على سجل لسمكة القرش "Sammy"، وسجل لسمكة الحبّار "Jamie"، وفي حال كان المطلوب جلب السجلات من جدول الأسماك التي تحقّق مجموعة من المعايير المُحدّدة، فمن الممكن استخدام العبارة WHERE على النحو التالي:

target_fish_name = "Jamie"
rows = cursor.execute(
    "SELECT name, species, tank_number FROM fish WHERE name = ?",
    (target_fish_name,),
).fetchall()
print(rows)

وعند تشغيل الشيفرة، سنحصل على الخرج التالي:

[('Jamie', 'cuttlefish', 7)]

تعمل التعليمة ()cursor.execute(<SQL statement>).fetchall في المثال السابق على جلب كافّة النتائج من التعليمة SELECT، بينما تعمل العبارة WHERE في التعليمة SELECT على ترشيح السجلات لتكون فقط تلك التي تكون فيها قيمة العمود name هي target_fish_name، ومن الجدير بالملاحظة أنّه من الممكن استخدام الموضع المؤقت ? بديلًا عن المتغير target_fish_name ضمن التعليمة SELECT، ومن المتوقّع في حالتنا أن يوافق سجلًا واحدًا هذا المعيار، إذ ستكون القيمة المعادة بعد الترشيح هي سجل سمكة الحبّار "Jamie".

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

الخطوة 4 – تعديل البيانات في قاعدة بيانات SQLite

من الممكن تعديل السجلات في قاعدة البيانات SQLite باستخدام تعليمتي UPDATE و DELETE من تعليمات SQL.

لنفترض على سبيل المثال أنّه قد نُقل القرش "Sammy" إلى الخزان رقم 2، فعندها من الممكن تعديل سجل هذه السمكة في الجدول "fish" للتعبير عن هذا التغيير على النحو التالي:

new_tank_number = 2
moved_fish_name = "Sammy"
cursor.execute(
    "UPDATE fish SET tank_number = ? WHERE name = ?",
    (new_tank_number, moved_fish_name)
)

استخدمنا في الشيفرة السابقة تعليمة UPDATE من تعليمات SQL لتغيير رقم الخزان tank_number للسمكة Sammy إلى القيمة الجديدة "2"، إذ تضمن الجملة WHERE في التعليمة UPDATE أنّه لن تتغير قيمة رقم الخزان إلا عند تحقق شرط وهو أن يكون اسم السمكة هو Sammy أي "name = "Sammy، ومن الممكن التأكّد من تنفيذ التعديل بالشكل الصحيح من خلال تشغيل تعليمة SELECT التالية:

rows = cursor.execute("SELECT name, species, tank_number FROM fish").fetchall()
print(rows)

وبتشغيل الشيفرة السابقة سنحصل على الخرج التالي:

[('Sammy', 'shark', 2), ('Jamie', 'cuttlefish', 7)]

فنلاحظ أنّ السجل الخاص بالسمكة "Sammy" يملك القيمة "2" مثل رقم للخزان ضمن العمود tank_number.

الآن لنفترض أنّنا حرّرنا القرش Sammy إلى الطبيعة، وبالتالي لم يعد موجودًا في الحوض، عندها يتوجّب حذف السجل الخاص به من الجدول "fish"، لذا سنستخدم التعليمة DELETE من تعليمات SQL لحذف السجل المطلوب كما يلي:

released_fish_name = "Sammy"
cursor.execute(
    "DELETE FROM fish WHERE name = ?",
    (released_fish_name,)
)

استخدمنا في الشيفرة السابقة التعليمة DELETE من تعليمات SQL لحذف سجل السمكة Sammy من النوع shark، إذ ضمنت الجملة WHERE في التعليمة DELETE أنّه لن يُحذف السجل إلّا عند تحقق شرط كون اسم السمكة هو Sammy أي "name = "Sammy، ومن الممكن التأكّد من تنفيذ الحذف بالشكل الصحيح من خلال تشغيل تعليمة SELECT التالية:

rows = cursor.execute("SELECT name, species, tank_number FROM fish").fetchall()
print(rows)

وبتشغيل الشيفرة السابقة سنحصل على الخرج التالي:

[('Jamie', 'cuttlefish', 7)]

فنلاحظ أنّه قد حُذف فعلًا السجل الخاص بالسمكة Sammy من النوع shark، ولم يتبقَ سوى سجل سمكة Jamie من نوع الحبّار cuttlefish.

الخطوة 5 – استخدام تعليمة with للإغلاق الآلي

استخدمنا في هذا المقال كائنين رئيسين للتعامل مع قاعدة البيانات "aquarium.db" من النوع SQLite وهما: كائن اتصال باسم connection وكائن مؤشّر باسم cursor.

يتوجّب إغلاق ملفات بايثون بعد الانتهاء من العمل عليها، وكذلك الأمر بالنسبة للكائنات مثل Connection و Cursor، إذ يجب إغلاقها عند الانتهاء من استخدامها، ومن الممكن استخدام العبارة with لمساعدتنا على إغلاق الكائنات Connection و Cursor تلقائيًا على النحو التالي:

from contextlib import closing

with closing(sqlite3.connect("aquarium.db")) as connection:
    with closing(connection.cursor()) as cursor:
        rows = cursor.execute("SELECT 1").fetchall()
        print(rows)

تُعد الدالة closing من الدوال سهلة الاستخدام التي توفّرها الوحدة contextlib، فعند إنهاء التعليمة with، تضمن closing استدعاء الدالة ()close بغض النظر عن الكائن المُمرّر إليها، وفي مثالنا استخدمنا الدالة closing مرتين، الأولى لضمان الإغلاق التلقائي للكائن Connection المُعاد من الدالة ()sqlite3.connect، والثانية لضمان الإغلاق التلقائي للكائن Cursor المُعاد من الدالة ()connection.cursor، وبتشغيل الشيفرة السابقة سنحصل على الخرج التالي:

[(1,)]

وبما أنّ التعليمة "SELECT 1" هي تعليمة SQL تعيد دومًا سجلًا وحيدًا بعمود وحيد قيمتة "1"، فمن المنطقي في هذه الحالة الحصول على سجل يحتوي فقط القيمة "1" بمثابة قيمة معادة من الشيفرة.

الخاتمة

تمثّل الوحدة sqlite3 جزءًا فعّالًا من مكتبة بايثون المعيارية، إذ تمكنّنا من العمل مع قاعدة بيانات SQL محلية كاملة الميزات دون الحاجة لتثبيت أي برمجيات إضافية.

استعرضنا في هذا المقال كيفية استخدام الوحدة sqlite3 للاتصال مع قاعدة بيانات SQLite، وكيفية إضافة البيانات إليها وقراءتها منها وتعديلها، كما نوهّنا لأخطار هجمات حقن استعلامات SQL، وبيّنا كيفية استخدام contextlib.closing لاستدعاء الدالة ()close تلقائيًا وتطبيقها على كائنات بايثون الموجودة ضمن عبارات with.

ترجمة -وبتصرف- للمقال How To Use the sqlite3 Module in Python 3 لصاحبه DavidMuller.

اقرأ أيضًا


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

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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...