পাঠ ৩৩ · ৫৭-এর মধ্যে · মডিউল ৮
Home / Courses / Mobile App Development / লোকাল স্টোরেজ

মোবাইলে লোকাল SQL ডেটাবেস

Local SQL databases on mobile
৯ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • কী-ভ্যালু স্টোরেজ ও লোকাল SQL ডেটাবেসের মধ্যে পার্থক্য — কখন কোনটি বেছে নেবেন
  • মোবাইলে SQLite-এর ভূমিকা (এবং Room/Core Data-র মতো লাইব্রেরিগুলো এর উপর কী যোগ করে)
  • একটি list-of-dicts "টেবিল"-এর উপর সত্যিকারের insert/query/ update/delete ফাংশন লেখা
  • একটি সম্পূর্ণ insert-then-query রাউন্ড ট্রিপ, সত্যিকারের ডেটা দিয়ে

১ · কেন কী-ভ্যালু স্টোরেজ যথেষ্ট নয়

L32-এর কী-ভ্যালু স্টোরেজ একটি একক, স্বাধীন মান রাখতে দুর্দান্ত। কিন্তু কল্পনা করুন একটি নোট-অ্যাপে শত শত নোট আছে, আর আপনাকে "শুধু পিন করা নোটগুলো দেখাও, তারিখ অনুযায়ী সাজিয়ে" — এই কাজটি করতে হবে। কী-ভ্যালু স্টোরে প্রতিটি নোটের জন্য আলাদা কী লাগত, আর ফিল্টার করতে সবগুলো কী নিজে হাতে লুপ করতে হতো। এখানেই লোকাল SQL ডেটাবেসLocal SQL Databaseএকাধিক সম্পর্কিত রেকর্ডকে টেবিল আকারে সংরক্ষণ করে, এবং শর্তসাপেক্ষ ফিল্টার/আপডেট/মুছে ফেলার জন্য একটি কোয়েরি ইন্টারফেস দেয়। কাজে আসে।

iOS
Core Data (একটি অবজেক্ট-গ্রাফ ফ্রেমওয়ার্ক, ভেতরে সাধারণত SQLite-ব্যাকড স্টোর ব্যবহার করে) অথবা সরাসরি SQLite।
Android
Room — SQLite-এর উপর একটি টাইপ-সেফ অ্যাবস্ট্রাকশন লেয়ার, কম্পাইল-টাইমে SQL কোয়েরি যাচাই করে।
নিচে সবখানেই
দুই প্ল্যাটফর্মেই নিচে একই ইঞ্জিন — SQLite — একটি হালকা, ফাইল-ভিত্তিক, সার্ভারবিহীন SQL ডেটাবেস।

২ · একটি সরল লোকাল টেবিল সিমুলেশন

একটি সত্যিকারের SQLite ইঞ্জিন এখানে চালানো সম্ভব নয় (Pyodide স্যান্ডবক্স শুধু Python চালায়), তাই আমরা একটি টেবিলকে dict-এর একটি Python list হিসেবে সিমুলেট করব, আর তার উপর চারটি সরাসরি ফাংশন লিখব — ../full-stack-web-frameworks/-এর পূর্ণাঙ্গ ORM-এর তুলনায় অনেক হালকা, কিন্তু একই মূল ধারণা (insert/query/update/delete) মেনে চলে।

Python
# একটি "notes" টেবিল -- dict-এর একটি লিস্ট -- এবং তার উপর চারটি সত্যিকারের ফাংশন

notes_table = []
_next_id = 1

def insert(table, **fields):
    global _next_id
    row = {"id": _next_id, **fields}
    table.append(row)
    _next_id += 1
    print(f"INSERT -> {row}")
    return row["id"]

def query(table, **filters):
    result = [row for row in table if all(row.get(k) == v for k, v in filters.items())]
    label = filters if filters else "(সব সারি)"
    print(f"QUERY  {label} -> {len(result)}টি সারি: {result}")
    return result

def update(table, filters, changes):
    matched = [row for row in table if all(row.get(k) == v for k, v in filters.items())]
    for row in matched:
        row.update(changes)
    print(f"UPDATE {filters} SET {changes} -> {len(matched)}টি সারি বদলালো")
    return len(matched)

def delete(table, **filters):
    before = len(table)
    table[:] = [row for row in table if not all(row.get(k) == v for k, v in filters.items())]
    removed = before - len(table)
    print(f"DELETE {filters} -> {removed}টি সারি মুছে গেলো")
    return removed

# ---------- একটি সম্পূর্ণ insert-then-query রাউন্ড ট্রিপ ----------
insert(notes_table, title="Grocery list", body="Milk, eggs, bread", pinned=False)
insert(notes_table, title="Meeting notes", body="Discuss Q3 roadmap", pinned=True)
insert(notes_table, title="Recipe idea", body="Bengali fish curry", pinned=False)

query(notes_table, pinned=True)

update(notes_table, {"title": "Grocery list"}, {"pinned": True})
query(notes_table, pinned=True)

delete(notes_table, title="Recipe idea")
query(notes_table)

    
লক্ষ্য করুন প্রথম query(notes_table, pinned=True) মাত্র একটি সারি ফেরত দেয় ("Meeting notes") — কিন্তু update-এর পর যখন "Grocery list"-এর pinned সত্য করে দেওয়া হয়, দ্বিতীয় query-তে দুটো সারি ফেরত আসে। শেষ query(notes_table) কোনো filters ছাড়াই কল হয়েছে — all(...) একটি খালি জেনারেটরের উপর সবসময় True ফেরায়, তাই ফিল্টার-হীন কল স্বাভাবিকভাবেই সব (বাকি থাকা) সারি ফেরত দেয়, "Recipe idea" বাদে যেটি ততক্ষণে delete হয়ে গেছে।
মূল কথা · Key takeaway

