ویرگول
ورودثبت نام
رضا رضایی
رضا رضاییبرنامه‌نویس جونیور بک‌اند با تمرکز بر پایتون و فریم‌ورک جنگو. علاقه‌مند به ساخت برنامه‌های تحت وب مقیاس‌پذیر و قابل اعتماد. مشتاق مشارکت در پروژه‌های واقعی و پیشرفت در توسعه بک‌اند.
رضا رضایی
رضا رضایی
خواندن ۷ دقیقه·۶ ساعت پیش

۱.۱۲: کار با QuerySet و جستجو در پایگاه داده در جنگو | ترجمه کتاب Django 5 By Example

ترجمه کتاب Django 5 By Example - کار با کوئری ست ها در جنگو
ترجمه کتاب Django 5 By Example - کار با کوئری ست ها در جنگو

QuerySet دقیقا چیست و چه وظیفه ای بر عهده دارد؟

حالا که یک پنل مدیریت کاملاً کاربردی برای مدیریت پست‌های بلاگ داریم، زمان مناسبی است که بیاموزیم چگونه به صورت برنامه‌نویسی‌شده (Programmatically) داده‌ها را در پایگاه داده بخوانیم و بنویسیم.

ORM جنگو، یک API قدرتمند برای انتزاع پایگاه داده است که به شما امکان می‌دهد آبجکت‌ها را به آسانی ایجاد، بازیابی، به‌روزرسانی و حذف کنید. ORM به شما اجازه می‌دهد تا کوئری‌های SQL را با استفاده از پارادایم شیءگرا در پایتون تولید کنید. می‌توانید آن را روشی برای تعامل با پایگاه داده به سبک پایتونی (Pythonic) در نظر بگیرید، بدون اینکه نیاز باشد کوئری‌های خام SQL بنویسید.

ORM مدل‌های شما را به جداول پایگاه داده نگاشت می‌کند و یک رابط پایتونی ساده برای تعامل با دیتابیس در اختیارتان قرار می‌دهد. ORM کوئری‌های SQL را تولید کرده و نتایج را به آبجکت‌های مدل نگاشت می‌کند. این ابزار با MySQL، PostgreSQL، SQLite، Oracle و MariaDB سازگار است.

به یاد داشته باشید که می‌توانید پایگاه داده پروژه خود را در تنظیمات DATABASES داخل فایل settings.py تعریف کنید. جنگو می‌تواند همزمان با چندین پایگاه داده کار کند و شما می‌توانید Routerهای دیتابیس را برای ایجاد طرح‌های مسیریابی داده اختصاصی برنامه‌نویسی کنید.

پس از ایجاد مدل‌های داده، جنگو یک API رایگان برای تعامل با آن‌ها به شما می‌دهد. می‌توانید مستندات رسمی مربوط به API مدل‌ها را در آدرس https://docs.djangoproject.com/en/5.0/ref/models/ مطالعه کنید.

ORM جنگو بر پایه QuerySetها بنا شده است. یک QuerySet مجموعه‌ای از کوئری‌های پایگاه داده برای بازیابی آبجکت‌ها از دیتابیس است. شما می‌توانید فیلترهایی را روی QuerySetها اعمال کنید تا نتایج کوئری را بر اساس پارامترهای مشخص محدود کنید. QuerySet معادل عبارت SELECT در SQL است و فیلترها شروط و محدودکننده‌هایی مانند WHERE یا LIMIT محسوب می‌شوند.

در ادامه می‌آموزید که چگونه QuerySetها را بسازید و اجرا کنید.

ایجاد آبجکت‌ها

برای باز کردن شل پایتون، دستور زیر را در ترمینال اجرا کنید:

python manage.py shell

سپس خطوط زیر را وارد کنید:

>>> from django.contrib.auth.models import User >>> from blog.models import Post >>> user = User.objects.get(username='admin') >>> post = Post(title='Another post', slug='another-post', body='Post body.', author=user) >>> post.save()

بیایید عملکرد این کد را تحلیل کنیم:

ابتدا، آبجکت کاربر با نام کاربری admin را بازیابی می‌کنیم:

user = User.objects.get(username='admin')

