آشنایی با Query و QuerySet در جنگو (قسمت دوم)

زمان مطالعه: 6 دقیقه
آشنایی با Query و QuerySet در جنگو (قسمت اول)

بدون مقدمه به ادامه آموزش و آشنایی با کوئری و کوئری ست در جنگو می پردازیم:

فیلتر ها می توانند فیلدها را روی مدل ارجاع دهند

خب تو مثال های قسمت اول، مقدار فیلد مدل را با یک مقدار ثابت مقایسه می کردیم، حال می خواهیم مقدار یک فیلد را با مقدار فیلد دیگر از همان مدل مقایسه کنیم.

جنگو برای چنین مقایسه هایی،  F expressions معرفی کرده است. نمونه های F() به عنوان مرجعی هستند که با یک کوئری به فیلدی از یک مدل اشاره می کنند. این مراجع سپس در فیلترهای کوئری برای مقایسه مقادیر دو فیلد متفاوت از یک مدل یکسان به کار برده می شوند.

برای مثال: جهت پیدا کردن لیستی از تمامی entryهای بلاگ که کامنت های بیشتری از pingbacks دارند، ما یک objectاز F()می سازیم که به تعداد pingbacks اشاره می کند و از اون F() در کوئری استفاده می کنیم.

>>> from django.db.models import F
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))

جنگو از جمع، تفریق، ضرب، تقسیم ، توان و مُد روی F() پشتیبانی می کند. برای پیدا کردن تمامی entryهای بلاگ که بیش از دو برابر pingbacks دارای commentهستند به کد زیر نکگاهی بیاندازید:

>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks') * 2)

برای پیدا کردن تمامی entryهایی که rating اون entryاز مجموع تعداد pingback و comment کمتر باشد، کوئری زیر را داریم:

>>> Entry.objects.filter(rating__lt=F('number_of_comments') + F('number_of_pingbacks'))

درون F() نیز می توانید از دو تا underscore استفاده کنید تا relationship را گسترش دهید. برای مثال، برای استخراج همگی entry ها که نام نویسنده مشابه با نام بلاگ باشد می توانیم از کوئری زیر استفاده کنیم:

>>> Entry.objects.filter(authors__name=F('blog__name'))

برای فیلد های date و date/time ، شما می توانید یک شی timedelta را کم یا اضافه کنید. کوئری ذیل، تمامی entry های که بیش از ۳ روز بعد از انتشار ویرایش شده اند را بر می گرداند:

>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

آشنایی با متد annotate() در کوئری ست جنگو

annotate(*args**kwargs)

هر object داخل QuerySet را با لیست فراهم شده از query expression ها، حاشیه نویسی (annotate) می کند.

هر آرگومان برای annotate شدن، annotation ای می باشد که به هر objectای که از QuerySet برگردانده می شود، اضافه می شود.

توابع تجمیعی (aggregation functions ) که توسط Djangi فراهم شده اند، در اینجا تشریح شده اند.

برای مثال، اگر شما در حال فراهم کردن لیستی از بلاگ ها هستید، شما ممکن است که بخواهید تعداد entryهایی مه برای هر بلاگ ایجحاد شده است را نیز بدست بیاورید:

>>> from django.db.models import Count
>>> q = Blog.objects.annotate(Count('entry'))
# The name of the first blog
>>> q[0].name
'Blogasaurus'
# The number of entries on the first blog
>>> q[0].entry__count
42

مدل Blog صفتی با نام entry__count را توسط خودش تعریف نکرده است، اما با استفاده از آرگومان کلمه کلیدی جهت انجام تابع تجمیعی، شما می توانید نام annotation را کنترل کنید: (استفاده از نام number_of_entries به عنوان آرگومتن کلمه کلیدی)

>>> q = Blog.objects.annotate(number_of_entries=Count('entry'))
# The number of entries on the first blog, using the name provided
>>> q[0].number_of_entries
42
آشنایی با متد alias() در کوئری ست جنگو

مشابه با annotate است، منتهی بجای حاشیه نویسی (annotating) روی object، عبارت (expression) را برای استفاده بعدی با دیگر متد های کوئری ست، ذخیره می کند. این جاهایی به کار می رود که نتیجه expression خودش به تنهایی کاربرد ندارد، ولی برای فیلترینگ یا ordering یا قسمتی از یک عبارت (expression) پیچیده تر به کار می رود. انتخاب نکردن مقادیری که استفاده نمی شوند، کار بیهوده را از روی پایگاه داده حذف می کند که می تواند منجر به کارایی بهتر شود.

