কোয়েরি বিল্ডার বনাম রॉ SQL বনাম ORM
এই পাঠে যা শিখবেন
- তিনটি কোয়েরি-প্রকাশ পদ্ধতির মধ্যে বিমূর্ততা ও নিয়ন্ত্রণের ট্রেড-অফ
- একটি ফ্লুয়েন্ট, চেইনযোগ্য
QueryBuilderকীভাবে.where()/.order_by()দিয়ে ধাপে ধাপে একটি কোয়েরি-স্পেক তৈরি করে, তারপর.execute()দিয়ে প্রয়োগ করে - কেন একটি লিটারাল SQL স্ট্রিং শুধু "দেখানো" যায়, এই কোর্সে এক্সিকিউট করা যায় না
- দুইটি ভিন্ন পদ্ধতির (কোয়েরি বিল্ডার, ORM) ফলাফল একই কি না সেট-তুলনা দিয়ে যাচাই করা
১ · তিনটি পদ্ধতি, একই লক্ষ্য
প্রায় প্রতিটি ব্যাক-এন্ড ফ্রেমওয়ার্কে ডেটাবেস কোয়েরি করার জন্য একাধিক স্তরের বিমূর্ততা পাওয়া যায়। নিচের চিপ-গ্রিডে তিনটি পদ্ধতির মূল বৈশিষ্ট্য তুলনা করা হলো — একই লক্ষ্য অর্জনের তিনটি ভিন্ন উপায়।
সর্বোচ্চ নিয়ন্ত্রণ ও পারফরম্যান্স টিউনিং সম্ভব, কিন্তু ডেটাবেস-নির্দিষ্ট সিনট্যাক্স হাতে লিখতে হয় ও টাইপো সহজেই রানটাইমে ধরা পড়ে।
প্রোগ্রামিং ভাষার মধ্যেই চেইনযোগ্য মেথড দিয়ে কোয়েরি তৈরি — SQL-এর কাছাকাছি নিয়ন্ত্রণ, কিন্তু সিনট্যাক্স-চেক ও কম্পোজেবিলিটি পাওয়া যায়।
সবচেয়ে বেশি বিমূর্ততা — অবজেক্ট ও মেথড দিয়ে কাজ, ডেটাবেস সিনট্যাক্স সম্পূর্ণ আড়ালে থাকে, কিন্তু জটিল কোয়েরিতে কম নিয়ন্ত্রণ।
২ · একই কোয়েরি তিনভাবে — কোড ও যাচাই
লক্ষ্য: "১৮ বছরের বেশি বয়সী সব ইউজার, নাম অনুসারে (A→Z) সাজানো"। নিচের কোড সেলে এই একই কোয়েরি তিনভাবে লেখা হয়েছে, এবং কোয়েরি বিল্ডার ও ORM ভার্সনের ফলাফল একই কি না সেট-তুলনা দিয়ে যাচাই করা হয়েছে।
# ---------- L28-এর মিনি-ORM (পুনরায় সংজ্ঞায়িত) ----------
class QueryManager:
def __init__(self, model_cls):
self.model_cls = model_cls
def filter(self, **criteria):
return [
row for row in self.model_cls._table
if all(row.get(key) == value for key, value in criteria.items())
]
class Model:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls._table = []
cls._next_id = 1
cls.objects = QueryManager(cls)
def __init__(self, **fields):
self.id = fields.pop("id", None)
for key, value in fields.items():
setattr(self, key, value)
def save(self):
cls = type(self)
self.id = cls._next_id
cls._next_id += 1
cls._table.append(dict(self.__dict__))
return self
class User(Model):
pass
people = [("Zafar", 25), ("Aporna", 30), ("Karim", 15), ("Bithi", 22), ("Nadia", 17), ("Rafi", 40)]
for name, age in people:
User(name=name, age=age).save()
# ---------- ১. ফ্লুয়েন্ট কোয়েরি বিল্ডার ----------
class QueryBuilder:
def __init__(self, table):
self.table = table
self._filters = {}
self._order_key = None
def where(self, **criteria):
self._filters.update(criteria)
return self # নিজেকেই রিটার্ন করে -- চেইনিং সম্ভব করে তোলে
def order_by(self, key):
self._order_key = key
return self
def execute(self):
result = []
for row in self.table:
ok = True
for key, value in self._filters.items():
if key.endswith("__gt"):
field = key[: -len("__gt")]
if not (row.get(field) is not None and row[field] > value):
ok = False
break
elif row.get(key) != value:
ok = False
break
if ok:
result.append(row)
if self._order_key:
result = sorted(result, key=lambda r: r[self._order_key])
return result
qb_result = QueryBuilder(User._table).where(age__gt=18).order_by("name").execute()
print("১. কোয়েরি বিল্ডার ফলাফল:")
for row in qb_result:
print(" ", row["name"], row["age"])
# ---------- ২. লিটারাল SQL -- শুধু একটি স্ট্রিং, এক্সিকিউট করা হয় না ----------
raw_sql = "SELECT * FROM users WHERE age > 18 ORDER BY name ASC;"
print("\n২. লিটারাল SQL (শুধু দেখানো হলো, এক্সিকিউট করা হয়নি):")
print(" ", raw_sql)
# ---------- ৩. মিনি-ORM স্টাইল ----------
# এই সহজ filter() শুধু ইকুয়ালিটি সাপোর্ট করে (L28), তাই বয়সের ">" শর্ত সাধারণ পাইথন দিয়ে প্রয়োগ করা হলো --
# একটি বাস্তব ORM-এ (Django-এর age__gt=18) এই একই কাজ ভেতরে সত্যিকারের SQL দিয়ে হতো
all_users = User.objects.filter()
orm_result = sorted((row for row in all_users if row["age"] > 18), key=lambda r: r["name"])
print("\n৩. মিনি-ORM ফলাফল:")
for row in orm_result:
print(" ", row["name"], row["age"])
# ---------- অভিন্নতা যাচাই ----------
qb_set = {(r["name"], r["age"]) for r in qb_result}
orm_set = {(r["name"], r["age"]) for r in orm_result}
identical = qb_set == orm_set
print(f"\nকোয়েরি বিল্ডার ও ORM ফলাফল অভিন্ন: {identical}")
assert identical
QueryBuilder.where() ও .order_by() উভয়েই self রিটার্ন
করে — এটিই চেইনিং সম্ভব করে তোলে (.where(...).order_by(...).execute() একটি লাইনে লেখা যায়)।
মিনি-ORM-এর filter() শুধু ইকুয়ালিটি চেক করে, তাই age > 18-এর মতো একটি "greater
than" শর্তের জন্য এখানে সাধারণ পাইথন জেনারেটর এক্সপ্রেশন ব্যবহার করা হয়েছে — একটি বাস্তব ORM
(যেমন Django) এই একই কাজ age__gt=18-এর মতো একটি কী-ওয়ার্ড কনভেনশন দিয়ে ভেতরে সামলায়। শেষে
qb_set == orm_set সমতা প্রমাণ করে দুটি সম্পূর্ণ ভিন্ন কোড-পথ একই সঠিক ফলাফলে পৌঁছেছে।
রॉ SQL, কোয়েরি বিল্ডার, ও ORM — তিনটিই একই অন্তর্নিহিত প্রশ্নের ("কোন রো চাই, কোন ক্রমে") ভিন্ন উপস্থাপনা। কোয়েরি বিল্ডার SQL-এর নিয়ন্ত্রণ ও প্রোগ্রামিং ভাষার নিরাপত্তা/কম্পোজেবিলিটির মাঝামাঝি একটি ভারসাম্য দেয়, আর ORM সবচেয়ে বেশি বিমূর্ততা দেয় দাম হিসেবে কিছুটা নিয়ন্ত্রণ ছেড়ে। বাস্তব প্রজেক্টে তিনটিই একসাথে ব্যবহৃত হয় — সাধারণ কোয়েরিতে ORM, জটিল/পারফরম্যান্স-ক্রিটিক্যাল কোয়েরিতে রॉ SQL বা কোয়েরি বিল্ডার।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
raw_sql ভেরিয়েবলটি কেন শুধু একটি স্ট্রিং, এবং কোড সেলে সেটি চালিয়ে সত্যিকারের ফলাফল
কেন দেখানো হয়নি?
কারণ এই কোর্সের Pyodide স্যান্ডবক্সে কোনো সত্যিকারের ডেটাবেস কানেকশন নেই (কোর্সের CLAUDE.md-এ বর্ণিত
একটি সুস্পষ্ট সীমাবদ্ধতা) — শুধু ইন-মেমরি পাইথন ডেটা স্ট্রাকচার নিয়ে কাজ করা সম্ভব। SQL স্ট্রিংটি এখানে
শুধু দেখানোর জন্য যে এটি দেখতে কেমন হতো; একটি বাস্তব ডেটাবেসের বিপরীতে এই একই SQL সত্যিকারভাবে
এক্সিকিউট করার প্র্যাকটিস ../dbms-sql/ কোর্সে কভার করা হয়েছে।
প্র ০২
মিনি-ORM ভার্সনে কেন User.objects.filter() কোনো আর্গুমেন্ট ছাড়া কল করে তারপর ম্যানুয়ালি
বয়স ফিল্টার করা হলো, সরাসরি filter(age=18)-এর মতো কিছু কেন লেখা হলো না?
কারণ এই লেসনের filter() শুধু ইকুয়ালিটি (==) চেক করে — এটি "১৮-এর বেশি" বোঝার
কোনো উপায় জানে না। filter(age=18) লিখলে শুধু ঠিক ১৮ বছর বয়সী ইউজার পাওয়া যেত, যা প্রশ্নের
উত্তর না। তাই filter()-কে আর্গুমেন্ট ছাড়া কল করে (যা পুরো টেবিল ফেরত দেয়) তারপর সাধারণ
পাইথন দিয়ে age > 18 শর্ত প্রয়োগ করা হয়েছে — এটি স্পষ্ট করে যে এই সহজ ORM-এর একটি
বাস্তব সীমাবদ্ধতা আছে, যা বাস্তব ORM-গুলো age__gt-এর মতো বিশেষ কী-ওয়ার্ড কনভেনশন দিয়ে
সমাধান করে।
প্র ০৩
অভিন্নতা যাচাইয়ে সরাসরি লিস্ট তুলনা (qb_result == orm_result) না করে সেট তুলনা
(qb_set == orm_set) করা হলো কেন?
লিস্ট তুলনা উপাদানের ক্রমও মিলতে হয়, কিন্তু "একই ফলাফল সেট" বলতে বোঝানো হয়েছে একই সারিগুলো পাওয়া গেছে
কি না, ক্রম নয় (যদিও এখানে দুটোই order_by("name")-এর কারণে একই ক্রমে আছে)। এছাড়া
dict সরাসরি তুলনাযোগ্য হলেও set-এ রাখতে হ্যাশযোগ্য উপাদান লাগে, তাই প্রতিটি রো
থেকে (name, age) টাপল বের করে সেট বানানো হয়েছে — এটি একটি নিরাপদ, ক্রম-নিরপেক্ষ তুলনা
পদ্ধতি।
অনুশীলন
-
চিন্তা করুন: যদি
QueryBuilder.order_by()-এselfরিটার্ন না করেNoneরিটার্ন করা হতো, তাহলেQueryBuilder(User._table).where(age__gt=18).order_by("name").execute()লাইনটি চালালে কী হতো?.order_by("name")কলNoneরিটার্ন করত, এবং পরের.execute()কলটিNone-এর উপর কল হতো — যাAttributeError: 'NoneType' object has no attribute 'execute'ছুড়ে ফেলত। এটাই দেখায় ফ্লুয়েন্ট চেইনিং কাজ করার জন্য প্রতিটি মেথডকে অবশ্যইself(বা অন্তত একটি সামঞ্জস্যপূর্ণ অবজেক্ট) রিটার্ন করতে হবে। -
পরীক্ষা করুন: উপরের কোড সেলে
QueryBuilder-এ একটি নতুন মেথডlimit(n)যোগ করুন যাself._limit = nসেট করেselfরিটার্ন করে, তারপরexecute()-এর শেষেif self._limit-চেক দিয়ে ফলাফলকেresult[: self._limit]-এ কেটে ফেলুন, এবং.limit(2)চেইন করে দেখুন শুধু প্রথম দুইজন ইউজারই ফেরত আসে কি না।__init__-এself._limit = Noneযোগ করে,limit(n)মেথডেself._limit = n; return selfলিখে, এবংexecute()-এরreturn-এর ঠিক আগেif self._limit: result = result[: self._limit]যোগ করলে.where(age__gt=18).order_by("name").limit(2).execute()এখন সাজানো ফলাফলের প্রথম দুইটি রো (Aporna,Bithi) ফেরত দেবে — এটি দেখায় কোয়েরি বিল্ডার প্যাটার্নটি সহজেই নতুন ক্ষমতা (limit, offset, ইত্যাদি) দিয়ে বিস্তৃত করা যায়, প্রতিটি নতুন মেথডই শুধু চেইনে আরেকটি ধাপ যোগ করে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পরবর্তী পাঠ L32 N+1 কোয়েরি সমস্যা ও Eager Loading — কোয়েরি সংখ্যা গুনে পারফরম্যান্স সমস্যা বাস্তবে দেখুন।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক ফান্ডামেন্টাল, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি ও ডিপ্লয়মেন্ট।
-
Database Management Systems কোর্স সহোদর কোর্স
বাস্তব SQL
SELECT/WHERE/ORDER BYএক্সিকিউশন সেই কোর্সে বিস্তারিত শেখানো হয়েছে। - সব 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 — সব এক জায়গায়।