একটি লোকাল SQL-স্টাইল ডেটাবেস কী-ভ্যালু স্টোরেজের চেয়ে একধাপ উপরে — একাধিক সম্পর্কিত রেকর্ড, শর্তসাপেক্ষ কোয়েরি, আর বাল্ক আপডেট/ডিলিট সম্ভব করে। SQL-এর মৌলিক সিনট্যাক্স ও রিলেশনাল থিওরি DBMS & SQL কোর্সে ইতিমধ্যে শেখানো হয়েছে — মোবাইলের নতুন অংশটুকু হলো এই ডেটাবেসটি ডিভাইসের নিজস্ব ফাইল সিস্টেমে লোকালি বসে, কোনো নেটওয়ার্ক কল ছাড়াই তাৎক্ষণিকভাবে কাজ করে।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ query ফাংশনে all(row.get(k) == v for k, v in filters.items())-এ row[k]-এর বদলে row.get(k) কেন ব্যবহার করা হয়েছে?

কারণ প্রতিটি সারিতে সব কী নাও থাকতে পারে (উদাহরণস্বরূপ, ভবিষ্যতে একটি নতুন কলাম যোগ হলে পুরনো সারিগুলোতে সেটি নাও থাকতে পারে — L35-এ মাইগ্রেশনে এই পরিস্থিতি আসবে)। row[k] ব্যবহার করলে কী-টি না থাকলে সরাসরি KeyError দিয়ে ক্র্যাশ করত; row.get(k) নিরাপদে None ফেরায়, যা সাধারণত ফিল্টার-ভ্যালুর সাথে না মিলে স্বাভাবিকভাবেই বাদ পড়ে।

প্র ০২ এই সিমুলেশন একটি সত্যিকারের SQLite ডেটাবেস থেকে সবচেয়ে বড় কোন দিক থেকে আলাদা?

এখানে notes_table শুধু RAM-এ থাকা একটি Python লিস্ট — Pyodide স্যান্ডবক্স রিলোড হলেই হারিয়ে যায়। একটি সত্যিকারের SQLite ডেটাবেস ডিভাইসের ফাইল সিস্টেমে একটি বাইনারি ফাইলে লেখা হয়, ইনডেক্স ব্যবহার করে বড় টেবিলেও দ্রুত কোয়েরি চালায়, এবং ACID ট্রানজ্যাকশন গ্যারান্টি দেয় — এই সিমুলেশনের কোনোটিই আমাদের চারটি সরল ফাংশন করে না, এটি শুধু ইন্টারফেসের ধারণাটি শেখায়।

প্র ০৩ delete(notes_table, title="Recipe idea")-এর পর যদি আবার একই কল করা হতো, তাহলে কী প্রিন্ট হতো?

"DELETE {'title': 'Recipe idea'} -> 0টি সারি মুছে গেলো" — কারণ সারিটি আগেই মুছে ফেলা হয়েছে, তাই filters-এর সাথে মেলে এমন কোনো সারি বাকি নেই; before - len(table) এখন 0 হবে, এবং ফাংশনটি নিরাপদে কিছুই না মুছে রিটার্ন করবে।

অনুশীলন

  1. চিন্তা করুন: একটি নোট-অ্যাপে "সার্চ" ফিচার যোগ করতে চান — ইউজার একটি শব্দ লিখলে যেসব নোটের title-এ সেই শব্দ আছে সেগুলো দেখাতে হবে। এই query ফাংশনের বর্তমান ফিল্টার লজিক (== দিয়ে সমতা) দিয়ে এটি সরাসরি সম্ভব কি না ভাবুন।

    না — বর্তমান query শুধু ঠিক মিল (==) চেক করে, "আংশিক মিল" (substring) নয়। একটি সার্চ ফিচারের জন্য query-কে বাড়িয়ে filters-এর পাশাপাশি একটি আলাদা search_term প্যারামিটার নিতে হতো, আর ফিল্টার লজিকে search_term in row["title"]-এর মতো একটি চেক যোগ করতে হতো — বাস্তব SQL-এ এটাই LIKE '%...%'।

  2. পরীক্ষা করুন: কোড সেলের একদম শেষে একটি নতুন লাইনে update(notes_table, {"title": "Meeting notes"}, {"body": "Discuss Q4 roadmap"}) যোগ করুন, তারপর query(notes_table, title="Meeting notes") কল করে দেখুন body-এর মান বদলেছে কি না।

    হ্যাঁ বদলাবে — query আউটপুটে সেই সারির body এখন "Discuss Q4 roadmap" দেখাবে, কারণ update ফাংশনটি ম্যাচ হওয়া সারির উপর সরাসরি row.update(changes) কল করে — মূল notes_table লিস্টের ভেতরের dict-টিকেই মিউটেট করে, তাই পরবর্তী যেকোনো query সাথে সাথে পরিবর্তিত মানটি দেখতে পায়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ অ্যাপ লাইফসাইকেল, মোবাইল UI/UX, MVVM/MVI আর্কিটেকচার, নেভিগেশন, স্টেট ম্যানেজমেন্ট, লোকাল স্টোরেজ, ডিভাইস ফিচার ও ডিপ্লয়মেন্ট — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • DBMS & SQL কোর্স সহোদর কোর্স SQL-এর মৌলিক সিনট্যাক্স, রিলেশনাল থিওরি, ইনডেক্সিং ও নরমালাইজেশন সেই কোর্সেই বিস্তারিত শেখানো হয়েছে।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics, Full-Stack Web Frameworks ও Mobile App Development — সব এক জায়গায়।
আগের পাঠ
কী-ভ্যালু স্টোরেজ