Advanced ORM | Django'ning Foydali Expression'lari haqida

Advanced Expressions in Django ORM

Django'dagi ba'zi foydali expression'larni haqida va ularni qanday qo'llashni misollar bilan ko'rib chiqamiz.

7th March 2025

djangoormexpression

Django ORM juda qulay va kuchli vosita hisoblanadi, lekin ko'p hollarda murakkab operatsiyalarni amalga oshirishda resurslar va vaqt sarfi ortishi mumkin. Ushbu maqolada Django ORM ning ba'zi foydali expressions'laridan bazilari (OuterRef, Subquery, Aggregate, Value, ExpressionWrapper, F, Window, RowRange, ValueRange, When, Case, Coalesce, etc...) yordamida jarayonlarni qanday tezlashtirish va optimallashtirish mumkinligini ko'rib chiqamiz.

Maqolada quyidagilarni ko'rib chiqamiz:

  • Oddiy hollarda expressions'lardan foydalanmasdan ishlash.
  • Har bir expression'ga alohida misollar va ularning SQL query ko'rinishi.

Kirish

Expressions - bu Django ORM da ma'lumotlarni dinamik tarzda qayta ishlash va manipulyatsiya qilish imkonini beruvchi kuchli vosita (methodlar to'plami). Ular ko'p hollarda oddiy filter va annotatsiya operatsiyalariga qaraganda samaraliroq bo'ladi.


1. Oddiy Expressions:


1.1. F

F ma'lumotlarni ustunlar bo'yicha o'zaro solishtirish yoki hisoblash uchun ishlatiladi. Ya'ni, tasavvur qiling, sizdagi A nomli modeldagi biror field'dagi mavjud qiymatini 10% oshirishingiz kerak.

Eski usulda, siz bu ishni quyidagi ko'rinishda bajararsiz:

Misol (Expressions siz):

from myapp.models import Product

products = Product.objects.all()
for product in products:
    product.price = product.price * 1.1
    product.save()

Misol (Expressions bilan):

Narxni 10% oshirish:

from django.db.models import F
from myapp.models import Product

Product.objects.update(price=F('price') * 1.1)

Expression'dagi SQL Query:

UPDATE myapp_product SET price = price * 1.1;

1.2. Cast

Django’da Cast — bu ma’lumotlar bazasidagi maydon qiymatlarini bir turdan boshqa turga o‘zgartirish uchun ishlatiladigan foydali Expression. U django.db.models.functions modulida mavjud bo‘lib, so‘rovlar yoki annotatsiyalar jarayonida ma’lumotlar turini dinamik tarzda o‘zgartirish imkonini beradi. Bu, ayniqsa, ma’lumotlar bazasida saqlangan qiymatlarni Python’da boshqa turda ishlatish kerak bo‘lganda qo‘l keladi.

Cast nima uchun kerak?

Ma’lumotlar bazasida maydonlar ma’lum bir turda saqlanadi (masalan, CharField matn sifatida, IntegerField butun son sifatida). Ammo ba’zida bu qiymatlarni boshqa turda olish yoki ulardan foydalanish zarur bo‘ladi. Masalan, matn sifatida saqlangan raqamni butun songa aylantirish yoki aksincha. Cast aynan shu muammoni ma’lumotlar bazasi darajasida hal qiladi va kodni optimallashtiradi.

Misol (Castsiz):

Agar sizda price nomli maydon CharField sifatida saqlangan bo‘lsa va uni son sifatida qo‘shishni xohlasangiz, avval uni Python’da o‘zgartirish kerak bo‘lardi:

from myapp.models import Product

products = Product.objects.all()
total = 0
for product in products:
    total += int(product.price)  # Har bir qiymatni alohida o‘zgartirish
print(total)

Bu yondashuv ko‘p ma’lumotlar bilan ishlaganda samarasiz, chunki har bir qator uchun qo‘shimcha hisoblash talab qilinadi.

Misol (Cast bilan):

