~5 دقیقه مطالعه • بروزرسانی ۱۹ اسفند ۱۴۰۴
۱. مقدمه
Django سه روش برای اجرای SQL خام ارائه میدهد:
- استفاده از
RawSQLدر داخل QuerySet - استفاده از
Manager.raw()برای بازگرداندن نمونههای مدل - اجرای مستقیم SQL بدون استفاده از مدل
قبل از استفاده از SQL خام، همیشه بررسی کنید که آیا ORM Django نیاز شما را پوشش میدهد یا نه. ORM ابزارهای بسیار قدرتمندی برای فیلتر، annotate، aggregate و ساخت queryهای پیچیده دارد.
۲. استفاده از RawSQL در QuerySet
گاهی لازم است یک قطعه SQL خام را در داخل annotate یا filter استفاده کنید. برای این کار از RawSQL یا Func استفاده میشود.
۳. اجرای کوئری خام با raw()
متد raw() روی Manager امکان اجرای SQL خام و دریافت نمونههای مدل را فراهم میکند.
مثال:
for p in Person.objects.raw("SELECT * FROM myapp_person"):
print(p)
این روش یک RawQuerySet برمیگرداند که مانند QuerySet قابل پیمایش است.
نکته درباره نام جدول
Django نام جدول را از app label و نام مدل میسازد، مگر اینکه db_table را تغییر داده باشید.
هشدار:
- Django هیچ بررسی روی SQL انجام نمیدهد.
- اگر کوئری شما ردیفی برنگرداند، ممکن است خطاهای عجیب رخ دهد.
- در MySQL، تبدیل خودکار نوعها میتواند نتایج غیرمنتظره ایجاد کند.
۴. نگاشت فیلدهای SQL به فیلدهای مدل
raw() فیلدهای کوئری را بر اساس نام به فیلدهای مدل نگاشت میکند. ترتیب ستونها مهم نیست.
مثال:
SELECT last_name AS last_name, first_name AS first_name, id AS id FROM ...
یا با استفاده از translations:
name_map = {"first": "first_name", "last": "last_name"}
Person.objects.raw("SELECT * FROM some_table", translations=name_map)
۵. ایندکسگذاری روی RawQuerySet
میتوانید از ایندکس استفاده کنید:
first_person = Person.objects.raw("SELECT * FROM myapp_person")[0]
اما این ایندکسگذاری در سطح Python انجام میشود، نه SQL. برای کارایی بهتر:
SELECT * FROM myapp_person LIMIT 1
۶. حذف فیلدها (Deferred Fields)
میتوانید برخی فیلدها را در SELECT حذف کنید:
Person.objects.raw("SELECT id, first_name FROM myapp_person")
فیلدهای حذفشده هنگام دسترسی lazy load میشوند.
نکته مهم: فیلد primary key باید همیشه در کوئری باشد.
۷. ارسال پارامترهای امن به raw()
برای جلوگیری از SQL injection، همیشه از params استفاده کنید:
lname = "Doe"
Person.objects.raw("SELECT * FROM myapp_person WHERE last_name = %s", [lname])
برای دیکشنری:
WHERE last_name = %(lname)s
SQLite از دیکشنری پشتیبانی نمیکند.
هشدار امنیتی:
هرگز از string formatting استفاده نکنید:
"WHERE last_name = %s" % lname # خطرناک
و هرگز placeholder را quote نکنید:
"WHERE last_name = '%s'" # اشتباه
این کار شما را در برابر SQL injection آسیبپذیر میکند.
جمعبندی
اجرای SQL خام در Django ابزار قدرتمندی است، اما باید با دقت و فقط زمانی استفاده شود که ORM پاسخگو نباشد.
RawSQL برای قطعات کوچک SQL، raw() برای بازگرداندن نمونههای مدل، و اجرای مستقیم SQL برای کنترل کامل مناسب است.
همیشه از پارامترگذاری امن استفاده کنید و از string formatting پرهیز کنید.
۱. مقدمه
گاهی اوقات Manager.raw() کافی نیست—مثلاً زمانی که کوئری شما به مدلها نگاشت نمیشود یا نیاز دارید مستقیماً UPDATE، INSERT یا DELETE اجرا کنید. در این مواقع میتوانید کاملاً از لایهٔ مدل عبور کرده و مستقیماً با دیتابیس کار کنید.
ورودی اصلی این کار django.db.connection است که اتصال پیشفرض دیتابیس را نشان میدهد.
۲. اجرای SQL با Cursor
برای اجرای SQL، ابتدا یک cursor بگیرید و سپس execute() را فراخوانی کنید:
from django.db import connection
def my_custom_sql(self):
with connection.cursor() as cursor:
cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [self.baz])
cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
row = cursor.fetchone()
return row
نکتهٔ امنیتی: هرگز دور placeholderهای %s کوتیشن نگذارید. Django خودش مقدار را escape میکند.
استفاده از علامت درصد (%) در کوئری
اگر کوئری شما شامل % باشد، هنگام استفاده از پارامترها باید آن را escape کنید:
cursor.execute("SELECT foo FROM bar WHERE baz = '30%'")
cursor.execute("SELECT foo FROM bar WHERE baz = '30%%' AND id = %s", [self.id])
۳. کار با چند دیتابیس
اگر چند دیتابیس دارید، از django.db.connections استفاده کنید:
from django.db import connections
with connections["my_db_alias"].cursor() as cursor:
...
۴. بازگرداندن نتایج بهصورت dict
بهطور پیشفرض، cursor نتایج را بهصورت tuple برمیگرداند. برای دریافت dict میتوانید از این تابع استفاده کنید:
def dictfetchall(cursor):
columns = [col[0] for col in cursor.description]
return [dict(zip(columns, row)) for row in cursor.fetchall()]
۵. بازگرداندن نتایج بهصورت namedtuple
گزینهٔ دیگر استفاده از namedtuple است:
from collections import namedtuple
def namedtuplefetchall(cursor):
desc = cursor.description
nt_result = namedtuple("Result", [col[0] for col in desc])
return [nt_result(*row) for row in cursor.fetchall()]
این روش امکان دسترسی به فیلدها با نام یا ایندکس را فراهم میکند.
مقایسهٔ سه روش:
cursor.execute("SELECT id, parent_id FROM test LIMIT 2")
cursor.fetchall()
# → ((54360982, None), (54360880, None))
dictfetchall(cursor)
# → [{'id': 54360982, 'parent_id': None}, ...]
namedtuplefetchall(cursor)
# → [Result(id=54360982, parent_id=None), ...]
۶. Connection و Cursor
cursor و connection تقریباً مطابق استاندارد Python DB-API (PEP 249) عمل میکنند، بهجز مدیریت تراکنشها.
همیشه از placeholderهای %s استفاده کنید—even برای SQLite.
استفاده از cursor بهعنوان context manager
with connection.cursor() as c:
c.execute(...)
این معادل است با:
c = connection.cursor()
try:
c.execute(...)
finally:
c.close()
۷. فراخوانی Stored Procedure
برای فراخوانی stored procedure از cursor.callproc() استفاده کنید:
with connection.cursor() as cursor:
cursor.callproc("test_procedure", [1, "test"])
فقط Oracle از پارامترهای keyword پشتیبانی میکند.
جمعبندی
اجرای مستقیم SQL در Django زمانی مفید است که ORM پاسخگو نباشد. با استفاده از cursor میتوانید کوئریهای پیچیده، عملیات نوشتنی، کار با چند دیتابیس و فراخوانی stored procedureها را انجام دهید. همیشه از پارامترگذاری امن استفاده کنید و از string formatting برای ساخت کوئریها پرهیز کنید تا از SQL injection جلوگیری شود.
نوشته و پژوهش شده توسط دکتر شاهین صیامی