متد get() به ما امکان می‌دهد یک آبجکت تکی را از پایگاه داده بازیابی کنیم. این متد در پشت صحنه یک دستور SELECT اجرا می‌کند. توجه داشته باشید که این متد انتظار دارد دقیقاً یک نتیجه منطبق با کوئری پیدا کند. اگر دیتابیس هیچ نتیجه‌ای برنگرداند، این متد استثنای DoesNotExist و اگر بیش از یک نتیجه برگرداند، استثنای MultipleObjectsReturned را پرتاب می‌کند. هر دوی این استثناها ویژگی‌های کلاس مدلی هستند که کوئری روی آن انجام می‌شود.

سپس یک نمونه از Post با عنوان، اسلاگ و بدنه سفارشی ایجاد می‌کنیم و کاربری که قبلاً بازیابی کردیم را به عنوان نویسنده پست قرار می‌دهیم. این آبجکت در حافظه (RAM) قرار دارد و هنوز در دیتابیس ذخیره نشده است.

در نهایت، با استفاده از متد save()، آبجکت Post را در دیتابیس ذخیره می‌کنیم. این کار در پشت صحنه دستور INSERT را اجرا می‌کند.

شما می‌توانید ایجاد و ذخیره آبجکت را در یک مرحله با استفاده از متد create() نیز انجام دهید:

>>> Post.objects.create(title='One more post', slug='one-more-post', body='Post body.', author=user)

در شرایطی که نیاز دارید آبجکتی را از دیتابیس بگیرید یا اگر وجود نداشت آن را بسازید، متد get_or_create() کارآمد است. این متد یک تاپل شامل آبجکت بازیابی‌شده (یا ایجاد شده) و یک مقدار بولی (Boolean) که نشان می‌دهد آیا آبجکت جدید ساخته شده است یا خیر، برمی‌گرداند.

به‌روزرسانی آبجکت‌ها

کافی است ویژگی‌های آبجکت را تغییر داده و دوباره save() را فراخوانی کنید:

>>> post.title = 'New title' >>> post.save()

این بار متد save() دستور UPDATE را در SQL اجرا می‌کند.

بازیابی آبجکت‌ها

هر مدل جنگو حداقل یک Manager دارد و مدیر پیش‌فرض objects نامیده می‌شود. برای دریافت تمام آبجکت‌ها از یک جدول، از متد all() استفاده می‌کنیم:

all_posts = Post.objects.all()

نکته کلیدی اینجاست: QuerySetها تنبل (Lazy) هستند. یعنی تا زمانی که مجبور به اجرا نشوند، کوئری به دیتابیس ارسال نمی‌کنند. این ویژگی باعث بهره‌وری بالای ORM می‌شود.


فیلتر کردن آبجکت‌ها

برای فیلتر کردن یک QuerySet، از متد filter() استفاده می‌کنیم که به شما اجازه می‌دهد محتوای عبارت WHERE در SQL را مشخص کنید. (در شل وارد کنید):

Post.objects.filter(title='Who was Django Reinhardt?')

نکته: تغییراتی که روی یک مدل اعمال می‌کنید تا زمانی که متد save() را فراخوانی نکنید، در دیتابیس ثبت نمی‌شوند.

با چاپ کردن ویژگی query از یک QuerySet، می‌توانید دستور SQL تولید شده توسط جنگو را مشاهده کنید:

خروجی SQL نشان می‌دهد که یک جستجوی دقیق (Exact match) روی ستون title انجام شده و به دلیل اینکه ترتیب خاصی مشخص نکردیم، از گزینه پیش‌فرض ordering در تنظیمات Meta مدل استفاده شده است.

>>> posts = Post.objects.filter(title='Who was Django Reinhardt?') >>> print(posts.query) # <- query اتریبیوت

با چاپ اتریبیوت query از کوئری-ست دریافتی، میتوان کد SQL تولید شده توسط جنگو را مشاهده کرد که در ادامه خواهید دید:

SELECT "blog_post"."id", "blog_post"."title", "blog_post"."slug", "blog_ post"."author_id", "blog_post"."body", "blog_post"."publish", "blog_ post"."created", "blog_post"."updated", "blog_post"."status" FROM "blog_post" WHERE "blog_post"."title" = Who was Django Reinhardt? ORDER BY "blog_ post"."publish" DESC