برای مثال، اگر شما دنبال بلاگ هایی با بیش از ۵ تا entryهستید، اما علاقه ای به تعداد دقیق entries ها ندارید، می توانید این کار را بکنید:

>>> from django.db.models import Count
>>> blogs = Blog.objects.alias(entries=Count('entry')).filter(entries__gt=5)

alias()  می تواند در اتصال و ترکیب با annotate() exclude()filter()order_by() وupdate() قرار بگیرد. برای استفاده از expressionهای alias شده با دیگر متدها، شما باید آن را به یک annotate ارتقا دهید:

Blog.objects.alias(entries=Count('entry')).annotate(
    entries=F('entries'),
).aggregate(Sum('entries'))

+.0filter() و order_by() هر دو، expressionها را به صورت مستقیم می گیرند، منتهی ساخت expression و استفاده از آن گاهی در یک مکان رخ نمی دهد. (برای مثال، متدی از qUERYsET اقدام به ساخت EXPRESSIONها می کند برای بعدا تا در VIEWS استفاده کند ). alias() اجازه ساخت expressionهای پیچیده را به صورت افزایشی می دهد، ممکن است چندین متد و ماژول را در بر بگیرد و گسترش دهد، به پارت های expression توسط aliasهایشان اشاره کند و فقط از annotate برای نمایش نتیجه نهایی استفاده کند.

آشنایی با متد order_by() در کوئری ست جنگو

order_by(*fields)

به صورت پیش فرض نتایج برگردانده شده از QuerySet با توجه به مقدار ordering داخل META ی مدل مرتب می شوند. شما می توانید این ترتیب را با متد order_by تغییر دهید:

Entry.objects.filter(pub_date__year=2005).order_by('-pub_date', 'headline')

نتایج فوق ابتدا با pub_date به صورت نزولی مرتب می شوند و سپس توسط headline به صورت صعودی. علامت منفی (-) نشانگر نزولی بودن می باشد. برای مرتب کردن به صورت تصادفی از علامت ؟ استفاده کنید.

Entry.objects.order_by('?')

نکته: توجه داشته باشید، کوئری های order_by(‘?’) با توجه به پایگاه داده ممکن است کُند و پر هزینه باشند.

اگر قصد دارید روی فیلد های مدل دیگری order کنید، مشابه قبل از دو تا underScore استفاده کنید ( ـ ـ )، به مثال زیر توجه کنید:

Entry.objects.order_by('blog__name', 'headline')
آشنایی با متد reverse() در جنگو کوئری ست

از reverse() برای برعکس کردن order که اِلِمان های کوئری ست برگردانده است، استفاده می شود.

یه استفاده رایج از reverse برای موقعیت هایی است که میخوایم با اِلِمان های آخر کوئری ست کار کنیم. همان طور که قبلا در قسمت اول توضیح دادیم (در قسمت برش دادن – slicing) امکان استفاده از بازه ی منفی در SQL نیست، نمیتوانیم بگیم seq[-5:] به معنی ۵ تا المان آخر. برای این منظور از reverse استفاده می می کنیم، به مثال زیر توجه کنید:

my_queryset.reverse()[:5]

مثال فوق، پنج نتیجه آخر از کوئری ست را بر می گرداند.

آشنایی با متد distinct() در جنگو کوئری ست

distinct(*fields)

یک کوئری ست جدید باز می گرداند که از SELECT DISTINCT در SQL Query اش استفاده می کند.

به صورت پیش فرض، یک کوئری ست، ردیف های تکراری را حذف نمی کند. در عمل، این به ندرت یک مشکل است، چرا که کوئری های ساده مانند Blog.objects.all() امکان ایجاد نسخه های تکراری ندارند. گرچه، اگر کوئری شما روی چند جدول باشد، ممکن است که نتایج مشابه و تکراری بهنگام ارزیابی کوئری ست داشته باشیم، در اینجاست که از distinctاستفاده می کنیم.