Cast yordamida bu jarayonni ma’lumotlar bazasi darajasida optimallashtirish mumkin:

from django.db.models import CharField, IntegerField
from django.db.models.functions import Cast
from myapp.models import Product

products = Product.objects.annotate(
    price_as_int=Cast('price', IntegerField())
)
total = products.aggregate(total_sum=models.Sum('price_as_int'))['total_sum']
print(total)

Bu yerda price matnli maydon IntegerField ga o‘zgartiriladi va keyin aggregate yordamida yig‘indi hisoblanadi.

SQL Query (Orqa fonda):

Yuqoridagi kodning SQL ekvivalenti quyidagicha bo‘ladi:

SELECT SUM(CAST(price AS INTEGER)) AS total_sum
FROM myapp_product;

Yana bir misol (Float ga o‘zgartirish):

Agar price maydonini o‘nlik son (FloatField) sifatida ishlatmoqchi bo‘lsangiz:

from django.db.models import FloatField
from django.db.models.functions import Cast

products = Product.objects.annotate(
    price_as_float=Cast('price', FloatField())
)
for product in products:
    print(product.price_as_float * 1.2)  # Masalan, 20% qo‘shish

Qo‘llab-quvvatlanadigan turlar:

Cast quyidagi Django model maydon turlariga o‘zgartirishni qo‘llab-quvvatlaydi:

  • IntegerField
  • FloatField
  • CharField
  • BooleanField
  • DateField va boshqalar.

Afzalliklari:

  • Optimallashtirish: Ma’lumotlar bazasi darajasida tur o‘zgartirish Python’dagi qo‘lda hisoblashdan ko‘ra tezroq.
  • Moslashuvchanlik: Turli ma’lumot turlarini bir xil so‘rovda ishlatish imkoniyati.

Kamchiliklari:

  • Xatolik ehtimoli: Agar maydon qiymati o‘zgartiriladigan turga mos kelmasa (masalan, “abc” ni butun songa o‘zgartirish), xatolik yuzaga kelishi mumkin.
  • Ma’lumotlar bazasiga bog‘liqlik: Ba’zi ma’lumotlar bazalari (masalan, SQLite) tur o‘zgartirishda cheklovlarga ega bo‘lishi mumkin.

1.3. Coalesce va Uning Qo‘llanilishi

Django’da Coalesce — bu ma’lumotlar bazasida bir nechta qiymatlar orasidan birinchi NULL bo‘lmagan qiymatni qaytarish uchun ishlatiladigan foydali funksiya. U django.db.models.functions modulida joylashgan bo‘lib, so‘rovlar yoki annotatsiyalar jarayonida NULL qiymatlarni boshqarishda juda qulaydir. Bu funksiya SQL’dagi COALESCE operatori bilan bir xil ishlaydi va bir nechta argument qabul qiladi.

Coalesce nima uchun kerak?

Ma’lumotlar bazasida ba’zi maydonlar NULL qiymatga ega bo‘lishi mumkin. Agar siz ushbu maydonlardan foydalanayotganda standart qiymat qo‘ymoqchi bo‘lsangiz yoki bir nechta maydon orasidan birinchi mavjud qiymatni tanlamoqchi bo‘lsangiz, Coalesce bu jarayonni soddalashtiradi. U birinchi NULL bo‘lmagan qiymatni qaytaradi, agar barcha qiymatlar NULL bo‘lsa, oxirgi standart qiymatni qaytarish uchun ishlatilishi mumkin.

Misol (Coalescesiz):

Agar sizda mahsulotning narxi (price) yoki chegirmali narxi (discounted_price) bo‘lsa va birinchi mavjud qiymatni olishni xohlasangiz, odatda Python’da shartli tekshiruv qilish kerak bo‘lardi:

from myapp.models import Product

products = Product.objects.all()
for product in products:
    final_price = product.price if product.price is not None else product.discounted_price
    print(final_price)