انواع جستجو در QuerySet:

در این بخش از پست به بررسی QuerySetها در جنگو (Django) و انواع مختلف lookup یا الگوهای جستجو می‌پردازیم.

مثال پیشینِ QuerySet شامل یک فیلترِ جستجو (Lookup) با تطابق دقیق (Exact match) بود. رابطِ QuerySet انواع مختلفی از جستجوها را در اختیار شما قرار می‌دهد. برای تعریف نوع جستجو از دو آندرلاین استفاده می‌شود، که فرمت آن به صورت field__lookup است. برای نمونه، دستور زیر یک تطابق دقیق ایجاد می‌کند:

1) جستجوی دقیق

برای جستجوی دقیق از __exact استفاده می‌شود:

Post.objects.filter(id__exact=1)

اگر نوع جستجو را مشخص نکنید، جنگو به‌صورت پیش‌فرض exact را فرض می‌کند:

Post.objects.filter(id=1)

2) جستجوی دقیق بدون حساسیت به حروف

برای جستجوی دقیق بدون حساسیت به حروف کوچک و بزرگ از __iexact استفاده می‌شود:

Post.objects.filter(title__iexact='who was django reinhardt?')

3) جستجوی شامل

برای بررسی اینکه یک مقدار داخل مقدار فیلد وجود دارد از contains__ استفاده می‌شود. این lookup به
SQL LIKE ترجمه می‌شود:

WHERE title LIKE '%Django%'

نسخه بدون حساسیت به حروف آن نیز با icontains__ در دسترس است.

4) جستجو در یک مجموعه

برای بررسی وجود یک مقدار در یک iterable مانند لیست، tuple یا حتی یک QuerySet دیگر از __in استفاده می‌شود:

Post.objects.filter(id__in=[1, 3])

5) عملگرهای مقایسه‌ای

این lookupها برای مقایسه عددی و ترتیبی استفاده می‌شوند:

  • بزرگ‌تر از: gt__

Post.objects.filter(id__gt=3)

معادل:

... WHERE id > 3
  • بزرگ‌تر یا مساوی: gte__

  • کوچک‌تر از: lt__

  • کوچک‌تر یا مساوی: lte__

6) جستجو با شروع/پایان متن

برای تطبیق ابتدای یا انتهای رشته:

  • startswith__

  • istartswith__

  • endswith__

  • iendswith__

7) جستجوهای مبتنی بر تاریخ

می‌توانید بر اساس اجزای مختلف یک DateTimeField یا DateField جستجو کنید:

Post.objects.filter(publish__date=date(2024, 1, 31)) Post.objects.filter(publish__year=2024) Post.objects.filter(publish__month=1) Post.objects.filter(publish__day=1) Post.objects.filter(publish__date__gt=date(2024, 1, 1))

جستجو روی فیلدهای مرتبط

با استفاده از دو آندرلاین (__) می‌توان روی فیلدهای مدل‌های مرتبط هم فیلتر اعمال کرد. (مثل کلید خارجی یا 1:M):

Post.objects.filter(author__username='admin')

همچنین می‌توان جستجو در مدل مرتبط را زنجیره‌ای کرد:

Post.objects.filter(author__username__istartswith='ad')

و حتی چند شرط را همزمان اعمال کرد:

Post.objects.filter(publish__year=2024, author__username='admin')

زنجیره‌ای کردن فیلترها

نتیجه‌ی filter() خودش یک QuerySet دیگر است، بنابراین می‌توان فیلترها را زنجیره‌ای کرد:

Post.objects.filter(publish__year=2024) \ .filter(author__username='admin')

حذف نتایج با ()exclude

برای حذف برخی نتایج از QuerySet از ()exclude استفاده می‌شود:

Post.objects.filter(publish__year=2024) \ .exclude(title__startswith='Why')

مرتب‌سازی آبجکت‌ها

مرتب‌سازی پیش‌فرض در گزینه ordering در کلاس Meta مدل تعریف می‌شود.

