توقّف عن قراءة النظريات. ابنِ تطبيقاً حقيقياً يعمل بقاعدة بيانات SQLite و Python — من الصفر إلى تطبيق جاهز.
إن كنتَ قد قضيتَ الساعة الماضية تبحث عن "شرح SQLite مع Python"، فلا بد أنك لاحظت أمراً مزعجاً: معظم
الشروحات تعلّمك الأوامر بشكل منفصل ومجرّد. تجد CREATE TABLE
هنا، وINSERT INTO
هناك — وفي النهاية تخرج من المقالة وأنت لا تعرف كيف تستخدم SQLite في شيء حقيقي.
لهذا السبب أكتب هذا الشرح بطريقة مختلفة. خلال الـ 40 دقيقة القادمة، سنبني معاً تطبيق متتبع نفقات شخصية — تطبيق Python صغير لكنه متكامل، يحفظ مصاريفك اليومية، يستعلم عنها بذكاء، ويحميك من أكثر الأخطاء التي يقع فيها المبتدئون. في النهاية، سيكون لديك شيء حقيقي يمكنك تشغيله على جهازك، وليس مجرد أكواد متفرقة.
سأتجاوز الحشو النظري وأركّز على ما يهم فعلاً في عام 2026: الطريقة الحديثة لاستخدام مكتبة
sqlite3
التي تأتي مدمجة مع Python اليوم، استخدام الاستعلامات المُعَلَّمة (Parameterized Queries) من السطر الأول
وليس كنصيحة أخيرة، ومعالجة الأخطاء بشكل صحيح، والعادات الصغيرة التي تفصل بين كود هاوٍ وبرنامج موثوق.
أنا مصطفى أمان، وفي مدونة وادي التكنولوجيا أكتب أدلة تقنية وبرمجية عملية مبنية على مشاريع حقيقية. افتح الطرفية (Terminal) ولنبدأ العمل.
لماذا SQLite (ولماذا مكتبة Python المدمجة كافية تماماً)؟
قبل كتابة أي سطر من الكود، دعنا نوضّح أمراً مهماً. SQLite ليست قاعدة بيانات "خفيفة" أو "محدودة" كما يظن البعض من اسمها — إنها القاعدة التي تشغّل تطبيق جهات الاتصال في هاتفك، وسجل تصفح متصفحك، ومعظم تطبيقات سطح المكتب التي تستخدمها يومياً، وعدداً مفاجئاً من خدمات الويب الحقيقية. هي قاعدة البيانات الأكثر انتشاراً في العالم، وتتميّز بشيء لا تستطيع معظم القواعد الأخرى فعله: إنها تعيش بالكامل داخل ملف واحد يمكنك نسخه أو إرساله بالبريد أو نسخه احتياطياً.
بالنسبة لمشروع متتبع النفقات، SQLite هي الخيار المثالي للأسباب التالية:
- لا حاجة لخادم ولا إعدادات. لا حاويات Docker، ولا منافذ (Ports) لإدارتها. مجرد ملف.
- تأتي مدمجة مع Python. مكتبة
sqlite3موجودة في المكتبة القياسية لـ Python منذ إصدار 2.5. لا تحتاج لتثبيت أي شيء. - قابلة للتوسع أكثر مما تظن. SQLite تتعامل مع قواعد بيانات حجمها يصل إلى 281 تيرابايت وملايين الصفوف بسلاسة. للمشاريع الشخصية بل وحتى لكثير من حالات الإنتاج، لن تحتاج شيئاً غيرها.
- تعلّمك SQL الحقيقي. المهارات التي تبنيها هنا تنتقل بنسبة 100% إلى PostgreSQL أو MySQL عندما تحتاج للتوسع لاحقاً.
pip install pysqlite3.
لا تفعل ذلك. حزمة pysqlite3
هي نسخة معدّلة (Fork) لاستخدامات متخصصة جداً (مثل تشغيل إصدار SQLite أحدث من المُدمج مع Python). لكل ما
سنفعله هنا، مكتبة sqlite3
المدمجة هي ما تحتاجه بالضبط — وتثبيت pysqlite3 بجانبها قد يسبب تعارضات في الاستيراد محيّرة.
إذا كنت لا تزال تحتار بين قواعد البيانات لمشروع مستقبلي، ألقِ نظرة على مقارنتنا الشاملة بين MySQL و PostgreSQL و SQLite — ستوفّر عليك اختيار الأداة الخاطئة.
تجهيز بيئة العمل (5 دقائق فقط)
إليك خطوات التجهيز كاملة. افتح الطرفية (Terminal) وتأكّد من تثبيت Python:
Bash / Terminal
python --version # الناتج المتوقع: Python 3.13.x أو أحدث
في عام 2026، الإصدار 3.13 من Python هو الإصدار المستقر الأساسي، والإصدار 3.14 هو الأحدث. أي إصدار أعلى
من 3.10 سيعمل بشكل ممتاز مع هذا الشرح. إذا كنت على إصدار أقدم، حدّثه — مكتبة
sqlite3
تحسّنت كثيراً في الإصدارات الأخيرة.
الآن لنتأكّد من أن SQLite متاحة. في الصدفة التفاعلية لـ Python:
Python
import sqlite3 print(sqlite3.sqlite_version) # إصدار محرّك SQLite نفسه print(sqlite3.version_info) # إصدار مكتبة الربط في Python
sqlite3.version
تم إيقافها (Deprecated) وحُذفت تماماً في Python 3.14. استخدم دائماً
sqlite3.sqlite_version
لمعرفة إصدار المحرّك. الشروحات القديمة التي لا تزال تستخدم sqlite3.version
ستفشل على إصدارات Python الحديثة — وهذه إشارة جيدة على أن تلك الشروحات قديمة.
وهذا كل شيء بخصوص التجهيز. لا حاجة لـ pip install.
أنشئ مجلداً باسم expense_tracker
وضع داخله ملفاً اسمه tracker.py —
هذا المكان الذي سيعيش فيه كل ما سنكتبه اليوم.
المشروع: تطبيق متتبع نفقات شخصية
دعني أصف ما سنبنيه حتى يصبح الكود مفهوماً مع كل خطوة. متتبع النفقات الذي سنبنيه سيقوم بـ:
- تخزين كل نفقة مع المبلغ، الفئة، وصف، والتاريخ.
- إضافة نفقات جديدة بسرعة من الطرفية.
- عرض الإجماليات حسب الفئة (إلى أين تذهب أموالك فعلاً؟).
- تصفية النفقات حسب نطاق التاريخ — مفيد لمراجعات نهاية الشهر.
- تعديل أو حذف النفقات إذا أخطأت في إدخال شيء.
في النهاية، سيكون لديك أساس قوي لأداة إدارة مالية شخصية حقيقية يمكنك توسيعها لاحقاً برسوم بيانية، أو تصدير لـ Excel، أو حتى واجهة ويب بسيطة. هذا هو مخطط قاعدة البيانات الذي سنستخدمه:
| العمود | النوع | الوظيفة |
|---|---|---|
| id | INTEGER PRIMARY KEY | معرّف فريد، يزيد تلقائياً عبر SQLite |
| amount | REAL NOT NULL | قيمة المبلغ المُنفَق (مثلاً 12.50) |
| category | TEXT NOT NULL | طعام، مواصلات، فواتير، إلخ |
| description | TEXT | ملاحظة اختيارية حول النفقة |
| spent_on | TEXT NOT NULL | التاريخ بصيغة ISO (YYYY-MM-DD) |
الخطوة 1: الاتصال بقاعدة البيانات بالطريقة الحديثة
لنبدأ بأكثر التفاصيل التي تتجاهلها شروحات المبتدئين: كيف نفتح ونغلق اتصال قاعدة البيانات بأمان. الطريقة الكسولة تبدو هكذا:
Python — لا تفعل هذا
conn = sqlite3.connect("expenses.db")
cur = conn.cursor()
# ... نفّذ بعض العمليات ...
conn.commit()
conn.close()
أين المشكلة؟ إذا حدث أي خطأ بين connect()
و close()،
فإن الاتصال يبقى مفتوحاً، وقد يبقى ملف القاعدة مقفلاً، وتغييراتك قد لا تُحفظ. صدّقني، صحّحتُ هذه المشكلة
بالتحديد مرات أكثر مما أرغب في الاعتراف به. الحل هو استخدام عبارة
with،
التي تضمن الـ commit عند النجاح والـ rollback عند الفشل:
Python — افعل هذا بدلاً منه
import sqlite3
from contextlib import contextmanager
DB_PATH = "expenses.db"
@contextmanager
def get_connection():
"""يُرجع اتصال SQLite مع commit تلقائي عند النجاح، rollback عند الخطأ."""
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row # الوصول للأعمدة بالاسم وليس بالرقم
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
أمران يستحقّان الانتباه هنا. أولاً،
conn.row_factory = sqlite3.Row
سطر بسيط لكن أثره ضخم — يسمح لك بالوصول للأعمدة باسمها
(row["amount"])
بدلاً من رقم العمود. ستشكر نفسك مستقبلاً عندما يكون لديك عشرة أعمدة وتحاول تذكّر ترتيبها.
ثانياً، الـ rollback في كتلة except
هو ما يجعل الكود آمناً. إذا فشل أحد الـ inserts في منتصف عملية مجمّعة، لن تنتهي بقاعدة بيانات نصف مكتوبة.
هذا النوع من الموثوقية الصغيرة هو ما يصنع الفرق على المدى البعيد.
الخطوة 2: إنشاء جدول البيانات
الآن سنضيف دالة لإنشاء الجدول إذا لم يكن موجوداً. عبارة
IF NOT EXISTS
مهمة جداً — تعني أنه يمكننا استدعاء هذه الدالة في كل مرة يبدأ فيها التطبيق دون أخطاء، والجدول يُنشأ فقط
في المرة الأولى:
Python
SCHEMA_SQL = """
CREATE TABLE IF NOT EXISTS expenses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
amount REAL NOT NULL CHECK (amount > 0),
category TEXT NOT NULL,
description TEXT,
spent_on TEXT NOT NULL DEFAULT (date('now'))
);
CREATE INDEX IF NOT EXISTS idx_expenses_date ON expenses (spent_on);
CREATE INDEX IF NOT EXISTS idx_expenses_category ON expenses (category);
"""
def init_db():
with get_connection() as conn:
conn.executescript(SCHEMA_SQL)
print("قاعدة البيانات جاهزة.")
ثلاث تفاصيل تُغفلها شروحات المبتدئين دائماً:
CHECK (amount > 0)— قيد يرفض البيانات غير المنطقية على مستوى قاعدة البيانات نفسها. لن تستطيع أبداً إدخال نفقة بقيمة سالبة حتى لو كان في كود Python خطأ ما.DEFAULT (date('now'))— إذا لم تحدّد التاريخ، تضع SQLite تاريخ اليوم تلقائياً. عمل أقل عليك.- الفهرسان (Indexes) — بدونهما، الاستعلامات التي تصفّي حسب التاريخ أو الفئة ستضطر لمسح الجدول كاملاً. مع الفهارس، تظل الاستعلامات سريعة حتى مع آلاف النفقات. الفهارس هي أكبر عامل أداء في أي قاعدة بيانات.
categories),
يجب تشغيل أمر PRAGMA foreign_keys = ON;
على كل اتصال جديد. هذا الأمر يوقع كثيراً من المطورين في فخ — احفظه جانباً.
الخطوة 3: إدراج البيانات بأمان (الاستعلامات المُعَلَّمة من البداية)
إليك أهم قاعدة في كتابة SQL بأي لغة برمجة: لا تبنِ الاستعلامات أبداً عبر تنسيق النصوص (string formatting) أو f-strings. أبداً. الطريقة الخاطئة تبدو هكذا:
Python — خطر، لا تفعل هذا أبداً
# SQL injection ينتظر أن يحدث
cur.execute(f"INSERT INTO expenses (category) VALUES ('{user_input}')")
إذا احتوى user_input
على '); DROP TABLE expenses;--،
فقد تم حذف بياناتك للتو. هذه ليست مشكلة نظرية — إنها أكثر الثغرات الأمنية شيوعاً على الويب. الطريقة
الصحيحة هي استخدام علامات الاستبدال (Placeholders) ودَع SQLite تعالج الإفلات (Escaping):
Python — آمن ونظيف
def add_expense(amount, category, description=None, spent_on=None):
"""إدراج نفقة جديدة. يُرجع معرّف الصف الجديد."""
sql = """
INSERT INTO expenses (amount, category, description, spent_on)
VALUES (?, ?, ?, COALESCE(?, date('now')))
"""
with get_connection() as conn:
cur = conn.execute(sql, (amount, category, description, spent_on))
return cur.lastrowid
# مثال: إضافة بعض النفقات
add_expense(50.00, "طعام", "غداء")
add_expense(25.00, "مواصلات", "تذكرة مترو")
add_expense(450.00, "فواتير", "كهرباء الشهر")
علامات الاستفهام ?
هي الطريقة الآمنة. كل واحدة منها يتم استبدالها بالقيمة المقابلة من المجموعة (Tuple)، بعد إفلاتها بالكامل.
هذه ليست مجرد مسألة أمان — إنها أيضاً أسرع، لأن SQLite تستطيع تخزين خطة الاستعلام المُجمَّعة مسبقاً
وإعادة استخدامها لعمليات إدراج متعددة.
إدراج صفوف متعددة دفعة واحدة
إذا كنت تستورد قائمة نفقات (من ملف CSV مثلاً)، استخدم
executemany.
هي أسرع بشكل كبير من حلقة Python عادية لأنها تُرسِل بياناً واحداً مُجَمَّعاً وتترك SQLite تتعامل مع التكرار:
Python
def add_many(rows):
"""إدراج نفقات متعددة دفعة واحدة. `rows` قائمة من tuples."""
sql = """
INSERT INTO expenses (amount, category, description, spent_on)
VALUES (?, ?, ?, COALESCE(?, date('now')))
"""
with get_connection() as conn:
conn.executemany(sql, rows)
# إدراج 100 صف في معاملة واحدة — أسرع بكثير من 100 استدعاء منفصل
batch = [
(15.50, "طعام", "قهوة", "2026-04-01"),
(35.00, "مواصلات", "تاكسي", "2026-04-02"),
(120.30, "طعام", "بقالة", None),
# ... 97 صفاً إضافياً
]
add_many(batch)
الخطوة 4: قراءة البيانات وتصفيتها
الآن يأتي الجزء الممتع — استخراج إجابات حقيقية من بياناتك. سنكتب ثلاث دوال للاستعلام: واحدة لعرض أحدث النفقات، وأخرى لتلخيص الإنفاق حسب الفئة، وثالثة للتصفية حسب نطاق التاريخ.
Python
def list_recent(limit=10):
"""يُرجع أحدث النفقات كقائمة من Row objects."""
sql = """
SELECT id, amount, category, description, spent_on
FROM expenses
ORDER BY spent_on DESC, id DESC
LIMIT ?
"""
with get_connection() as conn:
return conn.execute(sql, (limit,)).fetchall()
def total_by_category():
"""يُرجع [(category, total, count), ...] مرتّبة من الأكبر إنفاقاً."""
sql = """
SELECT category,
ROUND(SUM(amount), 2) AS total,
COUNT(*) AS num_transactions
FROM expenses
GROUP BY category
ORDER BY total DESC
"""
with get_connection() as conn:
return conn.execute(sql).fetchall()
def expenses_between(start_date, end_date):
"""يُرجع كل النفقات ضمن نطاق تاريخي شامل (نصوص ISO 'YYYY-MM-DD')."""
sql = """
SELECT * FROM expenses
WHERE spent_on BETWEEN ? AND ?
ORDER BY spent_on
"""
with get_connection() as conn:
return conn.execute(sql, (start_date, end_date)).fetchall()
استخدامها يصبح في غاية النظافة:
Python
print("أعلى فئات الإنفاق هذا الشهر:")
for row in total_by_category():
print(f" {row['category']:<12} {row['total']:>8.2f} ({row['num_transactions']} عملية)")
# نموذج للناتج:
# فواتير 1240.50 (4 عمليات)
# طعام 587.30 (22 عملية)
# مواصلات 295.00 (8 عمليات)
لاحظ كيف يعمل row['category']
بفضل الـ row_factory
الذي أعددناه في الخطوة 1. لو نسيت ذلك، لكنت محصوراً مع
row[0]،
row[1]،
وهكذا — وداعاً للكود القابل للقراءة.
fetchall()
فقط عندما تعرف أن النتيجة صغيرة. للجداول التي تحتوي ملايين الصفوف، اعمل تكراراً بحلقة
for row in cur:
بدلاً من ذلك — SQLite تتدفّق بالصفوف صفاً تلو الآخر وتُبقي استخدام الذاكرة ثابتاً مهما كان حجم البيانات.
الخطوة 5: التعديل والحذف (بحذر شديد)
عمليات التعديل (UPDATE) والحذف (DELETE) هي حيث يصنع المبتدئون أكبر الكوارث. القاعدة الواحدة التي ستوفّر عليك مئات الصداع: ضع دائماً جملة WHERE. بدونها، عملية UPDATE ستُعيد كتابة كل صف في الجدول، وعملية DELETE ستمسح كل شيء. SQLite لن تطلب منك تأكيداً.
Python
def update_amount(expense_id, new_amount):
"""تعديل مبلغ نفقة واحدة. يُرجع True إذا تم تعديل صف."""
sql = "UPDATE expenses SET amount = ? WHERE id = ?"
with get_connection() as conn:
cur = conn.execute(sql, (new_amount, expense_id))
return cur.rowcount > 0
def delete_expense(expense_id):
"""حذف نفقة بالمعرّف. يُرجع True إذا حذف فعلاً شيئاً."""
sql = "DELETE FROM expenses WHERE id = ?"
with get_connection() as conn:
cur = conn.execute(sql, (expense_id,))
return cur.rowcount > 0
# الاستخدام مع تحقق الأمان
if not update_amount(7, 14.75):
print("لا توجد نفقة بمعرّف id=7. لم يحدث أي تغيير.")
التحقق من
cur.rowcount
شيء ستحتاجه في كل دوال التعديل والحذف تقريباً. يُخبرك ما إذا كانت العملية أثّرت فعلاً على شيء. بدونه،
قد ينجح كودك ظاهرياً مع أنه لم يفعل شيئاً — وهذا نوع من الأخطاء (Bugs) المؤلمة جداً في تتبّعها لاحقاً.
cp expenses.db expenses.backup.db
تستغرق ثانية واحدة وقد تنقذ مساءك. أو استخدم دالة النسخ الاحتياطي المدمجة
conn.backup()
التي تعمل حتى أثناء استخدام قاعدة البيانات.
5 أخطاء يقع فيها المبتدئون باستمرار
هذه ليست مخاوف نظرية — إنها المشاكل التي أصادفها مراراً عند مراجعة أكواد الآخرين. استوعب هذه الأخطاء وستتجاوز منحنى التعلّم المؤلم الذي يمرّ به معظم المطورين.
-
أخطاء "Database is locked". غالباً سببها ترك الاتصالات مفتوحة أو نسيان الـ commit. نمط
with get_connection()من الخطوة 1 يحلّ 95% من هذه المشاكل. الـ 5% الباقية تأتي من تشغيل عدة عمليات (Processes) ضد نفس قاعدة البيانات — فعّل وضع WAL (PRAGMA journal_mode=WAL;) وستختفي المشكلة عادة. - تخزين المبالغ المالية كـ REAL (نقطة عائمة). فعلتُ هذا في متتبعنا لأنه يُبسّط المثال، لكن لأي عمل محاسبي جدي، خزّن القيمة بالقروش/الفلوس كـ INTEGER (1250 بدلاً من 12.50). الحساب بالنقطة العائمة فيه أخطاء تقريب تتراكم عبر آلاف العمليات — كابوس محاسبي كلاسيكي.
-
التعامل مع التواريخ كنصوص عشوائية. خزّن التواريخ دائماً بصيغة ISO 8601
(
YYYY-MM-DD) حتى تعمل دوال التاريخ في SQLite وتُرتّب المقارنات النصية بشكل صحيح. "5 أبريل 2026" قد تكون مقروءة، لكنها تُرتَّب أبجدياً — مما يعني أن "مارس" تأتي بعد "أبريل" والترتيب يصبح فوضى. - تأجيل الفهارس (Indexes) "إلى وقت لاحق". جدول فيه 10,000 صف بدون فهرس على العمود الذي تصفّي حسبه سيكون بطيئاً حتى على لاب توب بـ 50 ألف جنيه. نفس الاستعلام مع فهرس يعمل في أقل من ميلي ثانية على Raspberry Pi رخيص. أضف الفهارس مبكراً لأي عمود ستصفّي أو تربط (Join) عليه.
-
عدم استخدام المعاملات (Transactions) للعمليات المرتبطة. إذا كنت تُدرج نفقة وتُحدّث
إجمالياً تراكمياً في جدول آخر، نفّذ العمليتين داخل
with get_connection()واحد. بهذه الطريقة، إما تنجح العمليتان معاً أو تفشلان معاً — بياناتك تبقى متّسقة. هذه هي الفائدة الحقيقية من المعاملات.
نصائح أداء تُحدث فرقاً حقيقياً
SQLite سريعة افتراضياً، لكن هناك بضعة إعدادات (Pragmas) تحوّلها من "سريعة بما يكفي" إلى "سريعة لدرجة غير عادلة تقريباً". هذه هي الإعدادات التي أضيفها لكل مشروع تقريباً بعد الساعة الأولى:
Python
def configure(conn):
"""تطبيق إعدادات أداء جيدة على الاتصال."""
conn.execute("PRAGMA journal_mode = WAL") # قراءات متزامنة + أمان أفضل عند الانهيار
conn.execute("PRAGMA synchronous = NORMAL") # كتابة أسرع، لا يزال آمناً مع WAL
conn.execute("PRAGMA foreign_keys = ON") # فرض المفاتيح الأجنبية (مغلقة افتراضياً!)
conn.execute("PRAGMA cache_size = -64000") # استخدام 64 ميغا للكاش (السالب يعني كيلوبايت)
conn.execute("PRAGMA temp_store = MEMORY") # الجداول المؤقتة في الذاكرة
أضف استدعاء configure(conn)
داخل دالة get_connection()
من الخطوة 1، مباشرة بعد فتح الاتصال. أكبر مكسب فردي هو وضع WAL — يسمح لعدة قُرّاء
بالعمل بينما هناك كاتب نشط، وهذا وحده يحلّ معظم شكاوى "database is locked".
الكود الكامل في ملف واحد
إليك كل ما بنيناه معاً، مُجَمَّعاً في ملف واحد يمكنك حفظه باسم
tracker.py
وتشغيله اليوم. حوالي 80 سطراً — صغير بما يكفي لتقرأه في جلسة واحدة، ومفيد بما يكفي لتتبّع نفقاتك فعلاً:
tracker.py
import sqlite3
from contextlib import contextmanager
DB_PATH = "expenses.db"
SCHEMA_SQL = """
CREATE TABLE IF NOT EXISTS expenses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
amount REAL NOT NULL CHECK (amount > 0),
category TEXT NOT NULL,
description TEXT,
spent_on TEXT NOT NULL DEFAULT (date('now'))
);
CREATE INDEX IF NOT EXISTS idx_expenses_date ON expenses (spent_on);
CREATE INDEX IF NOT EXISTS idx_expenses_category ON expenses (category);
"""
@contextmanager
def get_connection():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode = WAL")
conn.execute("PRAGMA foreign_keys = ON")
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
def init_db():
with get_connection() as conn:
conn.executescript(SCHEMA_SQL)
def add_expense(amount, category, description=None, spent_on=None):
sql = """INSERT INTO expenses (amount, category, description, spent_on)
VALUES (?, ?, ?, COALESCE(?, date('now')))"""
with get_connection() as conn:
return conn.execute(sql, (amount, category, description, spent_on)).lastrowid
def list_recent(limit=10):
sql = "SELECT * FROM expenses ORDER BY spent_on DESC, id DESC LIMIT ?"
with get_connection() as conn:
return conn.execute(sql, (limit,)).fetchall()
def total_by_category():
sql = """SELECT category, ROUND(SUM(amount), 2) AS total, COUNT(*) AS num
FROM expenses GROUP BY category ORDER BY total DESC"""
with get_connection() as conn:
return conn.execute(sql).fetchall()
def update_amount(expense_id, new_amount):
sql = "UPDATE expenses SET amount = ? WHERE id = ?"
with get_connection() as conn:
return conn.execute(sql, (new_amount, expense_id)).rowcount > 0
def delete_expense(expense_id):
sql = "DELETE FROM expenses WHERE id = ?"
with get_connection() as conn:
return conn.execute(sql, (expense_id,)).rowcount > 0
if __name__ == "__main__":
init_db()
add_expense(50.00, "طعام", "غداء")
add_expense(25.00, "مواصلات", "تذكرة مترو")
add_expense(450.00, "فواتير", "كهرباء الشهر")
print("\nأحدث النفقات:")
for row in list_recent():
desc = row['description'] or ''
print(f" #{row['id']:>3} {row['spent_on']} {row['category']:<10} {row['amount']:>7.2f} {desc}")
print("\nالإجماليات حسب الفئة:")
for row in total_by_category():
print(f" {row['category']:<12} {row['total']:>8.2f} ({row['num']} عملية)")
شغّله بأمر python tracker.py
وسترى النفقات الثلاث المُدخَلة معروضة وملخّصة. سيظهر ملف
expenses.db
في نفس المجلد — هذه قاعدة بياناتك بالكامل، قابلة للنقل وجاهزة للنسخ الاحتياطي.
إلى أين تأخذ هذا المشروع بعد ذلك؟
لديك الآن أساس عمل قوي. الجزء الممتع هو التوسّع به. إليك خمسة اتجاهات، مرتّبة من الأسهل إلى الأكثر طموحاً:
- قائمة تفاعلية في الطرفية. غلّف الدوال داخل حلقة
while Trueبسيطة مع خيارات للإضافة، العرض، والحذف. نصف ساعة عمل، ويصبح لديك تطبيق حقيقي. - استيراد/تصدير CSV. استخدم مكتبة Python المدمجة
csvلاستيراد النفقات من كشف حساب بنكي، أو لتصدير بياناتك للتحليل في Excel. - تقارير شهرية بصرية. أضف دالة تُرجع الإنفاق مجمّعاً حسب الشهر، ثم ارسمه باستخدام
matplotlibلرؤية الاتجاهات عبر الزمن. - واجهة ويب. غلّف الدوال داخل تطبيق Flask أو FastAPI صغير — طبقة الـ SQL تبقى كما هي، وتضيف فقط نقاط نهاية HTTP حولها.
- إصدار متعدد المستخدمين. أضف جدول
usersومفتاحاً أجنبياً (Foreign Key) من جدولexpensesإليه. هنا ستحتاج لتفعيلPRAGMA foreign_keys = ONوالبدء في التفكير في المصادقة وتسجيل الدخول.
إذا كنت تفكّر في الانتقال إلى قاعدة بيانات قائمة على خادم لإحدى هذه التوسعات، فإن دليلنا حول المقارنة بين MySQL و PostgreSQL و SQLite يشرح بالتفصيل متى تكون كل واحدة هي الخيار المناسب. وللمهتمين بالحوسبة السحابية، نظرتنا الشاملة على قواعد البيانات السحابية على AWS و Azure و Google Cloud ستساعدك على فهم الخيارات المتاحة.
خلاصة القول
أكبر معروف يمكن أن تقدّمه لنفسك عند تعلّم قواعد البيانات هو أن تتوقف عن القراءة وتبدأ في تنفيذ الاستعلامات.
SQLite هي البيئة الأكثر ودّية على وجه الأرض لتفعل ذلك — لا خادم، لا تثبيت، لا إعدادات. فقط
import sqlite3
وأنت على بُعد قاعدة بيانات من برنامج حقيقي.
الأنماط التي شرحتُها هنا — اتصالات مُدارة بـ context manager، استعلامات معلّمة، فهارس مُضافة من البداية، rollback مناسب عند الأخطاء — ليست "خدعاً متقدمة". إنها الحد الأدنى لكتابة كود لن تندم عليه بعد ستة أشهر. شروحات المبتدئين تتجاهلها لأنها تضيف بضعة أسطر. كود الإنتاج يستخدمها لأن البديل هو أن تستيقظ في الثانية فجراً لتُصلح أخطاء.
خذ المتتبع، اعبث به، اكسره، وسّعه. كل مفهوم في قواعد البيانات ستحتاجه لاحقاً — الـ joins، المعاملات، تطوير المخططات (Schema Migrations)، تحسين الاستعلامات — يمكن الوصول إليه من هنا. أنت لا تتعلّم "SQLite للمبتدئين". أنت تتعلّم SQL بالطريقة التي تُستخدَم بها فعلاً.
إذا أعجبك هذا الأسلوب العملي، تابع مع خارطة طريق تعلّم البرمجة للمبتدئين — تُريك بالضبط أين تقع مهارات قواعد البيانات في الصورة الأكبر لكي تصبح مطوّراً محترفاً.
أعجبك الأسلوب العملي؟
انضم إلى مئات المشتركين واحصل على أحدث الأدلة التقنية والبرمجية العملية — مشاريع حقيقية لا نظريات — تصلك مباشرة على بريدك.
نعم، أشترك! ✉️🔒 لا رسائل مزعجة أبداً. نحترم صندوق بريدك.
الأسئلة الشائعة
هذه الأسئلة الأكثر شيوعاً التي يطرحها القرّاء والطلاب. إن لم تجد سؤالك هنا، اتركه في التعليقات وسأضيفه.
يسعدنا أن نسمع آراءكم! اتركوا تعليقاً أدناه وشاركوا تجاربكم أو أسئلتكم.