Bu yondashuv har bir qator uchun qo‘shimcha tekshirish talab qiladi va ko‘p ma’lumotlar bilan samarasiz bo‘lishi mumkin.

Misol (Coalesce bilan):

Coalesce yordamida bu jarayonni ma’lumotlar bazasi darajasida hal qilish mumkin:

from django.db.models.functions import Coalesce
from myapp.models import Product

products = Product.objects.annotate(
    final_price=Coalesce('price', 'discounted_price', 0)
)
for product in products:
    print(product.final_price)

Bu yerda final_price birinchi navbatda price ni, agar u NULL bo‘lsa discounted_price ni, agar ikkalasi ham NULL bo‘lsa 0 ni qaytaradi.

SQL Query (Orqa fonda):

Yuqoridagi kodning SQL ekvivalenti quyidagicha bo‘ladi:

SELECT COALESCE(price, discounted_price, 0) AS final_price
FROM myapp_product;

Yana bir misol (Matn bilan):

Agar sizda foydalanuvchining ismi (first_name) yoki taxallusi (nickname) bo‘lsa va birinchi mavjud qiymatni olishni xohlasangiz:

from django.db.models.functions import Coalesce
from myapp.models import User

users = User.objects.annotate(
    display_name=Coalesce('first_name', 'nickname', 'Noma’lum')
)
for user in users:
    print(user.display_name)

Bu yerda display_name birinchi navbatda first_name ni, keyin nickname ni, agar ikkalasi ham NULL bo‘lsa “Noma’lum” ni qaytaradi.

Afzalliklari:

  • Soddalik: NULL qiymatlarni boshqarishni ma’lumotlar bazasi darajasida hal qiladi.
  • Tezlik: Python’da qo‘lda tekshirish o‘rniga ma’lumotlar bazasi optimallashtirishidan foydalanadi.

Kamchiliklari:

  • Moslashuvchanlik chegarasi: Faqat birinchi NULL bo‘lmagan qiymatni qaytaradi, murakkab shartli logikani qo‘llab-quvvatlamaydi.
  • Ma’lumotlar turiga e’tibor: Argumentlar bir xil turda bo‘lishi kerak, aks holda xatolik yuzaga kelishi mumkin.

2. O'rta Darajali Expressions:

2.1. ExpressionWrapper

ExpressionWrapper murakkab matematik yoki mantiqiy ifodalarni model ustunlariga qo'llash imkonini beradi. Masalan, QQS hisoblash:

Oddiy Misol:

from django.db.models import ExpressionWrapper, DecimalField, F
from myapp.models import Product

products = Product.objects.annotate(
    tax=ExpressionWrapper(F('price') * 0.15, output_field=DecimalField())
)
for product in products:
    print(product.name, product.tax)

Bu yerda ExpressionWrapper yordamida F('price') * 0.15 ifodasi DecimalField turiga o'giriladi. Ushbu yondashuv orqali modeldagi qiymatlari bo'yicha avtomatik hisob-kitoblar amalga oshiriladi.

Misol (Expressions siz):

from myapp.models import Product

products = Product.objects.all()
for product in products:
    product.tax = product.price * 0.1
    product.save()

Misol (Expressions bilan):

10% QQS hisoblash:

from django.db.models import ExpressionWrapper, DecimalField
from myapp.models import Product

products = Product.objects.annotate(
    tax=ExpressionWrapper(F('price') * 0.1, output_field=DecimalField())
)
for product in products:
    print(product.name, product.tax)

SQL Query (Expressions bilan):

SELECT name, (price * 0.1) AS tax FROM myapp_product;

2.2. Case va When

Shartli qiymatlarni generatsiya qilish uchun ishlatiladi.

Case va When kombinatsiyasi murakkab shartlarni amalga oshirish uchun juda mos keladi. Masalan, bir nechta ustunlarni solishtirish yoki shartlarga asoslangan turli hisoblashlarni bajarish uchun quyidagi kabi yondashuvdan foydalanishingiz mumkin:

Misol:

Narxni bir nechta shartlarga qarab hisoblash:

from django.db.models import Case, When, F, DecimalField
from myapp.models import Product

products = Product.objects.annotate(
    adjusted_price=Case(
        When(category='electronics', price__gt=500, then=F('price') * 0.85),
        When(category='clothing', price__lte=500, then=F('price') * 0.75),
        When(category='furniture', then=F('price') * 0.90),
        default=F('price'),
        output_field=DecimalField()
    )
)
for product in products:
    print(product.name, product.adjusted_price)

SQL Query (Expressions bilan):

SELECT name,
       CASE 
           WHEN category = 'electronics' AND price > 500 THEN price * 0.85
           WHEN category = 'clothing' AND price <= 500 THEN price * 0.75
           WHEN category = 'furniture' THEN price * 0.90
           ELSE price 
       END AS adjusted_price
FROM myapp_product;

Bu yondashuv orqali ko'p turli shartlarni bitta SQL so'rovda amalga oshirish mumkin, bu esa dastur samaradorligini oshiradi.

Misol (Expressions siz):

from myapp.models import Product

products = Product.objects.all()
for product in products:
    if product.category == 'electronics':
        product.discount = product.price * 0.9
    elif product.category == 'clothing':
        product.discount = product.price * 0.8
    else:
        product.discount = product.price
    product.save()

Misol (Expressions bilan):

Narxni toifaga qarab belgilash:

from django.db.models import Case, When
from myapp.models import Product

products = Product.objects.annotate(
    discount=Case(
        When(category='electronics', then=F('price') * 0.9),
        When(category='clothing', then=F('price') * 0.8),
        default=F('price'),
        output_field=DecimalField()
    )
)

3. Murakkab Expressions:

3.1. Subquery va OuterRef

Subquery va OuterRef katta ma'lumotlar bilan ishlashda juda foydali bo'lib, ular yordamida ma'lumotlarni optimallashtirilgan tarzda olish mumkin. Ayniqsa, bir nechta jadval bilan ishlaganda SQL so'rovlari sonini kamaytirishga yordam beradi. Ya'ni, quyidagi misolni ko'ramiz:

So'nggi buyurtma sanasini olish:

Misol (Expressions siz):

from myapp.models import Product, Order

products = Product.objects.all()
for product in products:
    latest_order = Order.objects.filter(product_id=product.id).order_by('-order_date').first()
    product.last_order_date = latest_order.order_date if latest_order else None
    product.save()

Misol (Expressions bilan):

from django.db.models import Subquery, OuterRef
from myapp.models import Product, Order

latest_order = Order.objects.filter(product_id=OuterRef('pk')).order_by('-order_date')

products = Product.objects.annotate(
    last_order_date=Subquery(latest_order.values('order_date')[:1])
)

SQL Query (Expressions bilan):

SELECT name, 
       (SELECT order_date 
        FROM myapp_order 
        WHERE product_id = myapp_product.id 
        ORDER BY order_date DESC LIMIT 1) AS last_order_date
FROM myapp_product;

3.2. ContentType, GenericForeignKey va GenericRelation

Django’da ContentType, GenericForeignKey va GenericRelation moslashuvchan va umumiy model aloqalarini yaratish uchun ishlatiladi. Bu vositalar yordamida bir modelni turli xil boshqa modellarga bog‘lash mumkin, bu esa ma’lumotlarni dinamik tarzda boshqarish imkonini beradi. Ushbu yondashuv ayniqsa, loyihada oldindan aniq model tuzilmasini bilish qiyin bo‘lgan holatlarda foydalidir.

ContentType nima?

ContentType — Django’ning django.contrib.contenttypes modulida mavjud bo‘lib, har bir modelni identifikatsiya qilish uchun ishlatiladi. U avtomatik ravishda har bir model uchun yozuv yaratadi va modelning nomi va ilova (app) nomi bilan bog‘lanadi.

GenericForeignKey nima?