توجه داشته باشید، هر فیلدی که در order_by() فراخوانی می کنید، در ردیف های SELECT در نظر گرفته و شامل ردیف ها می شوند، در نتیجه می تواند روی نتایج distinct تاثیر بگذارد. زمانی که شما order_byروی فیلدی از یک مدل مرتبط می کنید، اون فیلد نیز در ردیف های نتیجه، در نظر گرفته می شود و آنها ممکن است بطرق دیگر منجر به عدم ظهور ردیف های تکراری شوند. بدلیل اینکه ستون های اضافی حاصل از order_byنمایش داده نمی شوند، ممکن است این طور به نظر برسد که نتایج بدون در نظر گرفتن distinct برگردانده شده اند. برای مثال فرض کنید یک جدول ما شامل ۵ ردیف تکراری باشد، ولی وقتی order_by کردیم با جدولی که با آن کلید خارجی داشته است، اون مقداری که order_by شده است به عنوان ستونی از جدول اولیه در نظر گرفته شده و درنتیجه نتایج حاصل از کوئری ست ممکن است موارد تکراری را نمایش دهد، چرا که همراه با ستون اضافه order_byدیگر تکراری محسوب نمی شوند.

اگر distinct() را بدون فیلدی بفرستیم، همه ی فیلد ها را بررسی می کند تا موارد تکراری پیدا شود، اما اکر فیلدی به آن دهیم، فقط موارد تکارری روی آن فیلد را بررسی می کند.

آشنایی با Values() در کوئری ست Django

values(*fields**expressions)

QuerySet ای باز می گرداند که اگر به عنوان تکراری استفاده شده باشد، به جای model instance دیکشنری بر می گرداند.

هر کدام از دیکشنری ها یک objectرا ارائه می دهند. با key هایی که نمایانگر نام attributeهای مدل objectهستند.

مثال زیر، دیکشنری حاصل از Values را با Object های مدل معمولی مقایسه می کند:

# This list contains a Blog object.
>>> Blog.objects.filter(name__startswith='Beatles')
<QuerySet [<Blog: Beatles Blog>]>

# This list contains a dictionary.
>>> Blog.objects.filter(name__startswith='Beatles').values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>

متد values() آرگومان آپشنال نیز می گیرد، fields، که فیلدهایی که Select باید محدود به آنها شود را مشخص می کند. اگر فیلد مشخص کنید، هر دیکشنری محدود می شود فقط به فیلد هایی که شما انتخاب کرده اید.

>>> Blog.objects.values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
>>> Blog.objects.values('id', 'name')
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>

متد Values() همچنین آرگومان دیگری دارد، **expressions، که برای annotate کردن فرستاده می شود.

>>> from django.db.models.functions import Lower
>>> Blog.objects.values(lower_name=Lower('name'))
<QuerySet [{'lower_name': 'beatles blog'}]>
چند تا مثال با Values() :
>>> Entry.objects.values()
<QuerySet [{'blog_id': 1, 'headline': 'First Entry', ...}, ...]>

>>> Entry.objects.values('blog')
<QuerySet [{'blog': 1}, ...]>

>>> Entry.objects.values('blog_id')
<QuerySet [{'blog_id': 1}, ...]>
آشنایی با values_list() در کوئری ست Django:

values_list(*fieldsflat=Falsenamed=False)

همچون با values()می باشد جز اینکه بجای بازگردانی دیکشنری، tuplesبر می گرداند که روی آن تکرار پذیر می باشد. به مثال زیر توجه کنید:

>>> Entry.objects.values_list('id', 'headline')
<QuerySet [(1, 'First entry'), ...]>
>>> from django.db.models.functions import Lower
>>> Entry.objects.values_list('id', Lower('headline'))
<QuerySet [(1, 'first entry'), ...]>

شما می تواندی به هنگام استفاده از values_list() پارامتر flat را نیز بفرستید. پارامتر flatچه می کند؟ اگر Trueباشد به این معنی است که نتایج را به عنوان مقادیر تکی بازگردانم به جای آنکه هر کدام یه tuple ابشد، به مثال زیر توجه کنید:

>>> Entry.objects.values_list('id').order_by('id')
<QuerySet[(1,), (2,), (3,), ...]>

>>> Entry.objects.values_list('id', flat=True).order_by('id')
<QuerySet [1, 2, 3, ...]>

اگر پارامتر named را True رد کنید، نتایج خوانایی بیشتری دارند و نتایج همراه با name نمایش داده می شوند.

>>> Entry.objects.values_list('id', 'headline', named=True)
<QuerySet [Row(id=1, headline='First entry'), ...]>

یکی از استفاده های رایج از valued_list را در مثال زیر و بهنگام استفاده از getمی بینید:

>>> Entry.objects.values_list('headline', flat=True).get(pk=1)
'First entry'

افکار خود را به اشتراک گذارید