برای تغییر ترتیب از ()order_by استفاده می‌کنیم:

  • صعودی:

Post.objects.order_by('title')
  • نزولی:

Post.objects.order_by('-title')
  • بر اساس چند فیلد:

Post.objects.order_by('author', 'title')
  • ترتیب تصادفی:

Post.objects.order_by('?')

محدود کردن QuerySetها

می‌توان با syntax برش آرایه‌ای (slicing) تعداد نتایج را محدود کرد:

Post.objects.all()[:5]

این معادل SQL LIMIT 5 است.

مثال دیگر:

Post.objects.all()[3:6]

این مورد معادل دریافت اشیای چهارم تا ششم است و از نظر SQL به OFFSET و LIMIT ترجمه می‌شود.

برای گرفتن یک شیء منفرد نیز می‌توان از index استفاده کرد:

Post.objects.order_by('?')[0]

شمارش اشیاء

متد count() تعداد کل اشیای منطبق با QuerySet را برمی‌گرداند:

Post.objects.filter(id__lt=3).count()

این معادل SELECT COUNT(*) در SQL است.


بررسی وجود داشتن نتیجه کوئری

برای بررسی اینکه آیا QuerySet نتیجه‌ای دارد یا نه از ()exists استفاده می‌شود:

Post.objects.filter(title__startswith='Why').exists()

خروجی این متد True یا False است.


حذف اشیاء

برای حذف یک نمونه از متد ()delete استفاده می‌شود:

post = Post.objects.get(id=1) post.delete()

توجه داشته باشید که حذف یک آبجکت می‌تواند آبجکت‌های وابسته را هم حذف کند، به‌خصوص وقتی فیلدهای ForeignKey با on_delete=models.CASCADE تعریف شده باشند.


جستجوهای پیچیده به وسیله Q object

فیلترهای ()filter به‌صورت پیش‌فرض با AND در SQL ترکیب می‌شوند.

برای ساخت شرط‌های پیچیده‌تر، مثل OR، از Q object استفاده می‌شود.

Q object مجموعه‌ای از جستجوها | lookup را کپسوله می‌کند و می‌توان آن‌ها را با عملگرهای زیر ترکیب کرد:

  • & برای AND

  • | برای OR

  • ^ برای XOR

مثال:

pythonfrom django.db.models import Q starts_who = Q(title__istartswith='who') starts_why = Q(title__istartswith='why') Post.objects.filter(starts_who | starts_why)

برای اطلاعات بیشتر:

https://docs.djangoproject.com/en/5.0/topics/db/queries/#complex-lookups-with-q-objects


زمان ارزیابی QuerySetها

ساختن یک QuerySet به‌تنهایی هیچ کوئری‌ای به دیتابیس ارسال نمی‌کند تا زمانی که evaluate شود.

ویژگی مهم QuerySetها این است که lazy هستند؛ یعنی تا زمان نیاز واقعی اجرا نمی‌شوند. این باعث می‌شود بتوانید چندین ()filter را زنجیره‌ای کنید بدون اینکه هنوز دیتابیس را hit کنید.

QuerySetها معمولاً در موارد زیر ارزیابی می‌شوند:

  • اولین بار که روی آن‌ها iterate می‌کنید

  • هنگام slicing مثل:

Post.objects.all()[:3]
  • هنگام pickle یا cache کردن

  • هنگام فراخوانی ()repr یا ()len

  • هنگام تبدیل صریح به ()list

  • هنگام استفاده در شرط‌های بولی مانند ()bool، or، and یا if


پست قبلی: (۱.۱۱: اضافه کردن آیکون شمارنده به فیلترها)

پست بعدی: (۱.۱۳: یادگیری ModelManager و شخصی سازی آن)

۰
۰
رضا رضایی
رضا رضایی
برنامه‌نویس جونیور بک‌اند با تمرکز بر پایتون و فریم‌ورک جنگو. علاقه‌مند به ساخت برنامه‌های تحت وب مقیاس‌پذیر و قابل اعتماد. مشتاق مشارکت در پروژه‌های واقعی و پیشرفت در توسعه بک‌اند.
شاید از این پست‌ها خوشتان بیاید