GenericForeignKey — bu oddiy ForeignKeydan farqli o‘laroq, ma’lum bir modelga emas, balki har qanday modelga bog‘lanish imkonini beruvchi maydon. U ikkita komponentdan iborat:

  1. content_type — bog‘lanadigan modelni aniqlash uchun ContentType obyekti.
  2. object_id — bog‘lanadigan obyektning ID’si.

GenericRelation nima?

GenericRelation — bu teskari aloqani ta’minlaydi. Agar bir model GenericForeignKey orqali boshqa modellarga bog‘lansa, GenericRelation yordamida ushbu bog‘lanishlarni qaytarib olish mumkin.

Misol (Oddiy yondashuvsiz):

Agar bizda Comment modeli bo‘lsa va u Product yoki Order kabi turli modellarga izoh qoldirishi kerak bo‘lsa, har bir model uchun alohida ForeignKey yaratish kerak bo‘lardi. Bu esa kodni murakkablashtiradi va moslashuvchanlikni pasaytiradi.

Misol (GenericForeignKey bilan):

from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType

class Comment(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    text = models.TextField()

class Product(models.Model):
    name = models.CharField(max_length=100)

class Order(models.Model):
    order_date = models.DateTimeField(auto_now_add=True)

Bu yerda Comment modeli Product yoki Order ga bog‘lanishi mumkin. Masalan:

product = Product.objects.create(name="Telefon")
comment = Comment.objects.create(
    content_type=ContentType.objects.get_for_model(Product),
    object_id=product.pk,
    text="Bu juda yaxshi mahsulot!"
)
print(comment.content_object)  # Telefon

Misol (GenericRelation bilan):

Agar Product modelida unga bog‘langan barcha izohlarni olishni istasak:

from django.contrib.contenttypes.fields import GenericRelation

class Product(models.Model):
    name = models.CharField(max_length=100)
    comments = GenericRelation(Comment)

Endi foydalanish:

product = Product.objects.get(name="Telefon")
comments = product.comments.all()  # Ushbu mahsulotga tegishli barcha izohlar

SQL Query (Orqa fonda):

GenericForeignKey bilan yaratilgan so‘rov quyidagicha ko‘rinadi:

SELECT * 
FROM myapp_comment 
WHERE content_type_id = <Product ContentType ID> 
AND object_id = <Product ID>;

Afzalliklari:

  • Moslashuvchanlik: Har qanday modelga bog‘lanish imkoniyati.
  • Kodni qayta ishlatish: Har bir model uchun alohida aloqa yaratish zarurati yo‘q.

Kamchiliklari:

  • So‘rovlar murakkablashishi: Oddiy ForeignKey ga qaraganda so‘rovlar optimallashtirishni talab qilishi mumkin.
  • Ma’lumotlar yaxlitligi: object_id noto‘g‘ri modelga ishora qilishi mumkin, agar ehtiyotkorlik bilan boshqarilmasa.

Xulosa

Yuqoridagi misollardan ko'rinib turibdiki, Django ORM da expressions'larni ishlatish:

  • Kodni soddalashtiradi.
  • SQL darajasida optimallikni oshiradi.
  • Ko'p vaqt va resurslarni tejaydi.

Har bir expressionni to'g'ri ishlatish orqali siz Django ORMning to'liq qudratidan foydalanishingiz mumkin. Ushbu maqolada ko'rib chiqilgan oddiy sodda misollar orqali siz bu expression'larni qanday qo'llashni qisman o'rgandingiz. Endi esa ularni o'z loyihalaringizda qo'llab, dasturlaringizni yanada samaraliroq qilishingiz mumkin.

p.s) Postni oxirigacha yetib keldganingiz uchun rahmat! Xatoliklar va takliflar bo'lsa, izoh qoldiring. Happy coding! 😊

Foydali manbalar:


🔥 Yangi maqola va postlarni o'tkazib yubormaslik uchun @DavronbekDev telegram kanalga obuna bo'ling!